Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ npm run dev:android

# Web App Screenshot
<p align="center">
<img src="preview-web.png" alt="Web app Screenshot example" height="300"/>
<img src="preview-web.png" alt="Web app Screenshot example" height="300"/>
</p>

# Run it on the web
Expand Down
45 changes: 8 additions & 37 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,35 @@
import { Slot } from 'expo-router';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Button, KeyboardAvoidingView, Platform, StyleSheet, View, Text } from 'react-native';
import { KeyboardAvoidingView, Platform, StyleSheet } from 'react-native';
import React from 'react';
import AppDataProvider from '@/data/providers/AppDataProvider';
import { PersistenceType } from '@/data/types';
import store from '@/store';
import { Provider as ReduxProvider } from 'react-redux';

const enabledPersistenceTypes = Platform.select({
web: [PersistenceType.localstorage, PersistenceType.indexedDB, PersistenceType.sqlite],
default: [PersistenceType.localstorage, PersistenceType.sqlite],
});
const Root = () => {
const [persistenceType, setPersistenceType] = React.useState<PersistenceType>(
Platform.select({ web: PersistenceType.indexedDB, default: PersistenceType.sqlite })
);

return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.keyboardView}>
<AppDataProvider persistenceType={persistenceType}>
<Text style={styles.title}>
Persistence type: {persistenceType}, OS: {Platform.OS}
</Text>
<View style={styles.buttons}>
{enabledPersistenceTypes.map((persistenceType) => (
<Button
key={persistenceType}
title={persistenceType}
onPress={() => setPersistenceType(persistenceType)}
/>
))}
</View>
<Slot />
</AppDataProvider>
<ReduxProvider store={store}>
<AppDataProvider>
<Slot />
</AppDataProvider>
</ReduxProvider>
</KeyboardAvoidingView>
</SafeAreaView>
);
};

const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
justifyContent: 'space-around',
alignSelf: 'center',
width: Platform.select({ web: '50%', default: '100%' }),
padding: 10,
},
container: {
flex: 1,
backgroundColor: '#f8f8f8',
},
keyboardView: {
flex: 1,
},
title: {
textAlign: 'center',
fontSize: 20,
padding: 10,
},
});

export default Root;
22 changes: 6 additions & 16 deletions app/detail/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,28 @@ import { Button, StyleSheet, Text, View } from 'react-native';
import { useDataContext } from '@/data/DataContext';
import { useAppDispatch, useAppSelector } from '@/store';
import { removeTaskHandler, selectTask } from '@/store/taskSlice';
import Header from '@/Header';

