Fix single views for new custom headers

This commit is contained in:
Brandon Presley 2022-10-23 19:13:58 +13:00
parent 36e6637ba2
commit 80b1a1ef56
16 changed files with 299 additions and 446 deletions

View File

@ -8,7 +8,7 @@ import {FlatList, Image} from 'react-native';
import {List} from 'react-native-paper'; import {List} from 'react-native-paper';
import {getBestReps, getBestWeights} from './best.service'; import {getBestReps, getBestWeights} from './best.service';
import {BestPageParams} from './BestPage'; import {BestPageParams} from './BestPage';
import Header from './Header'; import DrawerHeader from './DrawerHeader';
import Page from './Page'; import Page from './Page';
import Set from './set'; import Set from './set';
import {useSettings} from './use-settings'; import {useSettings} from './use-settings';
@ -33,10 +33,7 @@ export default function BestList() {
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
refresh(); refresh();
navigation.getParent()?.setOptions({ }, [refresh]),
headerRight: () => null,
});
}, [refresh, navigation]),
); );
useEffect(() => { useEffect(() => {
@ -60,7 +57,7 @@ export default function BestList() {
return ( return (
<> <>
<Header name="Best" /> <DrawerHeader name="Best" />
<Page search={search} setSearch={setSearch}> <Page search={search} setSearch={setSearch}>
{bests?.length === 0 ? ( {bests?.length === 0 ? (
<List.Item <List.Item

View File

@ -1,10 +1,6 @@
import {DrawerNavigationProp} from '@react-navigation/drawer';
import {useNavigation} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack'; import {createStackNavigator} from '@react-navigation/stack';
import React from 'react'; import React from 'react';
import {IconButton} from 'react-native-paper';
import BestList from './BestList'; import BestList from './BestList';
import {DrawerParamList} from './drawer-param-list';
import Set from './set'; import Set from './set';
import ViewBest from './ViewBest'; import ViewBest from './ViewBest';
@ -17,26 +13,11 @@ export type BestPageParams = {
}; };
export default function BestPage() { export default function BestPage() {
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>();
return ( return (
<Stack.Navigator <Stack.Navigator
screenOptions={{headerShown: false, animationEnabled: false}}> screenOptions={{headerShown: false, animationEnabled: false}}>
<Stack.Screen name="BestList" component={BestList} /> <Stack.Screen name="BestList" component={BestList} />
<Stack.Screen <Stack.Screen name="ViewBest" component={ViewBest} />
name="ViewBest"
component={ViewBest}
listeners={{
beforeRemove: () => {
navigation.setOptions({
headerLeft: () => (
<IconButton icon="menu" onPress={navigation.openDrawer} />
),
title: 'Best',
});
},
}}
/>
</Stack.Navigator> </Stack.Navigator>
); );
} }

View File

@ -1,15 +1,16 @@
import {DrawerNavigationProp} from '@react-navigation/drawer';
import {useNavigation} from '@react-navigation/native'; import {useNavigation} from '@react-navigation/native';
import React from 'react'; import React from 'react';
import {Appbar, IconButton} from 'react-native-paper'; import {Appbar, IconButton} from 'react-native-paper';
import {DrawerParamList} from './drawer-param-list'; import {DrawerParamList} from './drawer-param-list';
import DrawerMenu from './DrawerMenu'; import DrawerMenu from './DrawerMenu';
export default function Header({name}: {name: keyof DrawerParamList}) { export default function DrawerHeader({name}: {name: keyof DrawerParamList}) {
const navigation = useNavigation(); const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>();
return ( return (
<Appbar.Header> <Appbar.Header>
<IconButton icon="menu" onPress={(navigation as any).openDrawer} /> <IconButton icon="menu" onPress={navigation.openDrawer} />
<Appbar.Content title={name} /> <Appbar.Content title={name} />
<DrawerMenu name={name} /> <DrawerMenu name={name} />
</Appbar.Header> </Appbar.Header>

View File

@ -1,18 +1,18 @@
import { import {
NavigationProp, NavigationProp,
RouteProp, RouteProp,
useFocusEffect,
useNavigation, useNavigation,
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 {ScrollView, StyleSheet, View} from 'react-native';
import {Button, IconButton, Text} from 'react-native-paper'; import {Button, 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 {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 StackHeader from './StackHeader';
import Switch from './Switch'; import Switch from './Switch';
import {DAYS} from './time'; import {DAYS} from './time';
@ -28,19 +28,6 @@ export default function EditPlan() {
const [names, setNames] = useState<string[]>([]); const [names, setNames] = useState<string[]>([]);
const navigation = useNavigation<NavigationProp<DrawerParamList>>(); const navigation = useNavigation<NavigationProp<DrawerParamList>>();
useFocusEffect(
useCallback(() => {
console.log(`${EditPlan.name}.focus:`, {plan});
navigation.getParent()?.setOptions({
headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} />
),
headerRight: () => null,
title: plan.id ? 'Edit plan' : 'Create plan',
});
}, [navigation, plan]),
);
useEffect(() => { useEffect(() => {
getNames().then(n => { getNames().then(n => {
console.log(EditPlan.name, {n}); console.log(EditPlan.name, {n});
@ -87,59 +74,62 @@ export default function EditPlan() {
); );
return ( return (
<View style={{padding: PADDING, flex: 1}}> <>
<ScrollView style={{flex: 1}}> <StackHeader title="Edit plan" />
<Text style={styles.title}>Days</Text> <View style={{padding: PADDING, flex: 1}}>
{DAYS.map(day => ( <ScrollView style={{flex: 1}}>
<Switch <Text style={styles.title}>Days</Text>
key={day} {DAYS.map(day => (
onValueChange={value => toggleDay(value, day)}
onPress={() => toggleDay(!days.includes(day), day)}
value={days.includes(day)}>
{day}
</Switch>
))}
<Text style={[styles.title, {marginTop: MARGIN}]}>Workouts</Text>
{names.length === 0 ? (
<View>
<Text>No workouts found.</Text>
</View>
) : (
names.map(name => (
<Switch <Switch
key={name} key={day}
onValueChange={value => toggleWorkout(value, name)} onValueChange={value => toggleDay(value, day)}
value={workouts.includes(name)} onPress={() => toggleDay(!days.includes(day), day)}
onPress={() => toggleWorkout(!workouts.includes(name), name)}> value={days.includes(day)}>
{name} {day}
</Switch> </Switch>
)) ))}
<Text style={[styles.title, {marginTop: MARGIN}]}>Workouts</Text>
{names.length === 0 ? (
<View>
<Text>No workouts found.</Text>
</View>
) : (
names.map(name => (
<Switch
key={name}
onValueChange={value => toggleWorkout(value, name)}
value={workouts.includes(name)}
onPress={() => toggleWorkout(!workouts.includes(name), name)}>
{name}
</Switch>
))
)}
</ScrollView>
{names.length === 0 ? (
<Button
disabled={workouts.length === 0 && days.length === 0}
mode="contained"
onPress={() => {
navigation.goBack();
navigation.navigate('Workouts', {
screen: 'EditWorkout',
params: {value: {name: ''}},
});
}}>
Add workout
</Button>
) : (
<Button
disabled={workouts.length === 0 && days.length === 0}
style={{marginTop: MARGIN}}
mode="contained"
icon="save"
onPress={save}>
Save
</Button>
)} )}
</ScrollView> </View>
{names.length === 0 ? ( </>
<Button
disabled={workouts.length === 0 && days.length === 0}
mode="contained"
onPress={() => {
navigation.goBack();
navigation.navigate('Workouts', {
screen: 'EditWorkout',
params: {value: {name: ''}},
});
}}>
Add workout
</Button>
) : (
<Button
disabled={workouts.length === 0 && days.length === 0}
style={{marginTop: MARGIN}}
mode="contained"
icon="save"
onPress={save}>
Save
</Button>
)}
</View>
); );
} }

View File

@ -1,18 +1,13 @@
import { import {RouteProp, useNavigation, useRoute} from '@react-navigation/native';
RouteProp,
useFocusEffect,
useNavigation,
useRoute,
} from '@react-navigation/native';
import React, {useCallback} from 'react'; import React, {useCallback} from 'react';
import {NativeModules, View} from 'react-native'; import {NativeModules, View} from 'react-native';
import {IconButton} from 'react-native-paper';
import {PADDING} from './constants'; import {PADDING} from './constants';
import {HomePageParams} from './home-page-params'; import {HomePageParams} from './home-page-params';
import {useSnackbar} from './MassiveSnack'; import {useSnackbar} from './MassiveSnack';
import Set from './set'; import Set from './set';
import {addSet, getSet, updateSet} from './set.service'; import {addSet, getSet, updateSet} from './set.service';
import SetForm from './SetForm'; import SetForm from './SetForm';
import StackHeader from './StackHeader';
import {useSettings} from './use-settings'; import {useSettings} from './use-settings';
export default function EditSet() { export default function EditSet() {
@ -22,21 +17,6 @@ export default function EditSet() {
const {toast} = useSnackbar(); const {toast} = useSnackbar();
const {settings} = useSettings(); const {settings} = useSettings();
useFocusEffect(
useCallback(() => {
console.log(`${EditSet.name}.focus:`, set);
let title = 'Create set';
if (typeof set.id === 'number') title = 'Edit set';
navigation.getParent()?.setOptions({
headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} />
),
headerRight: null,
title,
});
}, [navigation, set]),
);
const startTimer = useCallback( const startTimer = useCallback(
async (name: string) => { async (name: string) => {
if (!settings.alarm) return; if (!settings.alarm) return;
@ -85,8 +65,11 @@ export default function EditSet() {
); );
return ( return (
<View style={{padding: PADDING, flex: 1}}> <>
<SetForm save={save} set={set} /> <StackHeader title="Edit set" />
</View> <View style={{padding: PADDING, flex: 1}}>
<SetForm save={save} set={set} />
</View>
</>
); );
} }

View File

@ -1,19 +1,15 @@
import { import {RouteProp, useNavigation, useRoute} from '@react-navigation/native';
RouteProp,
useFocusEffect,
useNavigation,
useRoute,
} from '@react-navigation/native';
import React, {useCallback, useRef, useState} from 'react'; import React, {useCallback, useRef, useState} from 'react';
import {ScrollView, TextInput, View} from 'react-native'; import {ScrollView, TextInput, 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, TouchableRipple} from 'react-native-paper';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import {MARGIN, PADDING} from './constants'; import {MARGIN, PADDING} from './constants';
import MassiveInput from './MassiveInput'; import MassiveInput from './MassiveInput';
import {useSnackbar} from './MassiveSnack'; import {useSnackbar} 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 StackHeader from './StackHeader';
import {useSettings} from './use-settings'; import {useSettings} from './use-settings';
import {WorkoutsPageParams} from './WorkoutsPage'; import {WorkoutsPageParams} from './WorkoutsPage';
@ -39,19 +35,6 @@ export default function EditWorkout() {
const secondsRef = useRef<TextInput>(null); const secondsRef = useRef<TextInput>(null);
const {settings} = useSettings(); const {settings} = useSettings();
useFocusEffect(
useCallback(() => {
navigation.getParent()?.setOptions({
headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} />
),
headerRight: null,
title: params.value.name || 'New workout',
});
if (!name) return;
}, [navigation, name, params.value.name]),
);
const update = async () => { const update = async () => {
await updateManySet({ await updateManySet({
oldName: params.value.name, oldName: params.value.name,
@ -118,83 +101,86 @@ export default function EditWorkout() {
}; };
return ( return (
<View style={{padding: PADDING, flex: 1}}> <>
<ScrollView style={{flex: 1}}> <StackHeader title="Edit workout" />
<MassiveInput <View style={{padding: PADDING, flex: 1}}>
autoFocus <ScrollView style={{flex: 1}}>
label="Name"
value={name}
onChangeText={handleName}
onSubmitEditing={submitName}
/>
{!!settings.steps && (
<MassiveInput <MassiveInput
innerRef={stepsRef} autoFocus
selectTextOnFocus={false} label="Name"
value={steps} value={name}
onChangeText={handleSteps} onChangeText={handleName}
label="Steps" onSubmitEditing={submitName}
multiline
onSubmitEditing={() => setsRef.current?.focus()}
/> />
)} {!!settings.steps && (
{!!settings.showSets && (
<MassiveInput
innerRef={setsRef}
value={sets}
onChangeText={setSets}
label="Sets per workout"
keyboardType="numeric"
onSubmitEditing={() => minutesRef.current?.focus()}
/>
)}
{!!settings.alarm && (
<>
<MassiveInput <MassiveInput
innerRef={minutesRef} innerRef={stepsRef}
onSubmitEditing={() => secondsRef.current?.focus()} selectTextOnFocus={false}
value={minutes} value={steps}
onChangeText={setMinutes} onChangeText={handleSteps}
label="Rest minutes" label="Steps"
keyboardType="numeric" multiline
onSubmitEditing={() => setsRef.current?.focus()}
/> />
)}
{!!settings.showSets && (
<MassiveInput <MassiveInput
innerRef={secondsRef} innerRef={setsRef}
value={seconds} value={sets}
onChangeText={setSeconds} onChangeText={setSets}
label="Rest seconds" label="Sets per workout"
keyboardType="numeric" keyboardType="numeric"
blurOnSubmit onSubmitEditing={() => minutesRef.current?.focus()}
/> />
</> )}
)} {!!settings.alarm && (
{!!settings.images && uri && ( <>
<TouchableRipple <MassiveInput
style={{marginBottom: MARGIN}} innerRef={minutesRef}
onPress={changeImage} onSubmitEditing={() => secondsRef.current?.focus()}
onLongPress={() => setShowRemove(true)}> value={minutes}
<Card.Cover source={{uri}} /> onChangeText={setMinutes}
</TouchableRipple> label="Rest minutes"
)} keyboardType="numeric"
{!!settings.images && !uri && ( />
<Button <MassiveInput
style={{marginBottom: MARGIN}} innerRef={secondsRef}
onPress={changeImage} value={seconds}
icon="add-photo-alternate"> onChangeText={setSeconds}
Image label="Rest seconds"
</Button> keyboardType="numeric"
)} blurOnSubmit
</ScrollView> />
<Button disabled={!name} mode="contained" icon="save" onPress={save}> </>
Save )}
</Button> {!!settings.images && uri && (
<ConfirmDialog <TouchableRipple
title="Remove image" style={{marginBottom: MARGIN}}
onOk={handleRemove} onPress={changeImage}
show={showRemove} onLongPress={() => setShowRemove(true)}>
setShow={setShowRemove}> <Card.Cover source={{uri}} />
Are you sure you want to remove the image? </TouchableRipple>
</ConfirmDialog> )}
</View> {!!settings.images && !uri && (
<Button
style={{marginBottom: MARGIN}}
onPress={changeImage}
icon="add-photo-alternate">
Image
</Button>
)}
</ScrollView>
<Button disabled={!name} mode="contained" icon="save" onPress={save}>
Save
</Button>
<ConfirmDialog
title="Remove image"
onOk={handleRemove}
show={showRemove}
setShow={setShowRemove}>
Are you sure you want to remove the image?
</ConfirmDialog>
</View>
</>
); );
} }

View File

@ -1,9 +1,5 @@
import {DrawerNavigationProp} from '@react-navigation/drawer';
import {useNavigation} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack'; import {createStackNavigator} from '@react-navigation/stack';
import React from 'react'; import React from 'react';
import {IconButton} from 'react-native-paper';
import {DrawerParamList} from './drawer-param-list';
import EditSet from './EditSet'; import EditSet from './EditSet';
import {HomePageParams} from './home-page-params'; import {HomePageParams} from './home-page-params';
import SetList from './SetList'; import SetList from './SetList';
@ -11,26 +7,11 @@ import SetList from './SetList';
const Stack = createStackNavigator<HomePageParams>(); const Stack = createStackNavigator<HomePageParams>();
export default function HomePage() { export default function HomePage() {
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>();
return ( return (
<Stack.Navigator <Stack.Navigator
screenOptions={{headerShown: false, animationEnabled: false}}> screenOptions={{headerShown: false, animationEnabled: false}}>
<Stack.Screen name="Sets" component={SetList} /> <Stack.Screen name="Sets" component={SetList} />
<Stack.Screen <Stack.Screen name="EditSet" component={EditSet} />
name="EditSet"
component={EditSet}
listeners={{
beforeRemove: () => {
navigation.setOptions({
headerLeft: () => (
<IconButton icon="menu" onPress={navigation.openDrawer} />
),
title: 'Home',
});
},
}}
/>
</Stack.Navigator> </Stack.Navigator>
); );
} }

View File

@ -6,8 +6,7 @@ import {
import React, {useCallback, useEffect, useState} from 'react'; import React, {useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native'; import {FlatList} from 'react-native';
import {List} from 'react-native-paper'; import {List} from 'react-native-paper';
import DrawerMenu from './DrawerMenu'; import DrawerHeader from './DrawerHeader';
import Header from './Header';
import Page from './Page'; import Page from './Page';
import {Plan} from './plan'; import {Plan} from './plan';
import {PlanPageParams} from './plan-page-params'; import {PlanPageParams} from './plan-page-params';
@ -26,10 +25,7 @@ export default function PlanList() {
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
refresh(); refresh();
navigation.getParent()?.setOptions({ }, [refresh]),
headerRight: () => <DrawerMenu name="Plans" />,
});
}, [refresh, navigation]),
); );
useEffect(() => { useEffect(() => {
@ -48,7 +44,7 @@ export default function PlanList() {
return ( return (
<> <>
<Header name="Plans" /> <DrawerHeader name="Plans" />
<Page onAdd={onAdd} search={search} setSearch={setSearch}> <Page onAdd={onAdd} search={search} setSearch={setSearch}>
{plans?.length === 0 ? ( {plans?.length === 0 ? (
<List.Item <List.Item

View File

@ -1,9 +1,5 @@
import {DrawerNavigationProp} from '@react-navigation/drawer';
import {useNavigation} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack'; import {createStackNavigator} from '@react-navigation/stack';
import React from 'react'; import React from 'react';
import {IconButton} from 'react-native-paper';
import {DrawerParamList} from './drawer-param-list';
import EditPlan from './EditPlan'; import EditPlan from './EditPlan';
import {PlanPageParams} from './plan-page-params'; import {PlanPageParams} from './plan-page-params';
import PlanList from './PlanList'; import PlanList from './PlanList';
@ -12,40 +8,12 @@ import StartPlan from './StartPlan';
const Stack = createStackNavigator<PlanPageParams>(); const Stack = createStackNavigator<PlanPageParams>();
export default function PlanPage() { export default function PlanPage() {
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>();
return ( return (
<Stack.Navigator <Stack.Navigator
screenOptions={{headerShown: false, animationEnabled: false}}> screenOptions={{headerShown: false, animationEnabled: false}}>
<Stack.Screen name="PlanList" component={PlanList} /> <Stack.Screen name="PlanList" component={PlanList} />
<Stack.Screen <Stack.Screen name="EditPlan" component={EditPlan} />
name="EditPlan" <Stack.Screen name="StartPlan" component={StartPlan} />
component={EditPlan}
listeners={{
beforeRemove: () => {
navigation.setOptions({
headerLeft: () => (
<IconButton icon="menu" onPress={navigation.openDrawer} />
),
title: 'Plans',
});
},
}}
/>
<Stack.Screen
name="StartPlan"
component={StartPlan}
listeners={{
beforeRemove: () => {
navigation.setOptions({
headerLeft: () => (
<IconButton icon="menu" onPress={navigation.openDrawer} />
),
title: 'Plans',
});
},
}}
/>
</Stack.Navigator> </Stack.Navigator>
); );
} }

View File

@ -6,7 +6,7 @@ import {
import React, {useCallback, useEffect, useState} from 'react'; import React, {useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native'; import {FlatList} from 'react-native';
import {List} from 'react-native-paper'; import {List} from 'react-native-paper';
import Header from './Header'; import DrawerHeader from './DrawerHeader';
import {HomePageParams} from './home-page-params'; import {HomePageParams} from './home-page-params';
import Page from './Page'; import Page from './Page';
import Set from './set'; import Set from './set';
@ -84,7 +84,7 @@ export default function SetList() {
return ( return (
<> <>
<Header name="Home" /> <DrawerHeader name="Home" />
<Page onAdd={onAdd} search={search} setSearch={setSearch}> <Page onAdd={onAdd} search={search} setSearch={setSearch}>
{sets?.length === 0 ? ( {sets?.length === 0 ? (
<List.Item <List.Item

View File

@ -8,7 +8,7 @@ import {useColor} from './color';
import {darkColors, lightColors} from './colors'; import {darkColors, lightColors} from './colors';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import {MARGIN} from './constants'; import {MARGIN} from './constants';
import Header from './Header'; import DrawerHeader from './DrawerHeader';
import Input from './input'; import Input from './input';
import {useSnackbar} from './MassiveSnack'; import {useSnackbar} from './MassiveSnack';
import Page from './Page'; import Page from './Page';
@ -168,7 +168,7 @@ export default function SettingsPage() {
return ( return (
<> <>
<Header name="Settings" /> <DrawerHeader name="Settings" />
<Page search={search} setSearch={setSearch}> <Page search={search} setSearch={setSearch}>
<ScrollView style={{marginTop: MARGIN}}> <ScrollView style={{marginTop: MARGIN}}>
{switches {switches

30
StackHeader.tsx Normal file
View File

@ -0,0 +1,30 @@
import {useNavigation} from '@react-navigation/native';
import React from 'react';
import Share from 'react-native-share';
import {FileSystem} from 'react-native-file-access';
import {Appbar, IconButton} from 'react-native-paper';
import {captureScreen} from 'react-native-view-shot';
export default function StackHeader({title}: {title: string}) {
const navigation = useNavigation();
return (
<Appbar.Header>
<IconButton icon="arrow-back" onPress={navigation.goBack} />
<Appbar.Content title={title} />
<IconButton
onPress={() =>
captureScreen().then(async uri => {
const base64 = await FileSystem.readFile(uri, 'base64');
const url = `data:image/jpeg;base64,${base64}`;
Share.open({
type: 'image/jpeg',
url,
});
})
}
icon="share"
/>
</Appbar.Header>
);
}

View File

@ -1,13 +1,8 @@
import { import {RouteProp, useFocusEffect, useRoute} from '@react-navigation/native';
RouteProp,
useFocusEffect,
useNavigation,
useRoute,
} from '@react-navigation/native';
import React, {useCallback, useMemo, useRef, useState} from 'react'; import React, {useCallback, useMemo, useRef, useState} from 'react';
import {NativeModules, TextInput, View} from 'react-native'; import {NativeModules, TextInput, View} from 'react-native';
import {FlatList} from 'react-native-gesture-handler'; import {FlatList} from 'react-native-gesture-handler';
import {Button, IconButton, List, RadioButton} from 'react-native-paper'; import {Button, List, RadioButton} from 'react-native-paper';
import {getBestSet} from './best.service'; import {getBestSet} from './best.service';
import {useColor} from './color'; import {useColor} from './color';
import {PADDING} from './constants'; import {PADDING} from './constants';
@ -18,6 +13,7 @@ import {PlanPageParams} from './plan-page-params';
import Set from './set'; import Set from './set';
import {addSet, countManyToday, getDistinctSets} from './set.service'; import {addSet, countManyToday, getDistinctSets} from './set.service';
import SetForm from './SetForm'; import SetForm from './SetForm';
import StackHeader from './StackHeader';
import {useSettings} from './use-settings'; import {useSettings} from './use-settings';
export default function StartPlan() { export default function StartPlan() {
@ -38,7 +34,6 @@ export default function StartPlan() {
const weightRef = useRef<TextInput>(null); const weightRef = useRef<TextInput>(null);
const repsRef = useRef<TextInput>(null); const repsRef = useRef<TextInput>(null);
const unitRef = useRef<TextInput>(null); const unitRef = useRef<TextInput>(null);
const navigation = useNavigation();
const workouts = useMemo(() => params.plan.workouts.split(','), [params]); const workouts = useMemo(() => params.plan.workouts.split(','), [params]);
const {color} = useColor(); const {color} = useColor();
@ -49,13 +44,6 @@ export default function StartPlan() {
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
navigation.getParent()?.setOptions({
headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} />
),
headerRight: null,
title: params.plan.days.replace(/,/g, ', '),
});
countManyToday().then(newCounts => { countManyToday().then(newCounts => {
setCounts(newCounts); setCounts(newCounts);
console.log(`${StartPlan.name}.focus:`, {newCounts}); console.log(`${StartPlan.name}.focus:`, {newCounts});
@ -66,7 +54,7 @@ export default function StartPlan() {
console.log(`${StartPlan.name}.focus:`, {newDistinct}); console.log(`${StartPlan.name}.focus:`, {newDistinct});
}, },
); );
}, [navigation, params]), }, [params]),
); );
const handleSubmit = async () => { const handleSubmit = async () => {
@ -129,70 +117,74 @@ export default function StartPlan() {
if (!distinctSets) return; if (!distinctSets) return;
const distinct = distinctSets.find(d => d.name === countName); const distinct = distinctSets.find(d => d.name === countName);
console.log(`${StartPlan.name}:`, {distinct}); console.log(`${StartPlan.name}:`, {distinct});
if (settings.showSets) return `${count?.total || 0} / ${distinct?.sets}`; if (settings.showSets)
return `${count?.total || 0} / ${distinct?.sets || 3}`;
return count?.total || '0'; return count?.total || '0';
}, },
[counts, distinctSets, settings.showSets], [counts, distinctSets, settings.showSets],
); );
return ( return (
<View style={{padding: PADDING, flex: 1, flexDirection: 'column'}}> <>
<View style={{flex: 1}}> <StackHeader title={params.plan.days.replace(/,/g, ', ')} />
<MassiveInput <View style={{padding: PADDING, flex: 1, flexDirection: 'column'}}>
label="Reps" <View style={{flex: 1}}>
keyboardType="numeric"
value={reps}
onChangeText={setReps}
onSubmitEditing={() => weightRef.current?.focus()}
selection={selection}
onSelectionChange={e => setSelection(e.nativeEvent.selection)}
innerRef={repsRef}
/>
<MassiveInput
label="Weight"
keyboardType="numeric"
value={weight}
onChangeText={setWeight}
onSubmitEditing={handleSubmit}
innerRef={weightRef}
blurOnSubmit
/>
{!!settings.showUnit && (
<MassiveInput <MassiveInput
autoCapitalize="none" label="Reps"
label="Unit" keyboardType="numeric"
value={unit} value={reps}
onChangeText={handleUnit} onChangeText={setReps}
innerRef={unitRef} onSubmitEditing={() => weightRef.current?.focus()}
selection={selection}
onSelectionChange={e => setSelection(e.nativeEvent.selection)}
innerRef={repsRef}
/> />
)} <MassiveInput
{counts && distinctSets && ( label="Weight"
<FlatList keyboardType="numeric"
data={workouts} value={weight}
renderItem={({item, index}) => ( onChangeText={setWeight}
<List.Item onSubmitEditing={handleSubmit}
title={item} innerRef={weightRef}
description={getDescription(item)} blurOnSubmit
onPress={() => select(index)}
left={() => (
<View
style={{alignItems: 'center', justifyContent: 'center'}}>
<RadioButton
onPress={() => select(index)}
value={index.toString()}
status={selected === index ? 'checked' : 'unchecked'}
color={color}
/>
</View>
)}
/>
)}
/> />
)} {!!settings.showUnit && (
<MassiveInput
autoCapitalize="none"
label="Unit"
value={unit}
onChangeText={handleUnit}
innerRef={unitRef}
/>
)}
{counts && distinctSets && (
<FlatList
data={workouts}
renderItem={({item, index}) => (
<List.Item
title={item}
description={getDescription(item)}
onPress={() => select(index)}
left={() => (
<View
style={{alignItems: 'center', justifyContent: 'center'}}>
<RadioButton
onPress={() => select(index)}
value={index.toString()}
status={selected === index ? 'checked' : 'unchecked'}
color={color}
/>
</View>
)}
/>
)}
/>
)}
</View>
<Button mode="contained" icon="save" onPress={handleSubmit}>
Save
</Button>
</View> </View>
<Button mode="contained" icon="save" onPress={handleSubmit}> </>
Save
</Button>
</View>
); );
} }

View File

@ -1,16 +1,7 @@
import {Picker} from '@react-native-picker/picker'; import {Picker} from '@react-native-picker/picker';
import { import {RouteProp, useRoute} from '@react-navigation/native';
RouteProp, import React, {useEffect, useState} from 'react';
useFocusEffect,
useNavigation,
useRoute,
} from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react';
import {View} from 'react-native'; import {View} from 'react-native';
import {FileSystem} from 'react-native-file-access';
import {IconButton} from 'react-native-paper';
import Share from 'react-native-share';
import {captureScreen} from 'react-native-view-shot';
import {getOneRepMax, getVolumes, getWeightsBy} from './best.service'; import {getOneRepMax, getVolumes, getWeightsBy} from './best.service';
import {BestPageParams} from './BestPage'; import {BestPageParams} from './BestPage';
import Chart from './Chart'; import Chart from './Chart';
@ -18,6 +9,7 @@ import {PADDING} from './constants';
import {Metrics} from './metrics'; import {Metrics} from './metrics';
import {Periods} from './periods'; import {Periods} from './periods';
import Set from './set'; import Set from './set';
import StackHeader from './StackHeader';
import {formatMonth} from './time'; import {formatMonth} from './time';
import useDark from './use-dark'; import useDark from './use-dark';
import Volume from './volume'; import Volume from './volume';
@ -29,34 +21,6 @@ export default function ViewBest() {
const [volumes, setVolumes] = useState<Volume[]>([]); const [volumes, setVolumes] = useState<Volume[]>([]);
const [metric, setMetric] = useState(Metrics.Weight); const [metric, setMetric] = useState(Metrics.Weight);
const [period, setPeriod] = useState(Periods.Monthly); const [period, setPeriod] = useState(Periods.Monthly);
const navigation = useNavigation();
useFocusEffect(
useCallback(() => {
console.log(`${ViewBest.name}.useFocusEffect`);
navigation.getParent()?.setOptions({
headerLeft: () => (
<IconButton icon="arrow-back" onPress={() => navigation.goBack()} />
),
headerRight: () => (
<IconButton
onPress={() =>
captureScreen().then(async uri => {
const base64 = await FileSystem.readFile(uri, 'base64');
const url = `data:image/jpeg;base64,${base64}`;
Share.open({
type: 'image/jpeg',
url,
});
})
}
icon="share"
/>
),
title: params.best.name,
});
}, [navigation, params.best]),
);
useEffect(() => { useEffect(() => {
console.log(`${ViewBest.name}.useEffect`, {metric}); console.log(`${ViewBest.name}.useEffect`, {metric});
@ -74,44 +38,47 @@ export default function ViewBest() {
}, [params.best.name, metric, period]); }, [params.best.name, metric, period]);
return ( return (
<View style={{padding: PADDING}}> <>
<Picker <StackHeader title={params.best.name} />
style={{color: dark ? 'white' : 'black'}} <View style={{padding: PADDING}}>
dropdownIconColor={dark ? 'white' : 'black'} <Picker
selectedValue={metric} style={{color: dark ? 'white' : 'black'}}
onValueChange={value => setMetric(value)}> dropdownIconColor={dark ? 'white' : 'black'}
<Picker.Item value={Metrics.Volume} label={Metrics.Volume} /> selectedValue={metric}
<Picker.Item value={Metrics.Weight} label={Metrics.Weight} /> onValueChange={value => setMetric(value)}>
<Picker.Item value={Metrics.OneRepMax} label={Metrics.OneRepMax} /> <Picker.Item value={Metrics.Volume} label={Metrics.Volume} />
</Picker> <Picker.Item value={Metrics.Weight} label={Metrics.Weight} />
<Picker <Picker.Item value={Metrics.OneRepMax} label={Metrics.OneRepMax} />
style={{color: dark ? 'white' : 'black'}} </Picker>
dropdownIconColor={dark ? 'white' : 'black'} <Picker
selectedValue={period} style={{color: dark ? 'white' : 'black'}}
onValueChange={value => setPeriod(value)}> dropdownIconColor={dark ? 'white' : 'black'}
<Picker.Item value={Periods.Weekly} label={Periods.Weekly} /> selectedValue={period}
<Picker.Item value={Periods.Monthly} label={Periods.Monthly} /> onValueChange={value => setPeriod(value)}>
<Picker.Item value={Periods.Yearly} label={Periods.Yearly} /> <Picker.Item value={Periods.Weekly} label={Periods.Weekly} />
</Picker> <Picker.Item value={Periods.Monthly} label={Periods.Monthly} />
{metric === Metrics.Volume ? ( <Picker.Item value={Periods.Yearly} label={Periods.Yearly} />
<Chart </Picker>
yData={volumes.map(v => v.value)} {metric === Metrics.Volume ? (
yFormat={(value: number) => <Chart
`${value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}${ yData={volumes.map(v => v.value)}
volumes[0].unit yFormat={(value: number) =>
}` `${value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}${
} volumes[0].unit
xData={weights} }`
xFormat={(_value, index) => formatMonth(weights[index].created!)} }
/> xData={weights}
) : ( xFormat={(_value, index) => formatMonth(weights[index].created!)}
<Chart />
yData={weights.map(set => set.weight)} ) : (
yFormat={value => `${value}${weights[0].unit}`} <Chart
xData={weights} yData={weights.map(set => set.weight)}
xFormat={(_value, index) => formatMonth(weights[index].created!)} yFormat={value => `${value}${weights[0].unit}`}
/> xData={weights}
)} xFormat={(_value, index) => formatMonth(weights[index].created!)}
</View> />
)}
</View>
</>
); );
} }

View File

@ -6,7 +6,7 @@ import {
import React, {useCallback, useEffect, useState} from 'react'; import React, {useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native'; import {FlatList} from 'react-native';
import {List} from 'react-native-paper'; import {List} from 'react-native-paper';
import Header from './Header'; import DrawerHeader from './DrawerHeader';
import Page from './Page'; import Page from './Page';
import Set from './set'; import Set from './set';
import {getDistinctSets} from './set.service'; import {getDistinctSets} from './set.service';
@ -81,7 +81,7 @@ export default function WorkoutList() {
return ( return (
<> <>
<Header name="Workouts" /> <DrawerHeader name="Workouts" />
<Page onAdd={onAdd} search={search} setSearch={setSearch}> <Page onAdd={onAdd} search={search} setSearch={setSearch}>
{workouts?.length === 0 ? ( {workouts?.length === 0 ? (
<List.Item <List.Item

View File

@ -1,9 +1,5 @@
import {DrawerNavigationProp} from '@react-navigation/drawer';
import {useNavigation} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack'; import {createStackNavigator} from '@react-navigation/stack';
import React from 'react'; import React from 'react';
import {IconButton} from 'react-native-paper';
import {DrawerParamList} from './drawer-param-list';
import EditWorkout from './EditWorkout'; import EditWorkout from './EditWorkout';
import Set from './set'; import Set from './set';
import WorkoutList from './WorkoutList'; import WorkoutList from './WorkoutList';
@ -18,26 +14,11 @@ export type WorkoutsPageParams = {
const Stack = createStackNavigator<WorkoutsPageParams>(); const Stack = createStackNavigator<WorkoutsPageParams>();
export default function WorkoutsPage() { export default function WorkoutsPage() {
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>();
return ( return (
<Stack.Navigator <Stack.Navigator
screenOptions={{headerShown: false, animationEnabled: false}}> screenOptions={{headerShown: false, animationEnabled: false}}>
<Stack.Screen name="WorkoutList" component={WorkoutList} /> <Stack.Screen name="WorkoutList" component={WorkoutList} />
<Stack.Screen <Stack.Screen name="EditWorkout" component={EditWorkout} />
name="EditWorkout"
component={EditWorkout}
listeners={{
beforeRemove: () => {
navigation.setOptions({
headerLeft: () => (
<IconButton icon="menu" onPress={navigation.openDrawer} />
),
title: 'Workouts',
});
},
}}
/>
</Stack.Navigator> </Stack.Navigator>
); );
} }