Compare commits

...

3 Commits

Author SHA1 Message Date
Brandon Presley 3ec685a76e Give up on hidden drawer navigation.
Auto focusing is all messed up here and I
don't think all this trouble is worth it.
2022-09-29 13:32:59 +13:00
Brandon Presley 8a74d750c4 Fix back logic 2022-09-29 12:50:56 +13:00
Brandon Presley 5fc4c758b6 Use hidden drawer routes instead of drawer + stack
Closes #68
2022-09-29 12:28:03 +13:00
26 changed files with 482 additions and 579 deletions

View File

@ -1,74 +0,0 @@
import {
NavigationProp,
useFocusEffect,
useNavigation,
} from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react';
import {FlatList, Image} from 'react-native';
import {List} from 'react-native-paper';
import {getBestReps, getBestWeights} from './best.service';
import {BestPageParams} from './BestPage';
import Page from './Page';
import Set from './set';
import {settings} from './settings.service';
export default function BestList() {
const [bests, setBests] = useState<Set[]>([]);
const [search, setSearch] = useState('');
const navigation = useNavigation<NavigationProp<BestPageParams>>();
const refresh = useCallback(async () => {
const weights = await getBestWeights(search);
console.log(`${BestList.name}.refresh:`, {length: weights.length});
let newBest: Set[] = [];
for (const set of weights) {
const reps = await getBestReps(set.name, set.weight);
newBest.push(...reps);
}
setBests(newBest);
}, [search]);
useFocusEffect(
useCallback(() => {
refresh();
navigation.getParent()?.setOptions({
headerRight: () => null,
});
}, [refresh, navigation]),
);
useEffect(() => {
refresh();
}, [search, refresh]);
const renderItem = ({item}: {item: Set}) => (
<List.Item
key={item.name}
title={item.name}
description={`${item.reps} x ${item.weight}${item.unit || 'kg'}`}
onPress={() => navigation.navigate('ViewBest', {best: item})}
left={() =>
(settings.images && item.image && (
<Image source={{uri: item.image}} style={{height: 75, width: 75}} />
)) ||
null
}
/>
);
return (
<Page search={search} setSearch={setSearch}>
<FlatList
style={{height: '99%'}}
ListEmptyComponent={
<List.Item
title="No exercises yet"
description="Once sets have been added, this will highlight your personal bests."
/>
}
renderItem={renderItem}
data={bests}
/>
</Page>
);
}

View File

@ -1,42 +1,74 @@
import {DrawerNavigationProp} from '@react-navigation/drawer'; import {
import {useNavigation} from '@react-navigation/native'; NavigationProp,
import {createStackNavigator} from '@react-navigation/stack'; useFocusEffect,
import React from 'react'; useNavigation,
import {IconButton} from 'react-native-paper'; } from '@react-navigation/native';
import BestList from './BestList'; import {default as React, useCallback, useEffect, useState} from 'react';
import {FlatList, Image} from 'react-native';
import {List} from 'react-native-paper';
import {getBestReps, getBestWeights} from './best.service';
import {DrawerParamList} from './drawer-param-list'; import {DrawerParamList} from './drawer-param-list';
import Page from './Page';
import Set from './set'; import Set from './set';
import ViewBest from './ViewBest'; import {settings} from './settings.service';
const Stack = createStackNavigator<BestPageParams>();
export type BestPageParams = {
BestList: {};
ViewBest: {
best: Set;
};
};
export default function BestPage() { export default function BestPage() {
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>(); const [bests, setBests] = useState<Set[]>([]);
const [search, setSearch] = useState('');
const navigation = useNavigation<NavigationProp<DrawerParamList>>();
const refresh = useCallback(async () => {
const weights = await getBestWeights(search);
console.log(`${BestPage.name}.refresh:`, {length: weights.length});
let newBest: Set[] = [];
for (const set of weights) {
const reps = await getBestReps(set.name, set.weight);
newBest.push(...reps);
}
setBests(newBest);
}, [search]);
useFocusEffect(
useCallback(() => {
refresh();
navigation.getParent()?.setOptions({
headerRight: () => null,
});
}, [refresh, navigation]),
);
useEffect(() => {
refresh();
}, [search, refresh]);
const renderItem = ({item}: {item: Set}) => (
<List.Item
key={item.name}
title={item.name}
description={`${item.reps} x ${item.weight}${item.unit || 'kg'}`}
onPress={() => navigation.navigate('View best', {best: item})}
left={() =>
(settings.images && item.image && (
<Image source={{uri: item.image}} style={{height: 75, width: 75}} />
)) ||
null
}
/>
);
return ( return (
<Stack.Navigator <Page search={search} setSearch={setSearch}>
screenOptions={{headerShown: false, animationEnabled: false}}> <FlatList
<Stack.Screen name="BestList" component={BestList} /> style={{height: '99%'}}
<Stack.Screen ListEmptyComponent={
name="ViewBest" <List.Item
component={ViewBest} title="No exercises yet"
listeners={{ description="Once sets have been added, this will highlight your personal bests."
beforeRemove: () => { />
navigation.setOptions({ }
headerLeft: () => ( renderItem={renderItem}
<IconButton icon="menu" onPress={navigation.openDrawer} /> data={bests}
),
title: 'Best',
});
},
}}
/> />
</Stack.Navigator> </Page>
); );
} }

View File