const Page = () => {
const Page: React.FC = () => {
const { id } = useLocalSearchParams<{ id: string }>();
const decodedId = parseInt(id);
const { tasksClient } = useDataContext();
const dispatch = useAppDispatch();
const task = useAppSelector(selectTask(decodedId));

const goBack = () => {
if (router.canGoBack()) {
router.back();
} else {
router.push('/');
}
};

const handleDelete = async () => {
dispatch(removeTaskHandler({ tasksClient, id: decodedId }));
goBack();
router.push('/');
};

if (!task) {
return <Unmatched />;
}
return (
<View style={styles.root}>
<View style={styles.taskItem}>
<Button
title="Go back"
onPress={goBack}
/>
</View>
<Header showBack={true}>
<Text style={styles.taskText}>Task: {task.task}</Text>
</Header>
<View style={styles.taskItem}>
<Text style={styles.taskText}>Task: {task.task}</Text>
</View>
Expand Down
36 changes: 26 additions & 10 deletions app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { FlatList, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { FlatList, Text, TextInput, TouchableOpacity } from 'react-native';
import React, { useState } from 'react';
import { Task } from '@/types';
import logger from '@/logger';
import type { ListRenderItem } from '@react-native/virtualized-lists';
import { router } from 'expo-router';
import globalStyles from '@/globalStyles';
import { useDataContext } from '@/data/DataContext';
import { useAppDispatch, useAppSelector } from '@/store';
import { addTaskHandler, selectTasksState } from '@/store/taskSlice';
import Header from '@/Header';
import Icon from '@expo/vector-icons/MaterialCommunityIcons';

const LandingPage = () => {
const { tasksClient } = useDataContext();
const LandingPage: React.FC = () => {
const { taskClient } = useDataContext();
const dispatch = useAppDispatch();
const [newTask, setNewTask] = useState('');
const tasks = useAppSelector(selectTasksState);

const addTask = async () => {
if (newTask.trim()) {
dispatch(addTaskHandler({ taskName: newTask, tasksClient }));
dispatch(addTaskHandler({ taskName: newTask, taskClient }));
setNewTask('');
}
};
Expand All @@ -35,21 +36,36 @@ const LandingPage = () => {
data={tasks}
keyExtractor={(item) => item.id.toString()}
renderItem={renderItem}
style={globalStyles.taskList}
style={globalStyles.root}
ListHeaderComponent={
<View style={globalStyles.inputContainer}>
<Header>
<TouchableOpacity
style={globalStyles.button}
onPress={() => router.push('/settings')}>
<Text style={globalStyles.buttonText}>
<Icon
name="cog"
size={globalStyles.icon.fontSize}
color={globalStyles.icon.color}
/>
</Text>
</TouchableOpacity>
<TextInput
style={globalStyles.input}
placeholder="Add a new task"
value={newTask}
onChangeText={setNewTask}
/>
<TouchableOpacity
style={globalStyles.addButton}
style={globalStyles.button}
onPress={addTask}>
<Text style={globalStyles.addButtonText}>Add</Text>
<Icon
name="plus"
size={globalStyles.icon.fontSize}
color={globalStyles.icon.color}
/>
</TouchableOpacity>
</View>
</Header>
}
/>
);
Expand Down
54 changes: 54 additions & 0 deletions app/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { View, Text, StyleSheet, Platform, Button } from 'react-native';
import { enabledPersistenceTypes } from '@/config';
import { useAppDispatch, useAppSelector } from '@/store';
import { changePersistence, selectedPersistence } from '@/store/settingsSlice';
import Header from '@/Header';
import globalStyles from '@/globalStyles';
import { clearData } from '@/store/globalReset';
import { useDataContext } from '@/data/DataContext';

const SettingsPage = () => {
const dispatch = useAppDispatch();
const persistenceType = useAppSelector(selectedPersistence);
const { opsClient } = useDataContext();

return (
<View style={globalStyles.root}>
<Header showBack={true}>
<Text style={globalStyles.headerText}>Settings</Text>
</Header>
<View style={styles.settingRow}>
<Text>
Persistence type: {persistenceType}, OS: {Platform.OS}
</Text>
{enabledPersistenceTypes.map((value) => (
<Button
key={value}
title={value}
onPress={() => dispatch(changePersistence({ value }))}
/>
))}
<View style={globalStyles.divider} />
<Text>Clear Data</Text>
<Button
title="Clear data"
onPress={() => {
console.log('aha');
dispatch(clearData({ opsClient }));
}}
/>
</View>
</View>
);
};

const styles = StyleSheet.create({
settingRow: {
alignItems: 'flex-start',
gap: 5,
padding: 16,
},
});

export default SettingsPage;
26 changes: 23 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"eslint-plugin-prettier": "^5.2.1",
"expo": "^51.0.34",
"expo-constants": "~16.0.2",
"expo-dev-client": "~4.0.29",
"expo-linking": "~6.3.1",
"expo-router": "~3.5.23",
"expo-sqlite": "~14.0.6",
Expand All @@ -40,7 +41,10 @@
"redux-devtools-expo-dev-plugin": "^0.2.1",
"semver": "^7.6.3",
"sql.js": "^1.12.0",
"expo-dev-client": "~4.0.29"
"@expo/vector-icons": "^14.0.3",
"expo-document-picker": "~12.0.2",
"expo-file-system": "~17.0.1",
"expo-sharing": "~12.0.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand Down
Binary file modified preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/DbMigrationRunner.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { SQLiteDatabase, openDatabaseAsync } from '@/data/sqliteDatabase';
import DbMigrationRunner from '@/DbMigrationRunner';
import migrations from '../migrations';
import { dbName } from '@/config';

describe('DbMigrationRunner', () => {
let sqlite: SQLiteDatabase;

beforeEach(async () => {
sqlite = await openDatabaseAsync('test.db');
sqlite = await openDatabaseAsync(dbName);
});

it('should migrate', async () => {
Expand Down
46 changes: 46 additions & 0 deletions src/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { View, StyleSheet, TouchableOpacity } from 'react-native';
import React from 'react';
import { router } from 'expo-router';
import globalStyles from '@/globalStyles';
import Icon from '@expo/vector-icons/MaterialCommunityIcons';

const Header: React.FC<{ children: React.ReactNode; showBack?: boolean }> = ({ children, showBack }) => {
return (
<View style={styles.root}>
{showBack && (
<TouchableOpacity
style={globalStyles.button}
onPress={() => {
if (router.canGoBack()) {
router.back();
} else {
router.push('/');
}
}}>
<Icon
name="arrow-left"
size={globalStyles.icon.fontSize}
color={globalStyles.icon.color}
/>
</TouchableOpacity>
)}
{children}
</View>
);
};

const styles = StyleSheet.create({
root: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
height: 60,
paddingVertical: 12,
backgroundColor: '#fff',
borderTopWidth: 1,
borderTopColor: '#e0e0e0',
gap: 5,
},
});

export default Header;
Loading