Refactor MassiveSnack

Instead of using a context for the whole app
use DeviceEventEmitter with root state.
This will probably improve performance,
since I think the react context was
re-rendering the entire DOM tree.
This commit is contained in:
Brandon Presley 2022-11-01 15:55:37 +13:00
parent ace327ecad
commit 49b5eb48c6
10 changed files with 109 additions and 136 deletions

34
App.tsx
View File

@ -4,19 +4,20 @@ import {
NavigationContainer, NavigationContainer,
} from '@react-navigation/native' } from '@react-navigation/native'
import {useEffect, useMemo, useState} from 'react' import {useEffect, useMemo, useState} from 'react'
import {useColorScheme} from 'react-native' import {DeviceEventEmitter, useColorScheme} from 'react-native'
import { import {
DarkTheme as PaperDarkTheme, DarkTheme as PaperDarkTheme,
DefaultTheme as PaperDefaultTheme, DefaultTheme as PaperDefaultTheme,
Provider as PaperProvider, Provider as PaperProvider,
Snackbar,
} from 'react-native-paper' } from 'react-native-paper'
import MaterialIcon from 'react-native-vector-icons/MaterialIcons' import MaterialIcon from 'react-native-vector-icons/MaterialIcons'
import {lightColors} from './colors' import {lightColors} from './colors'
import {AppDataSource} from './data-source' import {AppDataSource} from './data-source'
import {settingsRepo} from './db' import {settingsRepo} from './db'
import MassiveSnack from './MassiveSnack'
import Routes from './Routes' import Routes from './Routes'
import Settings from './settings' import Settings from './settings'
import {TOAST} from './toast'
import {defaultSettings, SettingsContext} from './use-settings' import {defaultSettings, SettingsContext} from './use-settings'
export const CombinedDefaultTheme = { export const CombinedDefaultTheme = {
@ -48,6 +49,7 @@ const App = () => {
? CombinedDarkTheme.colors.primary ? CombinedDarkTheme.colors.primary
: CombinedDefaultTheme.colors.primary, : CombinedDefaultTheme.colors.primary,
}) })
const [snackbar, setSnackbar] = useState('')
useEffect(() => { useEffect(() => {
AppDataSource.initialize().then(async () => { AppDataSource.initialize().then(async () => {
@ -56,6 +58,10 @@ const App = () => {
setSettings(gotSettings) setSettings(gotSettings)
setInitialized(true) setInitialized(true)
}) })
DeviceEventEmitter.addListener(TOAST, ({value}: {value: string}) => {
console.log(`${Routes.name}.toast:`, {value})
setSnackbar(value)
})
}, []) }, [])
const theme = useMemo(() => { const theme = useMemo(() => {
@ -87,14 +93,24 @@ const App = () => {
theme={theme} theme={theme}
settings={{icon: props => <MaterialIcon {...props} />}}> settings={{icon: props => <MaterialIcon {...props} />}}>
<NavigationContainer theme={theme}> <NavigationContainer theme={theme}>
<MassiveSnack> {initialized && (
{initialized && ( <SettingsContext.Provider value={settingsContext}>
<SettingsContext.Provider value={settingsContext}> <Routes />
<Routes /> </SettingsContext.Provider>
</SettingsContext.Provider> )}
)}
</MassiveSnack>
</NavigationContainer> </NavigationContainer>
<Snackbar
duration={3000}
onDismiss={() => setSnackbar('')}
visible={!!snackbar}
action={{
label: 'Close',
onPress: () => setSnackbar(''),
color: theme.colors.primary,
}}>
{snackbar}
</Snackbar>
</PaperProvider> </PaperProvider>
) )
} }

View File

@ -8,8 +8,8 @@ import {AppDataSource} from './data-source'
import {planRepo} from './db' import {planRepo} from './db'
import {DrawerParamList} from './drawer-param-list' import {DrawerParamList} from './drawer-param-list'
import GymSet from './gym-set' import GymSet from './gym-set'
import {useSnackbar} from './MassiveSnack'
import {Plan} from './plan' import {Plan} from './plan'
import {toast} from './toast'
import useDark from './use-dark' import useDark from './use-dark'
import {write} from './write' import {write} from './write'
@ -20,7 +20,6 @@ const setRepo = AppDataSource.manager.getRepository(GymSet)
export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { 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 {toast} = useSnackbar()
const {reset} = useNavigation<NavigationProp<DrawerParamList>>() const {reset} = useNavigation<NavigationProp<DrawerParamList>>()
const dark = useDark() const dark = useDark()
@ -65,7 +64,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
console.log(`${DrawerMenu.name}.uploadSets:`, file.length) console.log(`${DrawerMenu.name}.uploadSets:`, file.length)
const lines = file.split('\n') const lines = file.split('\n')
console.log(lines[0]) console.log(lines[0])
if (!setFields.includes(lines[0])) return toast('Invalid csv.', 3000) if (!setFields.includes(lines[0])) return toast('Invalid csv.')
const values = lines const values = lines
.slice(1) .slice(1)
.filter(line => line) .filter(line => line)
@ -92,21 +91,22 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
sets: +sets, sets: +sets,
minutes: +minutes, minutes: +minutes,
seconds: +seconds, seconds: +seconds,
image: '',
} }
return set return set
}) })
console.log(`${DrawerMenu.name}.uploadSets:`, {values}) console.log(`${DrawerMenu.name}.uploadSets:`, {values})
await setRepo.insert(values) await setRepo.insert(values)
toast('Data imported.', 3000) toast('Data imported.')
reset({index: 0, routes: [{name}]}) reset({index: 0, routes: [{name}]})
}, [reset, name, toast]) }, [reset, name])
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) return toast('Invalid csv.', 3000) if (lines[0] !== planFields) return toast('Invalid csv.')
const values = file const values = file
.split('\n') .split('\n')
.slice(1) .slice(1)
@ -122,8 +122,8 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
return plan return plan
}) })
await planRepo.insert(values) await planRepo.insert(values)
toast('Data imported.', 3000) toast('Data imported.')
}, [toast]) }, [])
const upload = useCallback(async () => { const upload = useCallback(async () => {
setShowMenu(false) setShowMenu(false)
@ -137,9 +137,9 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
setShowRemove(false) setShowRemove(false)
if (name === 'Home') await setRepo.delete({}) if (name === 'Home') await setRepo.delete({})
else if (name === 'Plans') await planRepo.delete({}) else if (name === 'Plans') await planRepo.delete({})
toast('All data has been deleted.', 4000) toast('All data has been deleted.')
reset({index: 0, routes: [{name}]}) reset({index: 0, routes: [{name}]})
}, [reset, name, toast]) }, [reset, name])
if (name === 'Home' || name === 'Plans') if (name === 'Home' || name === 'Plans')
return ( return (

View File

@ -2,19 +2,18 @@ import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'
import {useCallback} from 'react' import {useCallback} from 'react'
import {NativeModules, View} from 'react-native' import {NativeModules, View} from 'react-native'
import {PADDING} from './constants' import {PADDING} from './constants'
import {getNow, setRepo} from './db' import {setRepo} from './db'
import GymSet from './gym-set' import GymSet from './gym-set'
import {HomePageParams} from './home-page-params' import {HomePageParams} from './home-page-params'
import {useSnackbar} from './MassiveSnack'
import SetForm from './SetForm' import SetForm from './SetForm'
import StackHeader from './StackHeader' import StackHeader from './StackHeader'
import {toast} from './toast'
import {useSettings} from './use-settings' import {useSettings} from './use-settings'
export default function EditSet() { export default function EditSet() {
const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>() const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>()
const {set} = params const {set} = params
const navigation = useNavigation() const navigation = useNavigation()
const {toast} = useSnackbar()
const {settings} = useSettings() const {settings} = useSettings()
const startTimer = useCallback( const startTimer = useCallback(
@ -35,9 +34,6 @@ export default function EditSet() {
const add = useCallback( const add = useCallback(
async (value: GymSet) => { async (value: GymSet) => {
startTimer(value.name) startTimer(value.name)
const [{now}] = await getNow()
value.created = now
value.hidden = false
console.log(`${EditSet.name}.add`, {set: value}) console.log(`${EditSet.name}.add`, {set: value})
const result = await setRepo.save(value) const result = await setRepo.save(value)
console.log({result}) console.log({result})
@ -46,9 +42,9 @@ export default function EditSet() {
value.weight > set.weight || value.weight > set.weight ||
(value.reps > set.reps && value.weight === set.weight) (value.reps > set.reps && value.weight === set.weight)
) )
toast("Great work King! That's a new record.", 3000) toast("Great work King! That's a new record.")
}, },
[startTimer, set, toast, settings], [startTimer, set, settings],
) )
const save = useCallback( const save = useCallback(

View File

@ -7,8 +7,8 @@ import ConfirmDialog from './ConfirmDialog'
import {MARGIN, PADDING} from './constants' import {MARGIN, PADDING} from './constants'
import {getNow, planRepo, setRepo} from './db' import {getNow, planRepo, setRepo} from './db'
import MassiveInput from './MassiveInput' import MassiveInput from './MassiveInput'
import {useSnackbar} from './MassiveSnack'
import StackHeader from './StackHeader' import StackHeader from './StackHeader'
import {toast} from './toast'
import {useSettings} from './use-settings' import {useSettings} from './use-settings'
import {WorkoutsPageParams} from './WorkoutsPage' import {WorkoutsPageParams} from './WorkoutsPage'
@ -26,7 +26,6 @@ export default function EditWorkout() {
params.value.seconds?.toString() ?? '30', params.value.seconds?.toString() ?? '30',
) )
const [sets, setSets] = useState(params.value.sets?.toString() ?? '3') const [sets, setSets] = useState(params.value.sets?.toString() ?? '3')
const {toast} = useSnackbar()
const navigation = useNavigation() const navigation = useNavigation()
const setsRef = useRef<TextInput>(null) const setsRef = useRef<TextInput>(null)
const stepsRef = useRef<TextInput>(null) const stepsRef = useRef<TextInput>(null)
@ -94,13 +93,13 @@ export default function EditWorkout() {
const handleName = (value: string) => { const handleName = (value: string) => {
setName(value.replace(/,|'/g, '')) setName(value.replace(/,|'/g, ''))
if (value.match(/,|'/)) if (value.match(/,|'/))
toast('Commas and single quotes would break CSV exports', 6000) toast('Commas and single quotes would break CSV exports')
} }
const handleSteps = (value: string) => { const handleSteps = (value: string) => {
setSteps(value.replace(/,|'/g, '')) setSteps(value.replace(/,|'/g, ''))
if (value.match(/,|'/)) if (value.match(/,|'/))
toast('Commas and single quotes would break CSV exports', 6000) toast('Commas and single quotes would break CSV exports')
} }
const submitName = () => { const submitName = () => {

View File

@ -1,49 +0,0 @@
import {createContext, useContext, useState} from 'react'
import {Snackbar} from 'react-native-paper'
import {CombinedDarkTheme, CombinedDefaultTheme} from './App'
import useDark from './use-dark'
export const SnackbarContext = createContext<{
toast: (value: string, timeout: number) => void
}>({toast: () => null})
export const useSnackbar = () => {
return useContext(SnackbarContext)
}
export default function MassiveSnack({
children,
}: {
children?: JSX.Element[] | JSX.Element
}) {
const [snackbar, setSnackbar] = useState('')
const [timeoutId, setTimeoutId] = useState(0)
const dark = useDark()
const toast = (value: string, timeout: number) => {
setSnackbar(value)
clearTimeout(timeoutId)
const id = setTimeout(() => setSnackbar(''), timeout)
setTimeoutId(id)
}
return (
<>
<SnackbarContext.Provider value={{toast}}>
{children}
</SnackbarContext.Provider>
<Snackbar
onDismiss={() => setSnackbar('')}
visible={!!snackbar}
action={{
label: 'Close',
onPress: () => setSnackbar(''),
color: dark
? CombinedDarkTheme.colors.background
: CombinedDefaultTheme.colors.background,
}}>
{snackbar}
</Snackbar>
</>
)
}

View File

@ -1,6 +1,7 @@
import {createDrawerNavigator} from '@react-navigation/drawer' import {createDrawerNavigator} from '@react-navigation/drawer'
import {useMemo} from 'react' import {useEffect, useMemo, useState} from 'react'
import {IconButton} from 'react-native-paper' import {DeviceEventEmitter} from 'react-native'
import {IconButton, Snackbar, useTheme} from 'react-native-paper'
import BestPage from './BestPage' import BestPage from './BestPage'
import {DrawerParamList} from './drawer-param-list' import {DrawerParamList} from './drawer-param-list'
import HomePage from './HomePage' import HomePage from './HomePage'

View File

@ -4,10 +4,10 @@ import DocumentPicker from 'react-native-document-picker'
import {Button, Card, TouchableRipple} from 'react-native-paper' import {Button, Card, TouchableRipple} from 'react-native-paper'
import ConfirmDialog from './ConfirmDialog' import ConfirmDialog from './ConfirmDialog'
import {MARGIN} from './constants' import {MARGIN} from './constants'
import {setRepo} from './db' import {getNow, setRepo} from './db'
import GymSet from './gym-set' import GymSet from './gym-set'
import MassiveInput from './MassiveInput' import MassiveInput from './MassiveInput'
import {useSnackbar} from './MassiveSnack' import {toast} from './toast'
import {useSettings} from './use-settings' import {useSettings} from './use-settings'
export default function SetForm({ export default function SetForm({
@ -28,7 +28,6 @@ export default function SetForm({
end: set.reps.toString().length, end: set.reps.toString().length,
}) })
const [removeImage, setRemoveImage] = useState(false) const [removeImage, setRemoveImage] = useState(false)
const {toast} = useSnackbar()
const {settings} = useSettings() const {settings} = useSettings()
const weightRef = useRef<TextInput>(null) const weightRef = useRef<TextInput>(null)
const repsRef = useRef<TextInput>(null) const repsRef = useRef<TextInput>(null)
@ -42,8 +41,10 @@ export default function SetForm({
image = await setRepo.findOne({where: {name}}).then(s => s?.image) image = await setRepo.findOne({where: {name}}).then(s => s?.image)
console.log(`${SetForm.name}.handleSubmit:`, {image}) console.log(`${SetForm.name}.handleSubmit:`, {image})
const [{now}] = await getNow()
save({ save({
name, name,
created: now,
reps: Number(reps), reps: Number(reps),
weight: Number(weight), weight: Number(weight),
id: set.id, id: set.id,
@ -59,13 +60,13 @@ export default function SetForm({
const handleName = (value: string) => { const handleName = (value: string) => {
setName(value.replace(/,|'/g, '')) setName(value.replace(/,|'/g, ''))
if (value.match(/,|'/)) if (value.match(/,|'/))
toast('Commas and single quotes would break CSV exports', 6000) toast('Commas and single quotes would break CSV exports')
} }
const handleUnit = (value: string) => { const handleUnit = (value: string) => {
setUnit(value.replace(/,|'/g, '')) setUnit(value.replace(/,|'/g, ''))
if (value.match(/,|'/)) if (value.match(/,|'/))
toast('Commas and single quotes would break CSV exports', 6000) toast('Commas and single quotes would break CSV exports')
} }
const changeImage = useCallback(async () => { const changeImage = useCallback(async () => {

View File

@ -1,7 +1,7 @@
import {Picker} from '@react-native-picker/picker' import {Picker} from '@react-native-picker/picker'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {useCallback, useEffect, useMemo, useState} from 'react' import {useCallback, useEffect, useMemo, useState} from 'react'
import {NativeModules, ScrollView} from 'react-native' import {DeviceEventEmitter, NativeModules, ScrollView} from 'react-native'
import DocumentPicker from 'react-native-document-picker' import DocumentPicker from 'react-native-document-picker'
import {Button} from 'react-native-paper' import {Button} from 'react-native-paper'
import {darkColors, lightColors} from './colors' import {darkColors, lightColors} from './colors'
@ -10,10 +10,10 @@ import {MARGIN} from './constants'
import {settingsRepo} from './db' import {settingsRepo} from './db'
import DrawerHeader from './DrawerHeader' import DrawerHeader from './DrawerHeader'
import Input from './input' import Input from './input'
import {useSnackbar} from './MassiveSnack'
import Page from './Page' import Page from './Page'
import Settings from './settings' import Settings from './settings'
import Switch from './Switch' import Switch from './Switch'
import {toast} from './toast'
import {useSettings} from './use-settings' import {useSettings} from './use-settings'
export default function SettingsPage() { export default function SettingsPage() {
@ -21,7 +21,6 @@ export default function SettingsPage() {
const [ignoring, setIgnoring] = useState(false) const [ignoring, setIgnoring] = useState(false)
const [term, setTerm] = useState('') const [term, setTerm] = useState('')
const {settings, setSettings} = useSettings() const {settings, setSettings} = useSettings()
const {toast} = useSnackbar()
useEffect(() => { useEffect(() => {
console.log(`${SettingsPage.name}.useEffect:`, {settings}) console.log(`${SettingsPage.name}.useEffect:`, {settings})
@ -43,21 +42,25 @@ export default function SettingsPage() {
const changeAlarmEnabled = useCallback( const changeAlarmEnabled = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
if (enabled) toast('Timers will now run after each set.', 4000) if (enabled)
else toast('Stopped timers running after each set.', 4000) DeviceEventEmitter.emit('toast', {
value: 'Timers will now run after each set',
timeout: 4000,
})
else toast('Stopped timers running after each set.')
if (enabled && !ignoring) setBattery(true) if (enabled && !ignoring) setBattery(true)
update(enabled, 'alarm') update(enabled, 'alarm')
}, },
[setBattery, ignoring, toast, update], [setBattery, ignoring, update],
) )
const changeVibrate = useCallback( const changeVibrate = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
if (enabled) toast('When a timer completes, vibrate your phone.', 4000) if (enabled) toast('When a timer completes, vibrate your phone.')
else toast('Stop vibrating at the end of timers.', 4000) else toast('Stop vibrating at the end of timers.')
update(enabled, 'vibrate') update(enabled, 'vibrate')
}, },
[toast, update], [update],
) )
const changeSound = useCallback(async () => { const changeSound = useCallback(async () => {
@ -68,70 +71,70 @@ export default function SettingsPage() {
if (!fileCopyUri) return if (!fileCopyUri) return
settingsRepo.update({}, {sound: fileCopyUri}) settingsRepo.update({}, {sound: fileCopyUri})
setSettings({...settings, sound: fileCopyUri}) setSettings({...settings, sound: fileCopyUri})
toast('This song will now play after rest timers complete.', 4000) toast('This song will now play after rest timers complete.')
}, [toast, setSettings, settings]) }, [setSettings, settings])
const changeNotify = useCallback( const changeNotify = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
update(enabled, 'notify') update(enabled, 'notify')
if (enabled) toast('Show when a set is a new record.', 4000) if (enabled) toast('Show when a set is a new record.')
else toast('Stopped showing notifications for new records.', 4000) else toast('Stopped showing notifications for new records.')
}, },
[toast, update], [update],
) )
const changeImages = useCallback( const changeImages = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
update(enabled, 'images') update(enabled, 'images')
if (enabled) toast('Show images for sets.', 4000) if (enabled) toast('Show images for sets.')
else toast('Stopped showing images for sets.', 4000) else toast('Stopped showing images for sets.')
}, },
[toast, update], [update],
) )
const changeUnit = useCallback( const changeUnit = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
update(enabled, 'showUnit') update(enabled, 'showUnit')
if (enabled) toast('Show option to select unit for sets.', 4000) if (enabled) toast('Show option to select unit for sets.')
else toast('Hid unit option for sets.', 4000) else toast('Hid unit option for sets.')
}, },
[toast, update], [update],
) )
const changeSteps = useCallback( const changeSteps = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
update(enabled, 'steps') update(enabled, 'steps')
if (enabled) toast('Show steps for a workout.', 4000) if (enabled) toast('Show steps for a workout.')
else toast('Stopped showing steps for workouts.', 4000) else toast('Stopped showing steps for workouts.')
}, },
[toast, update], [update],
) )
const changeShowDate = useCallback( const changeShowDate = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
update(enabled, 'showDate') update(enabled, 'showDate')
if (enabled) toast('Show date for sets by default.', 4000) if (enabled) toast('Show date for sets by default.')
else toast('Stopped showing date for sets by default.', 4000) else toast('Stopped showing date for sets by default.')
}, },
[toast, update], [update],
) )
const changeShowSets = useCallback( const changeShowSets = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
update(enabled, 'showSets') update(enabled, 'showSets')
if (enabled) toast('Show target sets for workouts.', 4000) if (enabled) toast('Show target sets for workouts.')
else toast('Stopped showing target sets for workouts.', 4000) else toast('Stopped showing target sets for workouts.')
}, },
[toast, update], [update],
) )
const changeNoSound = useCallback( const changeNoSound = useCallback(
(enabled: boolean) => { (enabled: boolean) => {
update(enabled, 'noSound') update(enabled, 'noSound')
if (enabled) toast('Disable sound on rest timer alarms.', 4000) if (enabled) toast('Disable sound on rest timer alarms.')
else toast('Enabled sound for rest timer alarms.', 4000) else toast('Enabled sound for rest timer alarms.')
}, },
[toast, update], [update],
) )
const switches: Input<boolean>[] = [ const switches: Input<boolean>[] = [
@ -183,10 +186,13 @@ export default function SettingsPage() {
) )
}, [term, settings.color, changeTheme, settings.theme]) }, [term, settings.color, changeTheme, settings.theme])
const changeColor = useCallback((value: string) => { const changeColor = useCallback(
setSettings({...settings, color: value}) (value: string) => {
settingsRepo.update({}, {color: value}) setSettings({...settings, color: value})
}, []) settingsRepo.update({}, {color: value})
},
[setSettings, settings],
)
return ( return (
<> <>

View File

@ -10,11 +10,11 @@ import {AppDataSource} from './data-source'
import {getNow, setRepo} from './db' import {getNow, setRepo} from './db'
import GymSet from './gym-set' import GymSet from './gym-set'
import MassiveInput from './MassiveInput' import MassiveInput from './MassiveInput'
import {useSnackbar} from './MassiveSnack'
import {PlanPageParams} from './plan-page-params' import {PlanPageParams} from './plan-page-params'
import SetForm from './SetForm' import SetForm from './SetForm'
import StackHeader from './StackHeader' import StackHeader from './StackHeader'
import StartPlanItem from './StartPlanItem' import StartPlanItem from './StartPlanItem'
import {toast} from './toast'
import {useSettings} from './use-settings' import {useSettings} from './use-settings'
export default function StartPlan() { export default function StartPlan() {
@ -23,7 +23,6 @@ export default function StartPlan() {
const [reps, setReps] = useState('') const [reps, setReps] = useState('')
const [weight, setWeight] = useState('') const [weight, setWeight] = useState('')
const [unit, setUnit] = useState<string>('kg') const [unit, setUnit] = useState<string>('kg')
const {toast} = useSnackbar()
const [minutes, setMinutes] = useState(3) const [minutes, setMinutes] = useState(3)
const [seconds, setSeconds] = useState(30) const [seconds, setSeconds] = useState(30)
const [best, setBest] = useState<GymSet>() const [best, setBest] = useState<GymSet>()
@ -109,9 +108,9 @@ export default function StartPlan() {
settings.notify && settings.notify &&
(+weight > best.weight || (+reps > best.reps && +weight === best.weight)) (+weight > best.weight || (+reps > best.reps && +weight === best.weight))
) )
toast("Great work King! That's a new record.", 5000) toast("Great work King! That's a new record.")
else if (settings.alarm) toast('Resting...', 3000) else if (settings.alarm) toast('Resting...')
else toast('Added set', 3000) else toast('Added set')
if (!settings.alarm) return if (!settings.alarm) return
const milliseconds = Number(minutes) * 60 * 1000 + Number(seconds) * 1000 const milliseconds = Number(minutes) * 60 * 1000 + Number(seconds) * 1000
const {vibrate, sound, noSound} = settings const {vibrate, sound, noSound} = settings
@ -119,14 +118,11 @@ export default function StartPlan() {
NativeModules.AlarmModule.timer(...args) NativeModules.AlarmModule.timer(...args)
} }
const handleUnit = useCallback( const handleUnit = useCallback((value: string) => {
(value: string) => { setUnit(value.replace(/,|'/g, ''))
setUnit(value.replace(/,|'/g, '')) if (value.match(/,|'/))
if (value.match(/,|'/)) toast('Commas and single quotes would break CSV exports')
toast('Commas and single quotes would break CSV exports', 6000) }, [])
},
[toast],
)
return ( return (
<> <>

7
toast.ts Normal file
View File

@ -0,0 +1,7 @@
import {DeviceEventEmitter} from 'react-native'
export const TOAST = 'toast'
export function toast(value: string) {
DeviceEventEmitter.emit(TOAST, {value})
}