Replace settings context with theme context

The settings context was having a big performance
impact on the app. We only truly need the theme + color
to be a global context.
This commit is contained in:
Brandon Presley 2022-11-01 16:50:03 +13:00
parent 8d7fe149f5
commit 31f1528c35
15 changed files with 140 additions and 118 deletions

View File

@ -14,6 +14,7 @@ module.exports = {
curly: 'off',
'react/react-in-jsx-scope': 'off',
'react-native/no-inline-styles': 'off',
'no-spaced-func': 'off',
},
},
],

51
App.tsx
View File

@ -16,9 +16,8 @@ import {lightColors} from './colors'
import {AppDataSource} from './data-source'
import {settingsRepo} from './db'
import Routes from './Routes'
import Settings from './settings'
import {TOAST} from './toast'
import {defaultSettings, SettingsContext} from './use-settings'
import {ThemeContext} from './use-theme'
export const CombinedDefaultTheme = {
...NavigationDefaultTheme,
@ -44,19 +43,20 @@ const App = () => {
const isDark = useColorScheme() === 'dark'
const [initialized, setInitialized] = useState(false)
const [snackbar, setSnackbar] = useState('')
const [theme, setTheme] = useState('system')
const [settings, setSettings] = useState<Settings>({
...defaultSettings,
color: isDark
const [color, setColor] = useState<string>(
isDark
? CombinedDarkTheme.colors.primary
: CombinedDefaultTheme.colors.primary,
})
)
useEffect(() => {
AppDataSource.initialize().then(async () => {
const gotSettings = await settingsRepo.findOne({where: {}})
console.log(`${App.name}.useEffect:`, {gotSettings})
setSettings(gotSettings)
const settings = await settingsRepo.findOne({where: {}})
console.log(`${App.name}.useEffect:`, {gotSettings: settings})
setTheme(settings.theme)
setColor(settings.color)
setInitialized(true)
})
DeviceEventEmitter.addListener(TOAST, ({value}: {value: string}) => {
@ -65,48 +65,43 @@ const App = () => {
})
}, [])
const theme = useMemo(() => {
const darkTheme = settings?.color
const paperTheme = useMemo(() => {
const darkTheme = color
? {
...CombinedDarkTheme,
colors: {...CombinedDarkTheme.colors, primary: settings.color},
colors: {...CombinedDarkTheme.colors, primary: color},
}
: CombinedDarkTheme
const lightTheme = settings?.color
const lightTheme = color
? {
...CombinedDefaultTheme,
colors: {...CombinedDefaultTheme.colors, primary: settings.color},
colors: {...CombinedDefaultTheme.colors, primary: color},
}
: CombinedDefaultTheme
let value = isDark ? darkTheme : lightTheme
if (settings?.theme === 'dark') value = darkTheme
else if (settings?.theme === 'light') value = lightTheme
if (theme === 'dark') value = darkTheme
else if (theme === 'light') value = lightTheme
return value
}, [isDark, settings?.theme, settings?.color])
const settingsContext = useMemo(
() => ({settings, setSettings}),
[settings, setSettings],
)
}, [isDark, theme, color])
const action = useMemo(
() => ({
label: 'Close',
onPress: () => setSnackbar(''),
color: theme.colors.primary,
color: paperTheme.colors.primary,
}),
[theme.colors.primary],
[paperTheme.colors.primary],
)
return (
<PaperProvider
theme={theme}
theme={paperTheme}
settings={{icon: props => <MaterialIcon {...props} />}}>
<NavigationContainer theme={theme}>
<NavigationContainer theme={paperTheme}>
{initialized && (
<SettingsContext.Provider value={settingsContext}>
<ThemeContext.Provider value={{theme, setTheme, color, setColor}}>
<Routes />
</SettingsContext.Provider>
</ThemeContext.Provider>
)}
</NavigationContainer>

View File

@ -7,17 +7,23 @@ import {useCallback, useState} from 'react'
import {FlatList, Image} from 'react-native'
import {List} from 'react-native-paper'
import {BestPageParams} from './BestPage'
import {setRepo} from './db'
import {setRepo, settingsRepo} from './db'
import DrawerHeader from './DrawerHeader'
import GymSet from './gym-set'
import Page from './Page'
import {useSettings} from './use-settings'
import Settings from './settings'
export default function BestList() {
const [bests, setBests] = useState<GymSet[]>()
const [term, setTerm] = useState('')
const navigation = useNavigation<NavigationProp<BestPageParams>>()
const {settings} = useSettings()
const [settings, setSettings] = useState<Settings>()
useFocusEffect(
useCallback(() => {
settingsRepo.findOne({where: {}}).then(setSettings)
}, []),
)
const refresh = useCallback(async (value: string) => {
const weights = await setRepo

View File

@ -1,20 +1,31 @@
import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'
import {useCallback} from 'react'
import {
RouteProp,
useFocusEffect,
useNavigation,
useRoute,
} from '@react-navigation/native'
import {useCallback, useState} from 'react'
import {NativeModules, View} from 'react-native'
import {PADDING} from './constants'
import {setRepo} from './db'
import {setRepo, settingsRepo} from './db'
import GymSet from './gym-set'
import {HomePageParams} from './home-page-params'
import SetForm from './SetForm'
import Settings from './settings'
import StackHeader from './StackHeader'
import {toast} from './toast'
import {useSettings} from './use-settings'
export default function EditSet() {
const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>()
const {set} = params
const navigation = useNavigation()
const {settings} = useSettings()
const [settings, setSettings] = useState<Settings>()
useFocusEffect(
useCallback(() => {
settingsRepo.findOne({where: {}}).then(setSettings)
}, []),
)
const startTimer = useCallback(
async (name: string) => {
@ -23,9 +34,9 @@ export default function EditSet() {
const milliseconds = (minutes ?? 3) * 60 * 1000 + (seconds ?? 0) * 1000
NativeModules.AlarmModule.timer(
milliseconds,
!!settings.vibrate,
settings.vibrate,
settings.sound,
!!settings.noSound,
settings.noSound,
)
},
[settings],
@ -60,7 +71,7 @@ export default function EditSet() {
<>
<StackHeader title="Edit set" />
<View style={{padding: PADDING, flex: 1}}>
<SetForm save={save} set={set} />
{settings && <SetForm settings={settings} save={save} set={set} />}
</View>
</>
)

View File

@ -1,15 +1,20 @@
import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'
import {
RouteProp,
useFocusEffect,
useNavigation,
useRoute,
} from '@react-navigation/native'
import {useCallback, useRef, useState} from 'react'
import {ScrollView, TextInput, View} from 'react-native'
import DocumentPicker from 'react-native-document-picker'
import {Button, Card, TouchableRipple} from 'react-native-paper'
import ConfirmDialog from './ConfirmDialog'
import {MARGIN, PADDING} from './constants'
import {getNow, planRepo, setRepo} from './db'
import {getNow, planRepo, setRepo, settingsRepo} from './db'
import MassiveInput from './MassiveInput'
import Settings from './settings'
import StackHeader from './StackHeader'
import {toast} from './toast'
import {useSettings} from './use-settings'
import {WorkoutsPageParams} from './WorkoutsPage'
export default function EditWorkout() {
@ -31,7 +36,13 @@ export default function EditWorkout() {
const stepsRef = useRef<TextInput>(null)
const minutesRef = useRef<TextInput>(null)
const secondsRef = useRef<TextInput>(null)
const {settings} = useSettings()
const [settings, setSettings] = useState<Settings>()
useFocusEffect(
useCallback(() => {
settingsRepo.findOne({where: {}}).then(setSettings)
}, []),
)
const update = async () => {
await setRepo.update(
@ -119,7 +130,7 @@ export default function EditWorkout() {
onChangeText={handleName}
onSubmitEditing={submitName}
/>
{!!settings.steps && (
{settings.steps && (
<MassiveInput
innerRef={stepsRef}
selectTextOnFocus={false}
@ -130,7 +141,7 @@ export default function EditWorkout() {
onSubmitEditing={() => setsRef.current?.focus()}
/>
)}
{!!settings.showSets && (
{settings.showSets && (
<MassiveInput
innerRef={setsRef}
value={sets}
@ -140,7 +151,7 @@ export default function EditWorkout() {
onSubmitEditing={() => minutesRef.current?.focus()}
/>
)}
{!!settings.alarm && (
{settings.alarm && (
<>
<MassiveInput
innerRef={minutesRef}
@ -160,7 +171,7 @@ export default function EditWorkout() {
/>
</>
)}
{!!settings.images && uri && (
{settings.images && uri && (
<TouchableRipple
style={{marginBottom: MARGIN}}
onPress={changeImage}
@ -168,7 +179,7 @@ export default function EditWorkout() {
<Card.Cover source={{uri}} />
</TouchableRipple>
)}
{!!settings.images && !uri && (
{settings.images && !uri && (
<Button
style={{marginBottom: MARGIN}}
onPress={changeImage}

View File

@ -7,15 +7,17 @@ import {MARGIN} from './constants'
import {getNow, setRepo} from './db'
import GymSet from './gym-set'
import MassiveInput from './MassiveInput'
import Settings from './settings'
import {toast} from './toast'
import {useSettings} from './use-settings'
export default function SetForm({
save,
set,
settings,
}: {
set: GymSet
save: (set: GymSet) => void
settings: Settings
}) {
const [name, setName] = useState(set.name)
const [reps, setReps] = useState(set.reps.toString())
@ -28,7 +30,6 @@ export default function SetForm({
end: set.reps.toString().length,
})
const [removeImage, setRemoveImage] = useState(false)
const {settings} = useSettings()
const weightRef = useRef<TextInput>(null)
const repsRef = useRef<TextInput>(null)
const unitRef = useRef<TextInput>(null)
@ -113,7 +114,7 @@ export default function SetForm({
onSubmitEditing={handleSubmit}
innerRef={weightRef}
/>
{!!settings.showUnit && (
{settings.showUnit && (
<MassiveInput
autoCapitalize="none"
label="Unit"
@ -122,10 +123,10 @@ export default function SetForm({
innerRef={unitRef}
/>
)}
{typeof set.id === 'number' && !!settings.showDate && (
{typeof set.id === 'number' && settings.showDate && (
<MassiveInput label="Created" disabled value={set.created} />
)}
{!!settings.images && newImage && (
{settings.images && newImage && (
<TouchableRipple
style={{marginBottom: MARGIN}}
onPress={changeImage}
@ -133,7 +134,7 @@ export default function SetForm({
<Card.Cover source={{uri: newImage}} />
</TouchableRipple>
)}
{!!settings.images && !newImage && (
{settings.images && !newImage && (
<Button
style={{marginBottom: MARGIN}}
onPress={changeImage}

View File

@ -5,20 +5,21 @@ import {Divider, List, Menu, Text} from 'react-native-paper'
import {setRepo} from './db'
import GymSet from './gym-set'
import {HomePageParams} from './home-page-params'
import Settings from './settings'
import {format} from './time'
import useDark from './use-dark'
import {useSettings} from './use-settings'
export default function SetItem({
item,
onRemove,
settings,
}: {
item: GymSet
onRemove: () => void
settings: Settings
}) {
const [showMenu, setShowMenu] = useState(false)
const [anchor, setAnchor] = useState({x: 0, y: 0})
const {settings} = useSettings()
const dark = useDark()
const navigation = useNavigation<NavigationProp<HomePageParams>>()
@ -51,14 +52,14 @@ export default function SetItem({
description={`${item.reps} x ${item.weight}${item.unit || 'kg'}`}
onLongPress={longPress}
left={() =>
!!settings.images &&
settings.images &&
item.image && (
<Image source={{uri: item.image}} style={{height: 75, width: 75}} />
)
}
right={() => (
<>
{!!settings.showDate && (
{settings.showDate && (
<Text
style={{
alignSelf: 'center',

View File

@ -7,12 +7,13 @@ import React, {useCallback, useEffect, useState} from 'react'
import {FlatList} from 'react-native'
import {List} from 'react-native-paper'
import {Like} from 'typeorm'
import {getNow, setRepo} from './db'
import {getNow, setRepo, settingsRepo} from './db'
import DrawerHeader from './DrawerHeader'
import GymSet from './gym-set'
import {HomePageParams} from './home-page-params'
import Page from './Page'
import SetItem from './SetItem'
import Settings from './settings'
const limit = 15
@ -22,6 +23,7 @@ export default function SetList() {
const [offset, setOffset] = useState(0)
const [term, setTerm] = useState('')
const [end, setEnd] = useState(false)
const [settings, setSettings] = useState<Settings>()
const navigation = useNavigation<NavigationProp<HomePageParams>>()
useEffect(() => console.log({sets}), [sets])
@ -43,6 +45,7 @@ export default function SetList() {
useFocusEffect(
useCallback(() => {
refresh(term)
settingsRepo.findOne({where: {}}).then(setSettings)
}, [refresh, term]),
)
@ -107,12 +110,14 @@ export default function SetList() {
description="A set is a group of repetitions. E.g. 8 reps of Squats."
/>
) : (
<FlatList
data={sets}
style={{flex: 1}}
renderItem={renderItem}
onEndReached={next}
/>
settings && (
<FlatList
data={sets}
style={{flex: 1}}
renderItem={renderItem}
onEndReached={next}
/>
)
)}
</Page>
</>

View File

@ -15,13 +15,12 @@ import Select from './Select'
import Settings from './settings'
import Switch from './Switch'
import {toast} from './toast'
import {useSettings} from './use-settings'
export default function SettingsPage() {
const [battery, setBattery] = useState(false)
const [ignoring, setIgnoring] = useState(false)
const [term, setTerm] = useState('')
const {settings, setSettings} = useSettings()
const [settings, setSettings] = useState<Settings>({} as Settings)
useEffect(() => {
console.log(`${SettingsPage.name}.useEffect:`, {settings})
@ -30,6 +29,7 @@ export default function SettingsPage() {
useFocusEffect(
useCallback(() => {
NativeModules.AlarmModule.ignoringBattery(setIgnoring)
settingsRepo.findOne({where: {}}).then(setSettings)
}, []),
)

View File

@ -7,15 +7,15 @@ import {getBestSet} from './best.service'
import {PADDING} from './constants'
import CountMany from './count-many'
import {AppDataSource} from './data-source'
import {getNow, setRepo} from './db'
import {getNow, setRepo, settingsRepo} from './db'
import GymSet from './gym-set'
import MassiveInput from './MassiveInput'
import {PlanPageParams} from './plan-page-params'
import SetForm from './SetForm'
import Settings from './settings'
import StackHeader from './StackHeader'
import StartPlanItem from './StartPlanItem'
import {toast} from './toast'
import {useSettings} from './use-settings'
export default function StartPlan() {
const {params} = useRoute<RouteProp<PlanPageParams, 'StartPlan'>>()
@ -27,7 +27,7 @@ export default function StartPlan() {
const [seconds, setSeconds] = useState(30)
const [best, setBest] = useState<GymSet>()
const [selected, setSelected] = useState(0)
const {settings} = useSettings()
const [settings, setSettings] = useState<Settings>()
const [counts, setCounts] = useState<CountMany[]>()
const weightRef = useRef<TextInput>(null)
const repsRef = useRef<TextInput>(null)
@ -85,6 +85,7 @@ export default function StartPlan() {
useFocusEffect(
useCallback(() => {
refresh().then(newCounts => select(0, newCounts))
settingsRepo.findOne({where: {}}).then(setSettings)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refresh]),
)
@ -148,7 +149,7 @@ export default function StartPlan() {
innerRef={weightRef}
blurOnSubmit
/>
{!!settings.showUnit && (
{settings?.showUnit && (
<MassiveInput
autoCapitalize="none"
label="Unit"

View File

@ -5,20 +5,20 @@ import {List, Menu, Text} from 'react-native-paper'
import ConfirmDialog from './ConfirmDialog'
import {setRepo} from './db'
import GymSet from './gym-set'
import {useSettings} from './use-settings'
import {WorkoutsPageParams} from './WorkoutsPage'
export default function WorkoutItem({
item,
onRemove,
images,
}: {
item: GymSet
onRemove: () => void
images: boolean
}) {
const [showMenu, setShowMenu] = useState(false)
const [anchor, setAnchor] = useState({x: 0, y: 0})
const [showRemove, setShowRemove] = useState('')
const {settings} = useSettings()
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>()
const remove = useCallback(async () => {
@ -37,12 +37,8 @@ export default function WorkoutItem({
const description = useMemo(() => {
const seconds = item.seconds?.toString().padStart(2, '0')
if (settings.alarm && settings.showSets)
return `${item.sets} x ${item.minutes || 0}:${seconds}`
else if (settings.alarm && !settings.showSets)
return `${item.minutes || 0}:${seconds}`
return `${item.sets}`
}, [item, settings])
return `${item.sets} x ${item.minutes || 0}:${seconds}`
}, [item])
return (
<>
@ -52,7 +48,7 @@ export default function WorkoutItem({
description={description}
onLongPress={longPress}
left={() =>
!!settings.images &&
images &&
item.image && (
<Image source={{uri: item.image}} style={{height: 75, width: 75}} />
)

View File

@ -12,7 +12,8 @@ import GymSet from './gym-set'
import SetList from './SetList'
import WorkoutItem from './WorkoutItem'
import {WorkoutsPageParams} from './WorkoutsPage'
import {setRepo} from './db'
import {setRepo, settingsRepo} from './db'
import Settings from './settings'
const limit = 15
@ -21,6 +22,7 @@ export default function WorkoutList() {
const [offset, setOffset] = useState(0)
const [term, setTerm] = useState('')
const [end, setEnd] = useState(false)
const [settings, setSettings] = useState<Settings>()
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>()
const refresh = useCallback(async (value: string) => {
@ -41,14 +43,20 @@ export default function WorkoutList() {
useFocusEffect(
useCallback(() => {
refresh(term)
settingsRepo.findOne({where: {}}).then(setSettings)
}, [refresh, term]),
)
const renderItem = useCallback(
({item}: {item: GymSet}) => (
<WorkoutItem item={item} key={item.name} onRemove={() => refresh(term)} />
<WorkoutItem
images={settings?.images}
item={item}
key={item.name}
onRemove={() => refresh(term)}
/>
),
[refresh, term],
[refresh, term, settings?.images],
)
const next = useCallback(async () => {

View File

@ -1,11 +1,11 @@
import {useColorScheme} from 'react-native'
import {useSettings} from './use-settings'
import {useTheme} from './use-theme'
export default function useDark() {
const dark = useColorScheme() === 'dark'
const {settings} = useSettings()
const {theme} = useTheme()
if (settings.theme === 'dark') return true
if (settings.theme === 'light') return false
if (theme === 'dark') return true
if (theme === 'light') return false
return dark
}

View File

@ -1,31 +0,0 @@
import React, {useContext} from 'react'
import {darkColors} from './colors'
import Settings from './settings'
export const defaultSettings: Settings = {
alarm: true,
color: darkColors[0].hex,
date: '',
images: true,
notify: false,
showDate: false,
showSets: true,
showUnit: true,
sound: '',
steps: false,
theme: 'system',
vibrate: true,
noSound: false,
}
export const SettingsContext = React.createContext<{
settings: Settings
setSettings: (value: Settings) => void
}>({
settings: defaultSettings,
setSettings: () => null,
})
export function useSettings() {
return useContext(SettingsContext)
}

17
use-theme.ts Normal file
View File

@ -0,0 +1,17 @@
import {createContext, useContext} from 'react'
export const ThemeContext = createContext<{
theme: string
color: string
setTheme: (value: string) => void
setColor: (value: string) => void
}>({
theme: '',
color: '',
setTheme: () => null,
setColor: () => null,
})
export function useTheme() {
return useContext(ThemeContext)
}