From 2c9242b03ff3b4d5c515f3482f5136044657a402 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Thu, 25 Aug 2022 13:01:01 +1200 Subject: [PATCH] Use react-native-paper snackbar instead of ToastAndroid --- App.tsx | 26 +++++++++++++++++++++++++- DrawerMenu.tsx | 29 ++++++++++++++--------------- EditSet.tsx | 10 ++++++---- SetList.tsx | 6 +++++- SettingsPage.tsx | 18 +++++------------- file.ts | 36 ++++++++++++++++++++++-------------- 6 files changed, 77 insertions(+), 48 deletions(-) diff --git a/App.tsx b/App.tsx index 024714c..6714496 100644 --- a/App.tsx +++ b/App.tsx @@ -10,6 +10,7 @@ import { DarkTheme as PaperDarkTheme, DefaultTheme as PaperDefaultTheme, Provider, + Snackbar, } from 'react-native-paper'; import {SQLiteDatabase} from 'react-native-sqlite-storage'; import Ionicon from 'react-native-vector-icons/Ionicons'; @@ -25,6 +26,9 @@ export type DrawerParamList = { }; export const DatabaseContext = React.createContext({} as any); +export const SnackbarContext = React.createContext<{ + toast: (value: string, timeout: number) => void; +}>({toast: () => null}); const CombinedDefaultTheme = { ...PaperDefaultTheme, @@ -45,6 +49,7 @@ const CombinedDarkTheme = { const App = () => { const [db, setDb] = useState(null); + const [snackbar, setSnackbar] = useState(''); const dark = useColorScheme() === 'dark'; useEffect(() => { @@ -64,6 +69,11 @@ const App = () => { init(); }, []); + const toast = (value: string, timeout: number) => { + setSnackbar(value); + setTimeout(() => setSnackbar(''), timeout); + }; + return ( { - + + + + setSnackbar('')} + visible={!!snackbar} + action={{ + label: 'Close', + onPress: () => setSnackbar(''), + color: dark + ? CombinedDarkTheme.colors.primary + : CombinedDefaultTheme.colors.primary, + }}> + {snackbar} + ); }; diff --git a/DrawerMenu.tsx b/DrawerMenu.tsx index c5c6d6b..f821365 100644 --- a/DrawerMenu.tsx +++ b/DrawerMenu.tsx @@ -1,12 +1,11 @@ import {NavigationProp, useNavigation} from '@react-navigation/native'; import React, {useCallback, useContext, useState} from 'react'; -import {ToastAndroid} from 'react-native'; import DocumentPicker from 'react-native-document-picker'; import {FileSystem} from 'react-native-file-access'; import {Divider, IconButton, Menu} from 'react-native-paper'; -import {DatabaseContext, DrawerParamList} from './App'; +import {DatabaseContext, DrawerParamList, SnackbarContext} from './App'; import ConfirmDialog from './ConfirmDialog'; -import {write} from './file'; +import {useWrite} from './file'; import {Plan} from './plan'; import Set from './set'; @@ -17,7 +16,9 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { const [showMenu, setShowMenu] = useState(false); const [showRemove, setShowRemove] = useState(false); const db = useContext(DatabaseContext); + const {toast} = useContext(SnackbarContext); const {reset} = useNavigation>(); + const {write} = useWrite(); const exportSets = useCallback(async () => { const [result] = await db.executeSql('SELECT * FROM sets'); @@ -33,7 +34,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { .join('\n'); console.log(`${DrawerMenu.name}.exportSets`, {length: sets.length}); await write('sets.csv', data); - }, [db]); + }, [db, write]); const exportPlans = useCallback(async () => { const [result] = await db.executeSql('SELECT * FROM plans'); @@ -44,7 +45,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { .join('\n'); console.log(`${DrawerMenu.name}.exportPlans`, {length: sets.length}); await write('plans.csv', data); - }, [db]); + }, [db, write]); const download = useCallback(async () => { setShowMenu(false); @@ -57,8 +58,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { const file = await FileSystem.readFile(result.uri); console.log(`${DrawerMenu.name}.${uploadSets.name}:`, file.length); const lines = file.split('\n'); - if (lines[0] != setFields) - return ToastAndroid.show('Invalid csv.', ToastAndroid.SHORT); + if (lines[0] != setFields) return toast('Invalid csv.', 3000); const values = lines .slice(1) .filter(line => line) @@ -70,17 +70,16 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { await db.executeSql( `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}]}); - }, [db, reset, name]); + }, [db, reset, name, toast]); const uploadPlans = useCallback(async () => { const result = await DocumentPicker.pickSingle(); const file = await FileSystem.readFile(result.uri); console.log(`${DrawerMenu.name}.uploadPlans:`, file.length); const lines = file.split('\n'); - if (lines[0] != planFields) - return ToastAndroid.show('Invalid csv.', ToastAndroid.SHORT); + if (lines[0] != planFields) return toast('Invalid csv.', 3000); const values = file .split('\n') .slice(1) @@ -91,8 +90,8 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { }) .join(','); await db.executeSql(`INSERT INTO plans(days,workouts) VALUES ${values}`); - ToastAndroid.show('Data imported.', ToastAndroid.SHORT); - }, [db]); + toast('Data imported.', 3000); + }, [db, toast]); const upload = useCallback(async () => { setShowMenu(false); @@ -106,9 +105,9 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { setShowRemove(false); if (name === 'Home') await db.executeSql(`DELETE FROM sets`); 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}]}); - }, [db, reset, name]); + }, [db, reset, name, toast]); if (name === 'Home' || name === 'Plans') return ( diff --git a/EditSet.tsx b/EditSet.tsx index a2940c5..cd67b9e 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -5,9 +5,9 @@ import { useRoute, } from '@react-navigation/native'; 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 {DatabaseContext} from './App'; +import {DatabaseContext, SnackbarContext} from './App'; import {HomePageParams} from './HomePage'; import Set from './set'; import SetForm from './SetForm'; @@ -17,6 +17,7 @@ export default function EditSet() { const {params} = useRoute>(); const db = useContext(DatabaseContext); const navigation = useNavigation(); + const {toast} = useContext(SnackbarContext); useFocusEffect( useCallback(() => { @@ -24,6 +25,7 @@ export default function EditSet() { headerLeft: () => ( navigation.goBack()} /> ), + headerRight: null, title: 'Set', }); }, [navigation]), @@ -62,10 +64,10 @@ export default function EditSet() { 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(); }, - [db, navigation, startTimer, params.set], + [db, navigation, startTimer, params.set, toast], ); const save = useCallback( diff --git a/SetList.tsx b/SetList.tsx index 404887f..8fa30e7 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -7,6 +7,7 @@ import React, {useCallback, useContext, useEffect, useState} from 'react'; import {FlatList, StyleSheet, View} from 'react-native'; import {List, Searchbar} from 'react-native-paper'; import {DatabaseContext} from './App'; +import DrawerMenu from './DrawerMenu'; import {HomePageParams} from './HomePage'; import MassiveFab from './MassiveFab'; import {Plan} from './plan'; @@ -132,7 +133,10 @@ export default function SetList() { useCallback(() => { refresh(); predict(); - }, [refresh, predict]), + navigation.getParent()?.setOptions({ + headerRight: () => , + }); + }, [refresh, predict, navigation]), ); const renderItem = useCallback( diff --git a/SettingsPage.tsx b/SettingsPage.tsx index eee0d3b..a0fa51f 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -5,15 +5,9 @@ import React, { useEffect, useState, } from 'react'; -import { - NativeModules, - StyleSheet, - Text, - ToastAndroid, - View, -} from 'react-native'; +import {NativeModules, StyleSheet, Text, View} from 'react-native'; import {Searchbar, TextInput} from 'react-native-paper'; -import {DatabaseContext} from './App'; +import {DatabaseContext, SnackbarContext} from './App'; import ConfirmDialog from './ConfirmDialog'; import MassiveSwitch from './MassiveSwitch'; import Settings from './settings'; @@ -29,6 +23,7 @@ export default function SettingsPage() { const [ignoring, setIgnoring] = useState(false); const [search, setSearch] = useState(''); const db = useContext(DatabaseContext); + const {toast} = useContext(SnackbarContext); const refresh = useCallback(async () => { const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`); @@ -65,12 +60,9 @@ export default function SettingsPage() { const changePredictive = useCallback( (enabled: boolean) => { setPredictive(enabled); - ToastAndroid.show( - 'Predictive sets guess whats next based on todays plan.', - ToastAndroid.LONG, - ); + toast('Predictive sets guess whats next based on todays plan.', 7000); }, - [setPredictive], + [setPredictive, toast], ); const changeVibrate = useCallback( diff --git a/file.ts b/file.ts index 3f288ab..87ae5ec 100644 --- a/file.ts +++ b/file.ts @@ -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 {SnackbarContext} from './App'; -export const write = async (name: string, data: string) => { - const filePath = `${Dirs.DocumentDir}/${name}`; - const permission = async () => { - const granted = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, - ); - return granted === PermissionsAndroid.RESULTS.GRANTED; +export const useWrite = () => { + const {toast} = useContext(SnackbarContext); + + return { + write: async (name: string, data: string) => { + const filePath = `${Dirs.DocumentDir}/${name}`; + 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); };