@ -6,18 +6,17 @@ import {
useRoute, useRoute,
} from '@react-navigation/native'; } from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react'; import React, {useCallback, useEffect, useState} from 'react';
import {ScrollView, StyleSheet, View} from 'react-native'; import {BackHandler, ScrollView, StyleSheet, View} from 'react-native';
import {Button, IconButton, Text} from 'react-native-paper'; import {Button, IconButton, Text} from 'react-native-paper';
import {MARGIN, PADDING} from './constants'; import {MARGIN, PADDING} from './constants';
import {DrawerParamList} from './drawer-param-list'; import {DrawerParamList} from './drawer-param-list';
import {PlanPageParams} from './plan-page-params';
import {addPlan, updatePlan} from './plan.service'; import {addPlan, updatePlan} from './plan.service';
import {getNames} from './set.service'; import {getNames} from './set.service';
import Switch from './Switch'; import Switch from './Switch';
import {DAYS} from './time'; import {DAYS} from './time';
export default function EditPlan() { export default function EditPlan() {
const {params} = useRoute<RouteProp<PlanPageParams, 'EditPlan'>>(); const {params} = useRoute<RouteProp<DrawerParamList, 'Edit plan'>>();
const {plan} = params; const {plan} = params;
const [days, setDays] = useState<string[]>( const [days, setDays] = useState<string[]>(
plan.days ? plan.days.split(',') : [], plan.days ? plan.days.split(',') : [],
@ -31,13 +30,22 @@ export default function EditPlan() {
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
console.log(`${EditPlan.name}.focus:`, {plan}); console.log(`${EditPlan.name}.focus:`, {plan});
navigation.getParent()?.setOptions({ navigation.setOptions({
headerLeft: () => ( headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} /> <IconButton
icon="arrow-back"
onPress={() => navigation.navigate('Plans', {})}
/>
), ),
headerRight: () => null, headerRight: () => null,
title: plan.id ? 'Edit plan' : 'Create plan', title: plan.id ? 'Edit plan' : 'Create plan',
}); });
const onBack = () => {
navigation.navigate('Plans', {});
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBack);
return () => BackHandler.removeEventListener('hardwareBackPress', onBack);
}, [navigation, plan]), }, [navigation, plan]),
); );

View File

@ -1,14 +1,15 @@
import { import {
NavigationProp,
RouteProp, RouteProp,
useFocusEffect, useFocusEffect,
useNavigation, useNavigation,
useRoute, useRoute,
} from '@react-navigation/native'; } from '@react-navigation/native';
import React, {useCallback, useContext} from 'react'; import React, {useCallback, useContext} from 'react';
import {NativeModules, View} from 'react-native'; import {BackHandler, NativeModules, View} from 'react-native';
import {IconButton} from 'react-native-paper'; import {IconButton} from 'react-native-paper';
import {PADDING} from './constants'; import {PADDING} from './constants';
import {HomePageParams} from './home-page-params'; import {DrawerParamList} from './drawer-param-list';
import {SnackbarContext} from './MassiveSnack'; import {SnackbarContext} from './MassiveSnack';
import Set from './set'; import Set from './set';
import {addSet, updateSet} from './set.service'; import {addSet, updateSet} from './set.service';
@ -16,9 +17,9 @@ import SetForm from './SetForm';
import {getSettings, settings, updateSettings} from './settings.service'; import {getSettings, settings, updateSettings} from './settings.service';
export default function EditSet() { export default function EditSet() {
const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>(); const {params} = useRoute<RouteProp<DrawerParamList, 'Edit set'>>();
const {set, count, workouts} = params; const {set, count, workouts} = params;
const navigation = useNavigation(); const navigation = useNavigation<NavigationProp<DrawerParamList>>();
const {toast} = useContext(SnackbarContext); const {toast} = useContext(SnackbarContext);
useFocusEffect( useFocusEffect(
@ -28,13 +29,21 @@ export default function EditSet() {
if (typeof set.id === 'number') title = 'Edit set'; if (typeof set.id === 'number') title = 'Edit set';
else if (Number(set.sets) > 0) else if (Number(set.sets) > 0)
title = `${set.name} (${count + 1} / ${set.sets})`; title = `${set.name} (${count + 1} / ${set.sets})`;
navigation.getParent()?.setOptions({ navigation.setOptions({
headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} />
),
headerRight: null,
title, title,
headerLeft: () => (
<IconButton
icon="arrow-back"
onPress={() => navigation.navigate('Home', {})}
/>
),
}); });
const onBack = () => {
navigation.navigate('Home', {});
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBack);
return () => BackHandler.removeEventListener('hardwareBackPress', onBack);
}, [navigation, set, count]), }, [navigation, set, count]),
); );

View File

