Use react-native-paper snackbar instead of ToastAndroid

This commit is contained in:
Brandon Presley 2022-08-25 13:01:01 +12:00
parent c54396cb9c
commit 2c9242b03f
6 changed files with 77 additions and 48 deletions

26
App.tsx
View File

@ -10,6 +10,7 @@ import {
DarkTheme as PaperDarkTheme, DarkTheme as PaperDarkTheme,
DefaultTheme as PaperDefaultTheme, DefaultTheme as PaperDefaultTheme,
Provider, Provider,
Snackbar,
} from 'react-native-paper'; } from 'react-native-paper';
import {SQLiteDatabase} from 'react-native-sqlite-storage'; import {SQLiteDatabase} from 'react-native-sqlite-storage';
import Ionicon from 'react-native-vector-icons/Ionicons'; import Ionicon from 'react-native-vector-icons/Ionicons';
@ -25,6 +26,9 @@ export type DrawerParamList = {
}; };
export const DatabaseContext = React.createContext<SQLiteDatabase>({} as any); export const DatabaseContext = React.createContext<SQLiteDatabase>({} as any);
export const SnackbarContext = React.createContext<{
toast: (value: string, timeout: number) => void;
}>({toast: () => null});
const CombinedDefaultTheme = { const CombinedDefaultTheme = {
...PaperDefaultTheme, ...PaperDefaultTheme,
@ -45,6 +49,7 @@ const CombinedDarkTheme = {
const App = () => { const App = () => {
const [db, setDb] = useState<SQLiteDatabase | null>(null); const [db, setDb] = useState<SQLiteDatabase | null>(null);
const [snackbar, setSnackbar] = useState('');
const dark = useColorScheme() === 'dark'; const dark = useColorScheme() === 'dark';
useEffect(() => { useEffect(() => {
@ -64,6 +69,11 @@ const App = () => {
init(); init();
}, []); }, []);
const toast = (value: string, timeout: number) => {
setSnackbar(value);
setTimeout(() => setSnackbar(''), timeout);
};
return ( return (
<Provider <Provider
theme={dark ? CombinedDarkTheme : CombinedDefaultTheme} theme={dark ? CombinedDarkTheme : CombinedDefaultTheme}
@ -71,8 +81,22 @@ const App = () => {
<NavigationContainer <NavigationContainer
theme={dark ? CombinedDarkTheme : CombinedDefaultTheme}> theme={dark ? CombinedDarkTheme : CombinedDefaultTheme}>
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} /> <StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
<Routes db={db} /> <SnackbarContext.Provider value={{toast}}>
<Routes db={db} />
</SnackbarContext.Provider>
</NavigationContainer> </NavigationContainer>
<Snackbar
onDismiss={() => setSnackbar('')}
visible={!!snackbar}
action={{
label: 'Close',
onPress: () => setSnackbar(''),
color: dark
? CombinedDarkTheme.colors.primary
: CombinedDefaultTheme.colors.primary,
}}>
{snackbar}
</Snackbar>
</Provider> </Provider>
); );
}; };

View File

@ -1,12 +1,11 @@
import {NavigationProp, useNavigation} from '@react-navigation/native'; import {NavigationProp, useNavigation} from '@react-navigation/native';
import React, {useCallback, useContext, useState} from 'react'; import React, {useCallback, useContext, useState} from 'react';
import {ToastAndroid} from 'react-native';
import DocumentPicker from 'react-native-document-picker'; import DocumentPicker from 'react-native-document-picker';
import {FileSystem} from 'react-native-file-access'; import {FileSystem} from 'react-native-file-access';
import {Divider, IconButton, Menu} from 'react-native-paper'; import {Divider, IconButton, Menu} from 'react-native-paper';
import {DatabaseContext, DrawerParamList} from './App'; import {DatabaseContext, DrawerParamList, SnackbarContext} from './App';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import {write} from './file'; import {useWrite} from './file';
import {Plan} from './plan'; import {Plan} from './plan';
import Set from './set'; import Set from './set';
@ -17,7 +16,9 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const [showRemove, setShowRemove] = useState(false); const [showRemove, setShowRemove] = useState(false);
const db = useContext(DatabaseContext); const db = useContext(DatabaseContext);
const {toast} = useContext(SnackbarContext);
const {reset} = useNavigation<NavigationProp<DrawerParamList>>(); const {reset} = useNavigation<NavigationProp<DrawerParamList>>();
const {write} = useWrite();
const exportSets = useCallback(async () => { const exportSets = useCallback(async () => {
const [result] = await db.executeSql('SELECT * FROM sets'); const [result] = await db.executeSql('SELECT * FROM sets');
@ -33,7 +34,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
.join('\n'); .join('\n');
console.log(`${DrawerMenu.name}.exportSets`, {length: sets.length}); console.log(`${DrawerMenu.name}.exportSets`, {length: sets.length});
await write('sets.csv', data); await write('sets.csv', data);
}, [db]); }, [db, write]);
const exportPlans = useCallback(async () => { const exportPlans = useCallback(async () => {
const [result] = await db.executeSql('SELECT * FROM plans'); const [result] = await db.executeSql('SELECT * FROM plans');
@ -44,7 +45,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
.join('\n'); .join('\n');
console.log(`${DrawerMenu.name}.exportPlans`, {length: sets.length}); console.log(`${DrawerMenu.name}.exportPlans`, {length: sets.length});
await write('plans.csv', data); await write('plans.csv', data);
}, [db]); }, [db, write]);
const download = useCallback(async () => { const download = useCallback(async () => {
setShowMenu(false); setShowMenu(false);
@ -57,8 +58,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const file = await FileSystem.readFile(result.uri); const file = await FileSystem.readFile(result.uri);
console.log(`${DrawerMenu.name}.${uploadSets.name}:`, file.length); console.log(`${DrawerMenu.name}.${uploadSets.name}:`, file.length);
const lines = file.split('\n'); const lines = file.split('\n');
if (lines[0] != setFields) if (lines[0] != setFields) return toast('Invalid csv.', 3000);
return ToastAndroid.show('Invalid csv.', ToastAndroid.SHORT);
const values = lines const values = lines
.slice(1) .slice(1)
.filter(line => line) .filter(line => line)
@ -70,17 +70,16 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
await db.executeSql( await db.executeSql(
`INSERT INTO sets(name,reps,weight,created,unit) VALUES ${values}`, `INSERT INTO sets(name,reps,weight,created,unit) VALUES ${values}`,
); );
ToastAndroid.show('Data imported.', ToastAndroid.SHORT); toast('Data imported.', 3000);
reset({index: 0, routes: [{name}]}); reset({index: 0, routes: [{name}]});
}, [db, reset, name]); }, [db, reset, name, toast]);
const uploadPlans = useCallback(async () => { const uploadPlans = useCallback(async () => {
const result = await DocumentPicker.pickSingle(); const result = await DocumentPicker.pickSingle();
const file = await FileSystem.readFile(result.uri); const file = await FileSystem.readFile(result.uri);
console.log(`${DrawerMenu.name}.uploadPlans:`, file.length); console.log(`${DrawerMenu.name}.uploadPlans:`, file.length);
const lines = file.split('\n'); const lines = file.split('\n');
if (lines[0] != planFields) if (lines[0] != planFields) return toast('Invalid csv.', 3000);
return ToastAndroid.show('Invalid csv.', ToastAndroid.SHORT);
const values = file const values = file
.split('\n') .split('\n')
.slice(1) .slice(1)
@ -91,8 +90,8 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
}) })
.join(','); .join(',');
await db.executeSql(`INSERT INTO plans(days,workouts) VALUES ${values}`); await db.executeSql(`INSERT INTO plans(days,workouts) VALUES ${values}`);
ToastAndroid.show('Data imported.', ToastAndroid.SHORT); toast('Data imported.', 3000);
}, [db]); }, [db, toast]);
const upload = useCallback(async () => { const upload = useCallback(async () => {
setShowMenu(false); setShowMenu(false);
@ -106,9 +105,9 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
setShowRemove(false); setShowRemove(false);
if (name === 'Home') await db.executeSql(`DELETE FROM sets`); if (name === 'Home') await db.executeSql(`DELETE FROM sets`);
else if (name === 'Plans') await db.executeSql(`DELETE FROM plans`); else if (name === 'Plans') await db.executeSql(`DELETE FROM plans`);
ToastAndroid.show('All data has been deleted.', ToastAndroid.SHORT); toast('All data has been deleted.', 4000);
reset({index: 0, routes: [{name}]}); reset({index: 0, routes: [{name}]});
}, [db, reset, name]); }, [db, reset, name, toast]);
if (name === 'Home' || name === 'Plans') if (name === 'Home' || name === 'Plans')
return ( return (

View File

@ -5,9 +5,9 @@ import {
useRoute, useRoute,
} from '@react-navigation/native'; } from '@react-navigation/native';
import React, {useCallback, useContext} from 'react'; import React, {useCallback, useContext} from 'react';
import {NativeModules, ToastAndroid, View} from 'react-native'; import {NativeModules, View} from 'react-native';
import {IconButton} from 'react-native-paper'; import {IconButton} from 'react-native-paper';
import {DatabaseContext} from './App'; import {DatabaseContext, SnackbarContext} from './App';
import {HomePageParams} from './HomePage'; import {HomePageParams} from './HomePage';
import Set from './set'; import Set from './set';
import SetForm from './SetForm'; import SetForm from './SetForm';
@ -17,6 +17,7 @@ export default function EditSet() {
const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>(); const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>();
const db = useContext(DatabaseContext); const db = useContext(DatabaseContext);
const navigation = useNavigation(); const navigation = useNavigation();
const {toast} = useContext(SnackbarContext);
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@ -24,6 +25,7 @@ export default function EditSet() {
headerLeft: () => ( headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} /> <IconButton icon="arrow-back" onPress={() => navigation.goBack()} />
), ),
headerRight: null,
title: 'Set', title: 'Set',
}); });
}, [navigation]), }, [navigation]),
@ -62,10 +64,10 @@ export default function EditSet() {
weight > params.set.weight || weight > params.set.weight ||
(reps > params.set.reps && weight === params.set.weight) (reps > params.set.reps && weight === params.set.weight)
) )
ToastAndroid.show("Great work King, that's a new record!", 6000); toast("Great work King, that's a new record!", 6000);
navigation.goBack(); navigation.goBack();
}, },
[db, navigation, startTimer, params.set], [db, navigation, startTimer, params.set, toast],
); );
const save = useCallback( const save = useCallback(

View File

@ -7,6 +7,7 @@ import React, {useCallback, useContext, useEffect, useState} from 'react';
import {FlatList, StyleSheet, View} from 'react-native'; import {FlatList, StyleSheet, View} from 'react-native';
import {List, Searchbar} from 'react-native-paper'; import {List, Searchbar} from 'react-native-paper';
import {DatabaseContext} from './App'; import {DatabaseContext} from './App';
import DrawerMenu from './DrawerMenu';
import {HomePageParams} from './HomePage'; import {HomePageParams} from './HomePage';
import MassiveFab from './MassiveFab'; import MassiveFab from './MassiveFab';
import {Plan} from './plan'; import {Plan} from './plan';
@ -132,7 +133,10 @@ export default function SetList() {
useCallback(() => { useCallback(() => {
refresh(); refresh();
predict(); predict();
}, [refresh, predict]), navigation.getParent()?.setOptions({
headerRight: () => <DrawerMenu name="Home" />,
});
}, [refresh, predict, navigation]),
); );
const renderItem = useCallback( const renderItem = useCallback(

View File

@ -5,15 +5,9 @@ import React, {
useEffect, useEffect,
useState, useState,
} from 'react'; } from 'react';
import { import {NativeModules, StyleSheet, Text, View} from 'react-native';
NativeModules,
StyleSheet,
Text,
ToastAndroid,
View,
} from 'react-native';
import {Searchbar, TextInput} from 'react-native-paper'; import {Searchbar, TextInput} from 'react-native-paper';
import {DatabaseContext} from './App'; import {DatabaseContext, SnackbarContext} from './App';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import MassiveSwitch from './MassiveSwitch'; import MassiveSwitch from './MassiveSwitch';
import Settings from './settings'; import Settings from './settings';
@ -29,6 +23,7 @@ export default function SettingsPage() {
const [ignoring, setIgnoring] = useState(false); const [ignoring, setIgnoring] = useState(false);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const db = useContext(DatabaseContext); const db = useContext(DatabaseContext);
const {toast} = useContext(SnackbarContext);
const refresh = useCallback(async () => { const refresh = useCallback(async () => {
const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`); const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`);
@ -65,12 +60,9 @@ export default function SettingsPage() {
const changePredictive = useCallback( const changePredictive = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
setPredictive(enabled); setPredictive(enabled);
ToastAndroid.show( toast('Predictive sets guess whats next based on todays plan.', 7000);
'Predictive sets guess whats next based on todays plan.',
ToastAndroid.LONG,
);
}, },
[setPredictive], [setPredictive, toast],
); );
const changeVibrate = useCallback( const changeVibrate = useCallback(

36
file.ts
View File

@ -1,18 +1,26 @@
import {PermissionsAndroid, ToastAndroid} from 'react-native'; import {useContext} from 'react';
import {PermissionsAndroid} from 'react-native';
import {Dirs, FileSystem} from 'react-native-file-access'; import {Dirs, FileSystem} from 'react-native-file-access';
import {SnackbarContext} from './App';
export const write = async (name: string, data: string) => { export const useWrite = () => {
const filePath = `${Dirs.DocumentDir}/${name}`; const {toast} = useContext(SnackbarContext);
const permission = async () => {
const granted = await PermissionsAndroid.request( return {
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, write: async (name: string, data: string) => {
); const filePath = `${Dirs.DocumentDir}/${name}`;
return granted === PermissionsAndroid.RESULTS.GRANTED; const permission = async () => {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
};
const granted = await permission();
if (!granted) return;
await FileSystem.writeFile(filePath, data);
if (!FileSystem.exists(filePath)) return;
await FileSystem.cpExternal(filePath, name, 'downloads');
toast(`Saved "${name}" in your downloads folder.`, 6000);
},
}; };
const granted = await permission();
if (!granted) return;
await FileSystem.writeFile(filePath, data);
if (!FileSystem.exists(filePath)) return;
await FileSystem.cpExternal(filePath, name, 'downloads');
ToastAndroid.show(`Saved "${name}". Check downloads`, ToastAndroid.LONG);
}; };