@ -1,24 +1,25 @@
import { import {
NavigationProp,
RouteProp, RouteProp,
useFocusEffect, useFocusEffect,
useNavigation, useNavigation,
useRoute, useRoute,
} from '@react-navigation/native'; } from '@react-navigation/native';
import React, {useCallback, useContext, useState} from 'react'; import React, {useCallback, useContext, useState} from 'react';
import {ScrollView, View} from 'react-native'; import {BackHandler, ScrollView, View} from 'react-native';
import DocumentPicker from 'react-native-document-picker'; import DocumentPicker from 'react-native-document-picker';
import {Button, Card, IconButton, TouchableRipple} from 'react-native-paper'; import {Button, Card, IconButton, TouchableRipple} from 'react-native-paper';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import {MARGIN, PADDING} from './constants'; import {MARGIN, PADDING} from './constants';
import {DrawerParamList} from './drawer-param-list';
import MassiveInput from './MassiveInput'; import MassiveInput from './MassiveInput';
import {SnackbarContext} from './MassiveSnack'; import {SnackbarContext} from './MassiveSnack';
import {updatePlanWorkouts} from './plan.service'; import {updatePlanWorkouts} from './plan.service';
import {addSet, updateManySet, updateSetImage} from './set.service'; import {addSet, updateManySet, updateSetImage} from './set.service';
import {settings} from './settings.service'; import {settings} from './settings.service';
import {WorkoutsPageParams} from './WorkoutsPage';
export default function EditWorkout() { export default function EditWorkout() {
const {params} = useRoute<RouteProp<WorkoutsPageParams, 'EditWorkout'>>(); const {params} = useRoute<RouteProp<DrawerParamList, 'Edit workout'>>();
const [removeImage, setRemoveImage] = useState(false); const [removeImage, setRemoveImage] = useState(false);
const [showRemove, setShowRemove] = useState(false); const [showRemove, setShowRemove] = useState(false);
const [name, setName] = useState(params.value.name); const [name, setName] = useState(params.value.name);
@ -32,19 +33,27 @@ export default function EditWorkout() {
); );
const [sets, setSets] = useState(params.value.sets?.toString() ?? '3'); const [sets, setSets] = useState(params.value.sets?.toString() ?? '3');
const {toast} = useContext(SnackbarContext); const {toast} = useContext(SnackbarContext);
const navigation = useNavigation(); const navigation = useNavigation<NavigationProp<DrawerParamList>>();
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
navigation.getParent()?.setOptions({ navigation.setOptions({
headerLeft: () => ( headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} /> <IconButton
icon="arrow-back"
onPress={() => navigation.navigate('Workouts', {})}
/>
), ),
headerRight: null, headerRight: null,
title: params.value.name || 'New workout', title: params.value.name || 'New workout',
}); });
if (!name) return; const onBack = () => {
}, [navigation, name, params.value.name]), navigation.navigate('Workouts', {});
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBack);
return () => BackHandler.removeEventListener('hardwareBackPress', onBack);
}, [navigation, params.value.name]),
); );
const update = async () => { const update = async () => {

View File

@ -1,36 +1,135 @@
import {DrawerNavigationProp} from '@react-navigation/drawer'; import {
import {useNavigation} from '@react-navigation/native'; NavigationProp,
import {createStackNavigator} from '@react-navigation/stack'; useFocusEffect,
import React from 'react'; useNavigation,
import {IconButton} from 'react-native-paper'; } from '@react-navigation/native';
import {default as React, useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import {getBestSet} from './best.service';
import {DrawerParamList} from './drawer-param-list'; import {DrawerParamList} from './drawer-param-list';
import EditSet from './EditSet'; import Page from './Page';
import {HomePageParams} from './home-page-params'; import {getTodaysPlan} from './plan.service';
import SetList from './SetList'; import Set from './set';
import {countToday, defaultSet, getSets, getToday} from './set.service';
import SetItem from './SetItem';
import {settings} from './settings.service';
const Stack = createStackNavigator<HomePageParams>(); const limit = 15;
export default function HomePage() { export default function HomePage() {
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>(); const [sets, setSets] = useState<Set[]>();
const [set, setSet] = useState<Set>();
const [count, setCount] = useState(0);
const [workouts, setWorkouts] = useState<string[]>([]);
const [offset, setOffset] = useState(0);
const [search, setSearch] = useState('');
const [end, setEnd] = useState(false);
const [dates, setDates] = useState(false);
const [images, setImages] = useState(true);
const navigation = useNavigation<NavigationProp<DrawerParamList>>();
const predict = useCallback(async () => {
setCount(0);
setSet({...defaultSet});
if (!settings.predict) return;
const todaysPlan = await getTodaysPlan();
console.log(`${HomePage.name}.predict:`, {todaysPlan});
if (todaysPlan.length === 0) return;
const todaysWorkouts = todaysPlan[0].workouts.split(',');
setWorkouts(todaysWorkouts);
let best = await getBestSet(todaysWorkouts[0]);
const todaysSet = await getToday();
if (!todaysSet || !todaysWorkouts.includes(todaysSet.name))
return setSet({...best});
let _count = await countToday(todaysSet.name);
best = await getBestSet(todaysSet.name);
const index = todaysWorkouts.indexOf(todaysSet.name) + 1;
if (_count >= Number(best.sets)) {
best = await getBestSet(todaysWorkouts[index]);
_count = 0;
}
if (best.name === '') setCount(0);
else setCount(_count);
setSet({...best});
}, []);
const refresh = useCallback(async () => {
predict();
const newSets = await getSets({search: `%${search}%`, limit, offset: 0});
console.log(`${HomePage.name}.refresh:`, {first: newSets[0]});
if (newSets.length === 0) return setSets([]);
setSets(newSets);
setOffset(0);
setEnd(false);
}, [search, predict]);
useFocusEffect(
useCallback(() => {
refresh();
setImages(!!settings.images);
}, [refresh]),
);
useEffect(() => {
refresh();
}, [search, refresh]);
const renderItem = useCallback(
({item}: {item: Set}) => (
<SetItem
dates={dates}
setDates={setDates}
images={images}
setImages={setImages}
item={item}
key={item.id}
onRemove={refresh}
/>
),
[refresh, dates, setDates, images, setImages],
);
const next = useCallback(async () => {
if (end) return;
const newOffset = offset + limit;
console.log(`${HomePage.name}.next:`, {offset, newOffset, search});
const newSets = await getSets({
search: `%${search}%`,
limit,
offset: newOffset,
});
if (newSets.length === 0) return setEnd(true);
if (!sets) return;
setSets([...sets, ...newSets]);
if (newSets.length < limit) return setEnd(true);
setOffset(newOffset);
}, [search, end, offset, sets]);
const onAdd = useCallback(async () => {
console.log(`${HomePage.name}.onAdd`, {set, workouts});
navigation.navigate('Edit set', {
set: set || {...defaultSet},
workouts,
count,
});
}, [navigation, set, workouts, count]);
return ( return (
<Stack.Navigator <Page onAdd={onAdd} search={search} setSearch={setSearch}>
screenOptions={{headerShown: false, animationEnabled: false}}> <FlatList
<Stack.Screen name="Sets" component={SetList} /> data={sets}
<Stack.Screen style={{height: '99%'}}
name="EditSet" ListEmptyComponent={
component={EditSet} <List.Item
listeners={{ title="No sets yet"
beforeRemove: () => { description="A set is a group of repetitions. E.g. 8 reps of Squats."
navigation.setOptions({ />
headerLeft: () => ( }
<IconButton icon="menu" onPress={navigation.openDrawer} /> renderItem={renderItem}
), keyExtractor={s => s.id!.toString()}
title: 'Home', onEndReached={next}
});
},
}}
/> />
</Stack.Navigator> </Page>
); );
} }

View File

@ -2,8 +2,8 @@ import {NavigationProp, useNavigation} from '@react-navigation/native';
import React, {useCallback, useState} from 'react'; import React, {useCallback, useState} from 'react';
import {GestureResponderEvent} from 'react-native'; import {GestureResponderEvent} from 'react-native';
import {List, Menu} from 'react-native-paper'; import {List, Menu} from 'react-native-paper';
import {DrawerParamList} from './drawer-param-list';
import {Plan} from './plan'; import {Plan} from './plan';
import {PlanPageParams} from './plan-page-params';
import {deletePlan} from './plan.service'; import {deletePlan} from './plan.service';
export default function PlanItem({ export default function PlanItem({
@ -15,7 +15,7 @@ export default function PlanItem({
}) { }) {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const [anchor, setAnchor] = useState({x: 0, y: 0}); const [anchor, setAnchor] = useState({x: 0, y: 0});
const navigation = useNavigation<NavigationProp<PlanPageParams>>(); const navigation = useNavigation<NavigationProp<DrawerParamList>>();
const remove = useCallback(async () => { const remove = useCallback(async () => {
if (typeof item.id === 'number') await deletePlan(item.id); if (typeof item.id === 'number') await deletePlan(item.id);
@ -34,7 +34,7 @@ export default function PlanItem({
return ( return (
<> <>
<List.Item <List.Item
onPress={() => navigation.navigate('EditPlan', {plan: item})} onPress={() => navigation.navigate('Edit plan', {plan: item})}
title={ title={
item.days item.days
? item.days.replace(/,/g, ', ') ? item.days.replace(/,/g, ', ')

View File

@ -1,64 +0,0 @@
import {
NavigationProp,
useFocusEffect,
useNavigation,
} from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import DrawerMenu from './DrawerMenu';
import Page from './Page';
import {Plan} from './plan';
import {PlanPageParams} from './plan-page-params';
import {getPlans} from './plan.service';
import PlanItem from './PlanItem';
export default function PlanList() {
const [search, setSearch] = useState('');
const [plans, setPlans] = useState<Plan[]>([]);
const navigation = useNavigation<NavigationProp<PlanPageParams>>();
const refresh = useCallback(async () => {
getPlans(search).then(setPlans);
}, [search]);
useFocusEffect(
useCallback(() => {
refresh();
navigation.getParent()?.setOptions({
headerRight: () => <DrawerMenu name="Plans" />,
});
}, [refresh, navigation]),
);
useEffect(() => {
refresh();
}, [search, refresh]);
const renderItem = useCallback(
({item}: {item: Plan}) => (
<PlanItem item={item} key={item.id} onRemove={refresh} />
),
[refresh],
);
const onAdd = () =>
navigation.navigate('EditPlan', {plan: {days: '', workouts: ''}});
return (
<Page onAdd={onAdd} search={search} setSearch={setSearch}>
<FlatList
style={{height: '99%'}}
data={plans}
renderItem={renderItem}
keyExtractor={set => set.id?.toString() || ''}
ListEmptyComponent={
<List.Item
title="No plans yet"
description="A plan is a list of workouts for certain days."
/>
}
/>
</Page>
);
}

View File

@ -1,36 +1,64 @@
import {DrawerNavigationProp} from '@react-navigation/drawer'; import {
import {useNavigation} from '@react-navigation/native'; NavigationProp,
import {createStackNavigator} from '@react-navigation/stack'; useFocusEffect,
import React from 'react'; useNavigation,
import {IconButton} from 'react-native-paper'; } from '@react-navigation/native';
import {default as React, useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import {DrawerParamList} from './drawer-param-list'; import {DrawerParamList} from './drawer-param-list';
import EditPlan from './EditPlan'; import DrawerMenu from './DrawerMenu';
import {PlanPageParams} from './plan-page-params'; import Page from './Page';
import PlanList from './PlanList'; import {Plan} from './plan';
import {getPlans} from './plan.service';
const Stack = createStackNavigator<PlanPageParams>(); import PlanItem from './PlanItem';
export default function PlanPage() { export default function PlanPage() {
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>(); const [search, setSearch] = useState('');
const [plans, setPlans] = useState<Plan[]>([]);
const navigation = useNavigation<NavigationProp<DrawerParamList>>();
const refresh = useCallback(async () => {
getPlans(search).then(setPlans);
}, [search]);
useFocusEffect(
useCallback(() => {
refresh();
navigation.getParent()?.setOptions({
headerRight: () => <DrawerMenu name="Plans" />,
});
}, [refresh, navigation]),
);
useEffect(() => {
refresh();
}, [search, refresh]);
const renderItem = useCallback(
({item}: {item: Plan}) => (
<PlanItem item={item} key={item.id} onRemove={refresh} />
),
[refresh],
);
const onAdd = () =>
navigation.navigate('Edit plan', {plan: {days: '', workouts: ''}});
return ( return (
<Stack.Navigator <Page onAdd={onAdd} search={search} setSearch={setSearch}>
screenOptions={{headerShown: false, animationEnabled: false}}> <FlatList
<Stack.Screen name="PlanList" component={PlanList} /> style={{height: '99%'}}
<Stack.Screen data={plans}
name="EditPlan" renderItem={renderItem}
component={EditPlan} keyExtractor={set => set.id?.toString() || ''}
listeners={{ ListEmptyComponent={
beforeRemove: () => { <List.Item
navigation.setOptions({ title="No plans yet"
headerLeft: () => ( description="A plan is a list of workouts for certain days."
<IconButton icon="menu" onPress={navigation.openDrawer} /> />
), }
title: 'Plans',
});
},
}}
/> />
</Stack.Navigator> </Page>
); );
} }

View File

@ -6,11 +6,16 @@ import {CustomTheme} from './App';
import BestPage from './BestPage'; import BestPage from './BestPage';
import {runMigrations} from './db'; import {runMigrations} from './db';
import {DrawerParamList} from './drawer-param-list'; import {DrawerParamList} from './drawer-param-list';
import DrawerMenu from './DrawerMenu';
import EditPlan from './EditPlan';
import EditSet from './EditSet';
import EditWorkout from './EditWorkout';
import HomePage from './HomePage'; import HomePage from './HomePage';
import PlanPage from './PlanPage'; import PlanPage from './PlanPage';
import Route from './route'; import Route from './route';
import {getSettings, settings} from './settings.service'; import {getSettings, settings} from './settings.service';
import SettingsPage from './SettingsPage'; import SettingsPage from './SettingsPage';
import ViewBest from './ViewBest';
import WorkoutsPage from './WorkoutsPage'; import WorkoutsPage from './WorkoutsPage';
const Drawer = createDrawerNavigator<DrawerParamList>(); const Drawer = createDrawerNavigator<DrawerParamList>();
@ -39,6 +44,13 @@ export default function Routes() {
{name: 'Settings', component: SettingsPage, icon: 'settings'}, {name: 'Settings', component: SettingsPage, icon: 'settings'},
]; ];
const hiddenRoutes: Route[] = [
{name: 'Edit set', component: EditSet},
{name: 'Edit plan', component: EditPlan},
{name: 'Edit workout', component: EditWorkout},
{name: 'View best', component: ViewBest},
];
return ( return (
<Drawer.Navigator <Drawer.Navigator
screenOptions={{ screenOptions={{
@ -51,7 +63,18 @@ export default function Routes() {
name={route.name} name={route.name}
component={route.component} component={route.component}
options={{ options={{
drawerIcon: () => <IconButton icon={route.icon} />, drawerIcon: () => <IconButton icon={route.icon || ''} />,
headerRight: () => <DrawerMenu name={route.name} />,
}}
/>
))}
{hiddenRoutes.map(route => (
<Drawer.Screen
key={route.name}
name={route.name}
component={route.component}
options={{
drawerItemStyle: {height: 0},
}} }}
/> />
))} ))}

View File

@ -1,4 +1,11 @@
import React, {useContext, useEffect, useRef, useState} from 'react'; import {useFocusEffect} from '@react-navigation/native';
import React, {
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import {ScrollView, View} from 'react-native'; import {ScrollView, View} from 'react-native';
import {Button, Text} from 'react-native-paper'; import {Button, Text} from 'react-native-paper';
import MassiveInput from './MassiveInput'; import MassiveInput from './MassiveInput';
@ -30,13 +37,25 @@ export default function SetForm({
const repsRef = useRef<any>(null); const repsRef = useRef<any>(null);
const unitRef = useRef<any>(null); const unitRef = useRef<any>(null);
useFocusEffect(
useCallback(() => {
repsRef?.current.focus();
}, []),
);
useEffect(() => { useEffect(() => {
console.log('SetForm.useEffect:', {uri, name: set.name}); console.log('SetForm.useEffect:', {uri, set});
if (!uri) setName(set.name);
setReps(set.reps.toString());
setWeight(set.weight.toString());
setUnit(set.unit);
if (!set.image)
getSets({search: set.name, limit: 1, offset: 0}).then(([s]) => getSets({search: set.name, limit: 1, offset: 0}).then(([s]) =>
setUri(s?.image), setUri(s?.image),
); );
}, [uri, set.name]); else setUri(set.image);
repsRef?.current.focus();
}, [uri, set]);
const handleSubmit = () => { const handleSubmit = () => {
if (!name) return; if (!name) return;
@ -73,7 +92,6 @@ export default function SetForm({
value={name} value={name}
onChangeText={handleName} onChangeText={handleName}
autoCorrect={false} autoCorrect={false}
autoFocus={!name}
blurOnSubmit={false} blurOnSubmit={false}
onSubmitEditing={() => repsRef.current?.focus()} onSubmitEditing={() => repsRef.current?.focus()}
/> />
@ -85,7 +103,6 @@ export default function SetForm({
onSubmitEditing={() => weightRef.current?.focus()} onSubmitEditing={() => weightRef.current?.focus()}
selection={selection} selection={selection}
onSelectionChange={e => setSelection(e.nativeEvent.selection)} onSelectionChange={e => setSelection(e.nativeEvent.selection)}
autoFocus={!!name}
blurOnSubmit={false} blurOnSubmit={false}
innerRef={repsRef} innerRef={repsRef}
/> />
@ -109,7 +126,7 @@ export default function SetForm({
{workouts.length > 0 && !!settings.workouts && ( {workouts.length > 0 && !!settings.workouts && (
<View style={{flexDirection: 'row'}}> <View style={{flexDirection: 'row'}}>
{workouts.map((workout, index) => ( {workouts.map((workout, index) => (
<Text> <Text key={workout}>
<Text <Text
style={ style={
workout === name workout === name

View File

@ -2,7 +2,7 @@ import {NavigationProp, useNavigation} from '@react-navigation/native';
import React, {useCallback, useState} from 'react'; import React, {useCallback, useState} from 'react';
import {GestureResponderEvent, Image} from 'react-native'; import {GestureResponderEvent, Image} from 'react-native';
import {Divider, List, Menu, Text} from 'react-native-paper'; import {Divider, List, Menu, Text} from 'react-native-paper';
import {HomePageParams} from './home-page-params'; import {DrawerParamList} from './drawer-param-list';
import Set from './set'; import Set from './set';
import {deleteSet} from './set.service'; import {deleteSet} from './set.service';
@ -23,7 +23,7 @@ export default function SetItem({
}) { }) {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const [anchor, setAnchor] = useState({x: 0, y: 0}); const [anchor, setAnchor] = useState({x: 0, y: 0});
const navigation = useNavigation<NavigationProp<HomePageParams>>(); const navigation = useNavigation<NavigationProp<DrawerParamList>>();
const remove = useCallback(async () => { const remove = useCallback(async () => {
if (typeof item.id === 'number') await deleteSet(item.id); if (typeof item.id === 'number') await deleteSet(item.id);
@ -35,7 +35,7 @@ export default function SetItem({
const set: Set = {...item}; const set: Set = {...item};
delete set.id; delete set.id;
setShowMenu(false); setShowMenu(false);
navigation.navigate('EditSet', {set, workouts: [], count: 0}); navigation.navigate('Edit set', {set, workouts: [], count: 0});
}, [navigation, item]); }, [navigation, item]);
const longPress = useCallback( const longPress = useCallback(
@ -60,7 +60,7 @@ export default function SetItem({
<> <>
<List.Item <List.Item
onPress={() => onPress={() =>
navigation.navigate('EditSet', {set: item, workouts: [], count: 0}) navigation.navigate('Edit set', {set: item, workouts: [], count: 0})
} }
title={item.name} title={item.name}
description={`${item.reps} x ${item.weight}${item.unit || 'kg'}`} description={`${item.reps} x ${item.weight}${item.unit || 'kg'}`}

View File

@ -1,140 +0,0 @@
import {
NavigationProp,
useFocusEffect,
useNavigation,
} from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import {getBestSet} from './best.service';
import DrawerMenu from './DrawerMenu';
import {HomePageParams} from './home-page-params';
import Page from './Page';
import {getTodaysPlan} from './plan.service';
import Set from './set';
import {countToday, defaultSet, getSets, getToday} from './set.service';
import SetItem from './SetItem';
import {settings} from './settings.service';
const limit = 15;
export default function SetList() {
const [sets, setSets] = useState<Set[]>();
const [set, setSet] = useState<Set>();
const [count, setCount] = useState(0);
const [workouts, setWorkouts] = useState<string[]>([]);
const [offset, setOffset] = useState(0);
const [search, setSearch] = useState('');
const [end, setEnd] = useState(false);
const [dates, setDates] = useState(false);
const [images, setImages] = useState(true);
const navigation = useNavigation<NavigationProp<HomePageParams>>();
const predict = useCallback(async () => {
setCount(0);
setSet({...defaultSet});
if (!settings.predict) return;
const todaysPlan = await getTodaysPlan();
if (todaysPlan.length === 0) return;
const todaysWorkouts = todaysPlan[0].workouts.split(',');
setWorkouts(todaysWorkouts);
let workout = todaysWorkouts[0];
let best = await getBestSet(workout);
const todaysSet = await getToday();
if (!todaysSet || !todaysWorkouts.includes(todaysSet.name))
return setSet(best);
let _count = await countToday(todaysSet.name);
workout = todaysSet.name;
best = await getBestSet(workout);
const index = todaysWorkouts.indexOf(todaysSet.name) + 1;
if (_count >= Number(best.sets)) {
best = await getBestSet(todaysWorkouts[index]);
_count = 0;
}
if (best.name === '') setCount(0);
else setCount(_count);
setSet(best);
}, []);
const refresh = useCallback(async () => {
predict();
const newSets = await getSets({search: `%${search}%`, limit, offset: 0});
console.log(`${SetList.name}.refresh:`, {first: newSets[0]});
if (newSets.length === 0) return setSets([]);
setSets(newSets);
setOffset(0);
setEnd(false);
}, [search, predict]);
useFocusEffect(
useCallback(() => {
refresh();
navigation.getParent()?.setOptions({
headerRight: () => <DrawerMenu name="Home" />,
});
setImages(!!settings.images);
}, [refresh, navigation]),
);
useEffect(() => {
refresh();
}, [search, refresh]);
const renderItem = useCallback(
({item}: {item: Set}) => (
<SetItem
dates={dates}
setDates={setDates}
images={images}
setImages={setImages}
item={item}
key={item.id}
onRemove={refresh}
/>
),
[refresh, dates, setDates, images, setImages],
);
const next = useCallback(async () => {
if (end) return;
const newOffset = offset + limit;
console.log(`${SetList.name}.next:`, {offset, newOffset, search});
const newSets = await getSets({
search: `%${search}%`,
limit,
offset: newOffset,
});
if (newSets.length === 0) return setEnd(true);
if (!sets) return;
setSets([...sets, ...newSets]);
if (newSets.length < limit) return setEnd(true);
setOffset(newOffset);
}, [search, end, offset, sets]);
const onAdd = useCallback(async () => {
console.log(`${SetList.name}.onAdd`, {set, defaultSet, workouts});
navigation.navigate('EditSet', {
set: set || {...defaultSet},
workouts,
count,
});
}, [navigation, set, workouts, count]);
return (
<Page onAdd={onAdd} search={search} setSearch={setSearch}>
<FlatList
data={sets}
style={{height: '99%'}}
ListEmptyComponent={
<List.Item
title="No sets yet"
description="A set is a group of repetitions. E.g. 8 reps of Squats."
/>
}
renderItem={renderItem}
keyExtractor={s => s.id!.toString()}
onEndReached={next}
/>
</Page>
);
}

View File

@ -27,6 +27,7 @@ export default function SettingsPage() {
const [showUnit, setShowUnit] = useState(!!settings.showUnit); const [showUnit, setShowUnit] = useState(!!settings.showUnit);
const [workouts, setWorkouts] = useState(!!settings.workouts); const [workouts, setWorkouts] = useState(!!settings.workouts);
const [steps, setSteps] = useState(!!settings.steps); const [steps, setSteps] = useState(!!settings.steps);
const [focus, setFocus] = useState(settings.focus);
const {color, setColor} = useContext(CustomTheme); const {color, setColor} = useContext(CustomTheme);
const {toast} = useContext(SnackbarContext); const {toast} = useContext(SnackbarContext);
@ -48,6 +49,7 @@ export default function SettingsPage() {
color, color,
workouts: +workouts, workouts: +workouts,
steps: +steps, steps: +steps,
focus,
}); });
getSettings(); getSettings();
}, [ }, [
@ -61,6 +63,7 @@ export default function SettingsPage() {
color, color,
workouts, workouts,
steps, steps,
focus,
]); ]);
const changeAlarmEnabled = useCallback( const changeAlarmEnabled = useCallback(
@ -173,6 +176,18 @@ export default function SettingsPage() {
{input.name} {input.name}
</Switch> </Switch>
))} ))}
{'focus'.includes(search.toLowerCase()) && (
<Picker
style={{color}}
dropdownIconColor={color}
selectedValue={focus}
onValueChange={value => setFocus(value)}>
<Picker.Item value="" label="Don't auto focus" />
<Picker.Item value="name" label="Auto focus Name" />
<Picker.Item value="reps" label="Auto focus Reps" />
<Picker.Item value="weight" label="Auto focus Weight" />
</Picker>
)}
{'theme'.includes(search.toLowerCase()) && ( {'theme'.includes(search.toLowerCase()) && (
<Picker <Picker
style={{color}} style={{color}}
@ -190,7 +205,9 @@ export default function SettingsPage() {
</Picker> </Picker>
)} )}
{'alarm sound'.includes(search.toLowerCase()) && ( {'alarm sound'.includes(search.toLowerCase()) && (
<Button style={{alignSelf: 'flex-start'}} onPress={changeSound}> <Button
style={{alignSelf: 'flex-start', marginTop: MARGIN}}
onPress={changeSound}>
Alarm sound Alarm sound
{sound {sound
? ': ' + sound.split('/')[sound.split('/').length - 1] ? ': ' + sound.split('/')[sound.split('/').length - 1]

View File

@ -12,9 +12,9 @@ import {IconButton} from 'react-native-paper';
import Share from 'react-native-share'; import Share from 'react-native-share';
import {captureScreen} from 'react-native-view-shot'; import {captureScreen} from 'react-native-view-shot';
import {getVolumes, getWeightsBy} from './best.service'; import {getVolumes, getWeightsBy} from './best.service';
import {BestPageParams} from './BestPage';
import Chart from './Chart'; import Chart from './Chart';
import {PADDING} from './constants'; import {PADDING} from './constants';
import {DrawerParamList} from './drawer-param-list';
import {Metrics} from './metrics'; import {Metrics} from './metrics';
import {Periods} from './periods'; import {Periods} from './periods';
import Set from './set'; import Set from './set';
@ -22,7 +22,7 @@ import {formatMonth} from './time';
import Volume from './volume'; import Volume from './volume';
export default function ViewBest() { export default function ViewBest() {
const {params} = useRoute<RouteProp<BestPageParams, 'ViewBest'>>(); const {params} = useRoute<RouteProp<DrawerParamList, 'View best'>>();
const dark = useColorScheme() === 'dark'; const dark = useColorScheme() === 'dark';
const [weights, setWeights] = useState<Set[]>([]); const [weights, setWeights] = useState<Set[]>([]);
const [volumes, setVolumes] = useState<Volume[]>([]); const [volumes, setVolumes] = useState<Volume[]>([]);

View File

@ -3,9 +3,9 @@ import React, {useCallback, useState} from 'react';
import {GestureResponderEvent, Image} from 'react-native'; import {GestureResponderEvent, Image} from 'react-native';
import {List, Menu, Text} from 'react-native-paper'; import {List, Menu, Text} from 'react-native-paper';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import {DrawerParamList} from './drawer-param-list';
import Set from './set'; import Set from './set';
import {deleteSetsBy} from './set.service'; import {deleteSetsBy} from './set.service';
import {WorkoutsPageParams} from './WorkoutsPage';
export default function WorkoutItem({ export default function WorkoutItem({
item, item,
@ -17,7 +17,7 @@ export default function WorkoutItem({
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const [anchor, setAnchor] = useState({x: 0, y: 0}); const [anchor, setAnchor] = useState({x: 0, y: 0});
const [showRemove, setShowRemove] = useState(''); const [showRemove, setShowRemove] = useState('');
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>(); const navigation = useNavigation<NavigationProp<DrawerParamList>>();
const remove = useCallback(async () => { const remove = useCallback(async () => {
await deleteSetsBy(item.name); await deleteSetsBy(item.name);
@ -39,7 +39,7 @@ export default function WorkoutItem({
return ( return (
<> <>
<List.Item <List.Item
onPress={() => navigation.navigate('EditWorkout', {value: item})} onPress={() => navigation.navigate('Edit workout', {value: item})}
title={item.name} title={item.name}
description={`${item.sets} sets ${minutes}:${seconds} rest`} description={`${item.sets} sets ${minutes}:${seconds} rest`}
onLongPress={longPress} onLongPress={longPress}

View File

@ -1,98 +0,0 @@
import {
NavigationProp,
useFocusEffect,
useNavigation,
} from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import Page from './Page';
import Set from './set';
import {getDistinctSets} from './set.service';
import SetList from './SetList';
import WorkoutItem from './WorkoutItem';
import {WorkoutsPageParams} from './WorkoutsPage';
const limit = 15;
export default function WorkoutList() {
const [workouts, setWorkouts] = useState<Set[]>();
const [offset, setOffset] = useState(0);
const [search, setSearch] = useState('');
const [end, setEnd] = useState(false);
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>();
const refresh = useCallback(async () => {
const newWorkouts = await getDistinctSets({
search: `%${search}%`,
limit,
offset: 0,
});
console.log(`${WorkoutList.name}`, {newWorkout: newWorkouts[0]});
setWorkouts(newWorkouts);
setOffset(0);
setEnd(false);
}, [search]);
useEffect(() => {
refresh();
}, [search, refresh]);
useFocusEffect(
useCallback(() => {
refresh();
}, [refresh]),
);
const renderItem = useCallback(
({item}: {item: Set}) => (
<WorkoutItem item={item} key={item.name} onRemoved={refresh} />
),
[refresh],
);
const next = useCallback(async () => {
if (end) return;
const newOffset = offset + limit;
console.log(`${SetList.name}.next:`, {
offset,
limit,
newOffset,
search,
});
const newWorkouts = await getDistinctSets({
search: `%${search}%`,
limit,
offset: newOffset,
});
if (newWorkouts.length === 0) return setEnd(true);
if (!workouts) return;
setWorkouts([...workouts, ...newWorkouts]);
if (newWorkouts.length < limit) return setEnd(true);
setOffset(newOffset);
}, [search, end, offset, workouts]);
const onAdd = useCallback(async () => {
navigation.navigate('EditWorkout', {
value: {name: '', sets: 3, image: '', steps: '', reps: 0, weight: 0},
});
}, [navigation]);
return (
<Page onAdd={onAdd} search={search} setSearch={setSearch}>
<FlatList
data={workouts}
style={{height: '99%'}}
ListEmptyComponent={
<List.Item
title="No workouts yet."
description="A workout is something you do at the gym. For example Deadlifts are a workout."
/>
}
renderItem={renderItem}
keyExtractor={w => w.name}
onEndReached={next}
/>
</Page>
);
}

View File

@ -1,43 +1,97 @@
import {DrawerNavigationProp} from '@react-navigation/drawer'; import {
import {useNavigation} from '@react-navigation/native'; NavigationProp,
import {createStackNavigator} from '@react-navigation/stack'; useFocusEffect,
import React from 'react'; useNavigation,
import {IconButton} from 'react-native-paper'; } from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import {DrawerParamList} from './drawer-param-list'; import {DrawerParamList} from './drawer-param-list';
import EditWorkout from './EditWorkout'; import Page from './Page';
import Set from './set'; import Set from './set';
import WorkoutList from './WorkoutList'; import {getDistinctSets} from './set.service';
import WorkoutItem from './WorkoutItem';
export type WorkoutsPageParams = { const limit = 15;
WorkoutList: {};
EditWorkout: {
value: Set;
};
};
const Stack = createStackNavigator<WorkoutsPageParams>();
export default function WorkoutsPage() { export default function WorkoutsPage() {
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>(); const [workouts, setWorkouts] = useState<Set[]>();
const [offset, setOffset] = useState(0);
const [search, setSearch] = useState('');
const [end, setEnd] = useState(false);
const navigation = useNavigation<NavigationProp<DrawerParamList>>();
const refresh = useCallback(async () => {
const newWorkouts = await getDistinctSets({
search: `%${search}%`,
limit,
offset: 0,
});
console.log(`${WorkoutsPage.name}`, {newWorkout: newWorkouts[0]});
setWorkouts(newWorkouts);
setOffset(0);
setEnd(false);
}, [search]);
useEffect(() => {
refresh();
}, [search, refresh]);
useFocusEffect(
useCallback(() => {
refresh();
}, [refresh]),
);
const renderItem = useCallback(
({item}: {item: Set}) => (
<WorkoutItem item={item} key={item.name} onRemoved={refresh} />
),
[refresh],
);
const next = useCallback(async () => {
if (end) return;
const newOffset = offset + limit;
console.log(`${WorkoutsPage.name}.next:`, {
offset,
limit,
newOffset,
search,
});
const newWorkouts = await getDistinctSets({
search: `%${search}%`,
limit,
offset: newOffset,
});
if (newWorkouts.length === 0) return setEnd(true);
if (!workouts) return;
setWorkouts([...workouts, ...newWorkouts]);
if (newWorkouts.length < limit) return setEnd(true);
setOffset(newOffset);
}, [search, end, offset, workouts]);
const onAdd = useCallback(async () => {
navigation.navigate('Edit workout', {
value: {name: '', sets: 3, image: '', steps: '', reps: 0, weight: 0},
});
}, [navigation]);
return ( return (
<Stack.Navigator <Page onAdd={onAdd} search={search} setSearch={setSearch}>
screenOptions={{headerShown: false, animationEnabled: false}}> <FlatList
<Stack.Screen name="WorkoutList" component={WorkoutList} /> data={workouts}
<Stack.Screen style={{height: '99%'}}
name="EditWorkout" ListEmptyComponent={
component={EditWorkout} <List.Item
listeners={{ title="No workouts yet."
beforeRemove: () => { description="A workout is something you do at the gym. For example Deadlifts are a workout."
navigation.setOptions({ />
headerLeft: () => ( }
<IconButton icon="menu" onPress={navigation.openDrawer} /> renderItem={renderItem}
), keyExtractor={w => w.name}
title: 'Workouts', onEndReached={next}
});
},
}}
/> />
</Stack.Navigator> </Page>
); );
} }

3
db.ts
View File

@ -103,6 +103,9 @@ const migrations = [
` `
ALTER TABLE settings ADD COLUMN nextAlarm TEXT NULL ALTER TABLE settings ADD COLUMN nextAlarm TEXT NULL
`, `,
`
ALTER TABLE settings ADD COLUMN focus TEXT NULL
`,
]; ];
export let db: SQLiteDatabase; export let db: SQLiteDatabase;

View File

@ -1,7 +1,24 @@
import {Plan} from './plan';
import Set from './set';
export type DrawerParamList = { export type DrawerParamList = {
Home: {}; Home: {};
Settings: {}; Settings: {};
Best: {}; Best: {};
Plans: {}; Plans: {};
Workouts: {}; Workouts: {};
'Edit set': {
set: Set;
workouts: string[];
count: number;
};
'Edit plan': {
plan: Plan;
};
'Edit workout': {
value: Set;
};
'View best': {
best: Set;
};
}; };

View File

@ -1,10 +0,0 @@
import Set from './set';
export type HomePageParams = {
Sets: {};
EditSet: {
set: Set;
workouts: string[];
count: number;
};
};

View File

@ -16,7 +16,6 @@
"@react-native-picker/picker": "^2.4.4", "@react-native-picker/picker": "^2.4.4",
"@react-navigation/drawer": "^6.5.0", "@react-navigation/drawer": "^6.5.0",
"@react-navigation/native": "^6.0.13", "@react-navigation/native": "^6.0.13",
"@react-navigation/stack": "^6.3.0",
"@types/d3-shape": "^3.1.0", "@types/d3-shape": "^3.1.0",
"@types/react-native-sqlite-storage": "^5.0.2", "@types/react-native-sqlite-storage": "^5.0.2",
"@types/react-native-svg-charts": "^5.0.12", "@types/react-native-svg-charts": "^5.0.12",

View File

@ -1,8 +0,0 @@
import {Plan} from './plan';
export type PlanPageParams = {
PlanList: {};
EditPlan: {
plan: Plan;
};
};

View File

@ -3,5 +3,5 @@ import {DrawerParamList} from './drawer-param-list';
export default interface Route { export default interface Route {
name: keyof DrawerParamList; name: keyof DrawerParamList;
component: React.ComponentType<any>; component: React.ComponentType<any>;
icon: string; icon?: string;
} }

View File

@ -10,4 +10,5 @@ export default interface Settings {
workouts: number; workouts: number;
steps: number; steps: number;
nextAlarm?: string; nextAlarm?: string;
focus?: 'name' | 'reps' | 'weight';
} }

View File

@ -2393,24 +2393,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@react-navigation/stack@npm:^6.3.0":
version: 6.3.0
resolution: "@react-navigation/stack@npm:6.3.0"
dependencies:
"@react-navigation/elements": ^1.3.6
color: ^4.2.3
warn-once: ^0.1.0
peerDependencies:
"@react-navigation/native": ^6.0.0
react: "*"
react-native: "*"
react-native-gesture-handler: ">= 1.0.0"
react-native-safe-area-context: ">= 3.0.0"
react-native-screens: ">= 3.0.0"
checksum: fac540297b827249317e1383b6b4d47b3f356e6d20f8e9acadfb5ce5973faf63e56b74b18846837031c15264fc37518701aae16efcf9b8131d22b66f47c19d14
languageName: node
linkType: hard
"@sideway/address@npm:^4.1.3": "@sideway/address@npm:^4.1.3":
version: 4.1.4 version: 4.1.4
resolution: "@sideway/address@npm:4.1.4" resolution: "@sideway/address@npm:4.1.4"
@ -6434,7 +6416,6 @@ __metadata:
"@react-native-picker/picker": ^2.4.4 "@react-native-picker/picker": ^2.4.4
"@react-navigation/drawer": ^6.5.0 "@react-navigation/drawer": ^6.5.0
"@react-navigation/native": ^6.0.13 "@react-navigation/native": ^6.0.13
"@react-navigation/stack": ^6.3.0
"@types/d3-shape": ^3.1.0 "@types/d3-shape": ^3.1.0
"@types/react-native": ^0.69.0 "@types/react-native": ^0.69.0
"@types/react-native-sqlite-storage": ^5.0.2 "@types/react-native-sqlite-storage": ^5.0.2