Add ability to edit/delete multiple sets/plans
This commit is contained in:
parent
c3b14e901d
commit
2e347deb53
|
@ -6,13 +6,20 @@ export default function ConfirmDialog({
|
|||
onOk,
|
||||
show,
|
||||
setShow,
|
||||
onCancel,
|
||||
}: {
|
||||
title: string
|
||||
children: JSX.Element | JSX.Element[] | string
|
||||
onOk: () => void
|
||||
show: boolean
|
||||
setShow: (show: boolean) => void
|
||||
onCancel?: () => void
|
||||
}) {
|
||||
const cancel = () => {
|
||||
setShow(false)
|
||||
onCancel && onCancel()
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog visible={show} onDismiss={() => setShow(false)}>
|
||||
|
@ -22,7 +29,7 @@ export default function ConfirmDialog({
|
|||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button onPress={onOk}>OK</Button>
|
||||
<Button onPress={() => setShow(false)}>Cancel</Button>
|
||||
<Button onPress={cancel}>Cancel</Button>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
|
|
|
@ -5,7 +5,13 @@ import {DrawerParamList} from './drawer-param-list'
|
|||
import DrawerMenu from './DrawerMenu'
|
||||
import useDark from './use-dark'
|
||||
|
||||
export default function DrawerHeader({name}: {name: keyof DrawerParamList}) {
|
||||
export default function DrawerHeader({
|
||||
name,
|
||||
ids,
|
||||
}: {
|
||||
name: keyof DrawerParamList
|
||||
ids: number[]
|
||||
}) {
|
||||
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>()
|
||||
const dark = useDark()
|
||||
|
||||
|
@ -17,7 +23,7 @@ export default function DrawerHeader({name}: {name: keyof DrawerParamList}) {
|
|||
onPress={navigation.openDrawer}
|
||||
/>
|
||||
<Appbar.Content title={name} />
|
||||
<DrawerMenu name={name} />
|
||||
<DrawerMenu name={name} ids={ids} />
|
||||
</Appbar.Header>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,23 +4,34 @@ import {IconButton, Menu} from 'react-native-paper'
|
|||
import ConfirmDialog from './ConfirmDialog'
|
||||
import {planRepo, setRepo} from './db'
|
||||
import {DrawerParamList} from './drawer-param-list'
|
||||
import {toast} from './toast'
|
||||
import {HomePageParams} from './home-page-params'
|
||||
import useDark from './use-dark'
|
||||
|
||||
export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
|
||||
export default function DrawerMenu({
|
||||
name,
|
||||
ids,
|
||||
}: {
|
||||
name: keyof DrawerParamList
|
||||
ids: number[]
|
||||
}) {
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
const [showRemove, setShowRemove] = useState(false)
|
||||
const {reset} = useNavigation<NavigationProp<DrawerParamList>>()
|
||||
const {navigate} = useNavigation<NavigationProp<HomePageParams>>()
|
||||
const dark = useDark()
|
||||
|
||||
const remove = useCallback(async () => {
|
||||
setShowMenu(false)
|
||||
setShowRemove(false)
|
||||
if (name === 'Home') await setRepo.delete({})
|
||||
else if (name === 'Plans') await planRepo.delete({})
|
||||
toast('All data has been deleted.')
|
||||
if (name === 'Home') await setRepo.delete(ids.length > 0 ? ids : {})
|
||||
else if (name === 'Plans') await planRepo.delete(ids.length > 0 ? ids : {})
|
||||
reset({index: 0, routes: [{name}]})
|
||||
}, [reset, name])
|
||||
}, [reset, name, ids])
|
||||
|
||||
const edit = useCallback(() => {
|
||||
navigate('EditSets', {ids})
|
||||
setShowMenu(false)
|
||||
}, [ids, navigate])
|
||||
|
||||
if (name === 'Home' || name === 'Plans')
|
||||
return (
|
||||
|
@ -39,12 +50,22 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
|
|||
onPress={() => setShowRemove(true)}
|
||||
title="Delete"
|
||||
/>
|
||||
|
||||
{ids.length > 0 && name === 'Home' && (
|
||||
<Menu.Item icon="edit" title="Edit" onPress={edit} />
|
||||
)}
|
||||
|
||||
<ConfirmDialog
|
||||
title="Delete all data"
|
||||
show={showRemove}
|
||||
setShow={setShowRemove}
|
||||
onOk={remove}>
|
||||
This irreversibly deletes all data from the app. Are you sure?
|
||||
onOk={remove}
|
||||
onCancel={() => setShowMenu(false)}>
|
||||
{ids.length === 0 ? (
|
||||
<>This irreversibly deletes all data from the app. Are you sure?</>
|
||||
) : (
|
||||
<>This will delete {ids.length} records. Are you sure?</>
|
||||
)}
|
||||
</ConfirmDialog>
|
||||
</Menu>
|
||||
)
|
||||
|
|
151
EditSets.tsx
Normal file
151
EditSets.tsx
Normal file
|
@ -0,0 +1,151 @@
|
|||
import {
|
||||
RouteProp,
|
||||
useFocusEffect,
|
||||
useNavigation,
|
||||
useRoute,
|
||||
} from '@react-navigation/native'
|
||||
import {useCallback, useRef, useState} from 'react'
|
||||
import {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 {setRepo, settingsRepo} from './db'
|
||||
import GymSet from './gym-set'
|
||||
import {HomePageParams} from './home-page-params'
|
||||
import MassiveInput from './MassiveInput'
|
||||
import Settings from './settings'
|
||||
import StackHeader from './StackHeader'
|
||||
|
||||
export default function EditSets() {
|
||||
const {params} = useRoute<RouteProp<HomePageParams, 'EditSets'>>()
|
||||
const {ids} = params
|
||||
const navigation = useNavigation()
|
||||
const [settings, setSettings] = useState<Settings>({} as Settings)
|
||||
const [name, setName] = useState('')
|
||||
const [reps, setReps] = useState('')
|
||||
const [weight, setWeight] = useState('')
|
||||
const [newImage, setNewImage] = useState('')
|
||||
const [unit, setUnit] = useState('')
|
||||
const [showRemove, setShowRemove] = useState(false)
|
||||
const weightRef = useRef<TextInput>(null)
|
||||
const repsRef = useRef<TextInput>(null)
|
||||
const unitRef = useRef<TextInput>(null)
|
||||
|
||||
const [selection, setSelection] = useState({
|
||||
start: 0,
|
||||
end: 1,
|
||||
})
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
settingsRepo.findOne({where: {}}).then(setSettings)
|
||||
}, []),
|
||||
)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
console.log(`${EditSets.name}.handleSubmit:`, {uri: newImage, name})
|
||||
const update: Partial<GymSet> = {}
|
||||
if (name) update.name = name
|
||||
if (reps) update.reps = Number(reps)
|
||||
if (weight) update.weight = Number(weight)
|
||||
if (unit) update.unit = unit
|
||||
if (newImage) update.image = newImage
|
||||
await setRepo.update(ids, update)
|
||||
navigation.goBack()
|
||||
}
|
||||
|
||||
const changeImage = useCallback(async () => {
|
||||
const {fileCopyUri} = await DocumentPicker.pickSingle({
|
||||
type: DocumentPicker.types.images,
|
||||
copyTo: 'documentDirectory',
|
||||
})
|
||||
if (fileCopyUri) setNewImage(fileCopyUri)
|
||||
}, [])
|
||||
|
||||
const handleRemove = useCallback(async () => {
|
||||
setNewImage('')
|
||||
setShowRemove(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<StackHeader title={`Edit ${ids.length} sets`} />
|
||||
|
||||
<View style={{padding: PADDING, flex: 1}}>
|
||||
<MassiveInput
|
||||
label="Name"
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
autoCorrect={false}
|
||||
autoFocus={!name}
|
||||
onSubmitEditing={() => repsRef.current?.focus()}
|
||||
/>
|
||||
|
||||
<MassiveInput
|
||||
label="Reps"
|
||||
keyboardType="numeric"
|
||||
value={reps}
|
||||
onChangeText={setReps}
|
||||
onSubmitEditing={() => weightRef.current?.focus()}
|
||||
selection={selection}
|
||||
onSelectionChange={e => setSelection(e.nativeEvent.selection)}
|
||||
autoFocus={!!name}
|
||||
innerRef={repsRef}
|
||||
/>
|
||||
|
||||
<MassiveInput
|
||||
label="Weight"
|
||||
keyboardType="numeric"
|
||||
value={weight}
|
||||
onChangeText={setWeight}
|
||||
onSubmitEditing={handleSubmit}
|
||||
innerRef={weightRef}
|
||||
/>
|
||||
|
||||
{settings.showUnit && (
|
||||
<MassiveInput
|
||||
autoCapitalize="none"
|
||||
label="Unit"
|
||||
value={unit}
|
||||
onChangeText={setUnit}
|
||||
innerRef={unitRef}
|
||||
/>
|
||||
)}
|
||||
|
||||
{settings.images && newImage && (
|
||||
<TouchableRipple
|
||||
style={{marginBottom: MARGIN}}
|
||||
onPress={changeImage}
|
||||
onLongPress={() => setShowRemove(true)}>
|
||||
<Card.Cover source={{uri: newImage}} />
|
||||
</TouchableRipple>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
title="Remove image"
|
||||
onOk={handleRemove}
|
||||
show={showRemove}
|
||||
setShow={setShowRemove}>
|
||||
Are you sure you want to remove the image?
|
||||
</ConfirmDialog>
|
||||
|
||||
{settings.images && !newImage && (
|
||||
<Button
|
||||
style={{marginBottom: MARGIN}}
|
||||
onPress={changeImage}
|
||||
icon="add-photo-alternate">
|
||||
Image
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
icon="save"
|
||||
style={{margin: MARGIN}}
|
||||
onPress={handleSubmit}>
|
||||
Save
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import {createStackNavigator} from '@react-navigation/stack'
|
||||
import EditSet from './EditSet'
|
||||
import EditSets from './EditSets'
|
||||
import {HomePageParams} from './home-page-params'
|
||||
import SetList from './SetList'
|
||||
|
||||
|
@ -11,6 +12,7 @@ export default function HomePage() {
|
|||
screenOptions={{headerShown: false, animationEnabled: false}}>
|
||||
<Stack.Screen name="Sets" component={SetList} />
|
||||
<Stack.Screen name="EditSet" component={EditSet} />
|
||||
<Stack.Screen name="EditSets" component={EditSets} />
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
|
67
PlanItem.tsx
67
PlanItem.tsx
|
@ -4,25 +4,26 @@ import {
|
|||
useNavigation,
|
||||
} from '@react-navigation/native'
|
||||
import {useCallback, useMemo, useState} from 'react'
|
||||
import {GestureResponderEvent, Text} from 'react-native'
|
||||
import {Divider, List, Menu} from 'react-native-paper'
|
||||
import {Text} from 'react-native'
|
||||
import {List} from 'react-native-paper'
|
||||
import {getBestSet} from './best.service'
|
||||
import {planRepo} from './db'
|
||||
import {defaultSet} from './gym-set'
|
||||
import {Plan} from './plan'
|
||||
import {PlanPageParams} from './plan-page-params'
|
||||
import {DAYS} from './time'
|
||||
import useDark from './use-dark'
|
||||
|
||||
export default function PlanItem({
|
||||
item,
|
||||
onRemove,
|
||||
setIds,
|
||||
ids,
|
||||
}: {
|
||||
item: Plan
|
||||
onRemove: () => void
|
||||
ids: number[]
|
||||
setIds: (value: number[]) => void
|
||||
}) {
|
||||
const [show, setShow] = useState(false)
|
||||
const [anchor, setAnchor] = useState({x: 0, y: 0})
|
||||
const [today, setToday] = useState<string>()
|
||||
const dark = useDark()
|
||||
const days = useMemo(() => item.days.split(','), [item.days])
|
||||
const navigation = useNavigation<NavigationProp<PlanPageParams>>()
|
||||
|
||||
|
@ -33,34 +34,22 @@ export default function PlanItem({
|
|||
}, []),
|
||||
)
|
||||
|
||||
const remove = useCallback(async () => {
|
||||
if (typeof item.id === 'number') await planRepo.delete(item.id)
|
||||
setShow(false)
|
||||
onRemove()
|
||||
}, [setShow, item.id, onRemove])
|
||||
|
||||
const start = useCallback(async () => {
|
||||
console.log(`${PlanItem.name}.start:`, {item})
|
||||
setShow(false)
|
||||
const workout = item.workouts.split(',')[0]
|
||||
let first = await getBestSet(workout)
|
||||
if (!first) first = {...defaultSet, name: workout}
|
||||
delete first.id
|
||||
navigation.navigate('StartPlan', {plan: item, first})
|
||||
}, [item, navigation])
|
||||
if (ids.length === 0)
|
||||
return navigation.navigate('StartPlan', {plan: item, first})
|
||||
const removing = ids.find(id => id === item.id)
|
||||
if (removing) setIds(ids.filter(id => id !== item.id))
|
||||
else setIds([...ids, item.id])
|
||||
}, [ids, setIds, item, navigation])
|
||||
|
||||
const longPress = useCallback(
|
||||
(e: GestureResponderEvent) => {
|
||||
setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY})
|
||||
setShow(true)
|
||||
},
|
||||
[setAnchor, setShow],
|
||||
)
|
||||
|
||||
const edit = useCallback(() => {
|
||||
setShow(false)
|
||||
navigation.navigate('EditPlan', {plan: item})
|
||||
}, [navigation, item])
|
||||
const longPress = useCallback(() => {
|
||||
if (ids.length > 0) return
|
||||
setIds([item.id])
|
||||
}, [ids.length, item.id, setIds])
|
||||
|
||||
const title = useMemo(
|
||||
() =>
|
||||
|
@ -84,12 +73,11 @@ export default function PlanItem({
|
|||
[item.workouts],
|
||||
)
|
||||
|
||||
const copy = useCallback(() => {
|
||||
const plan: Plan = {...item}
|
||||
delete plan.id
|
||||
setShow(false)
|
||||
navigation.navigate('EditPlan', {plan})
|
||||
}, [navigation, item])
|
||||
const backgroundColor = useMemo(() => {
|
||||
if (!ids.includes(item.id)) return
|
||||
if (dark) return '#c2c2c2'
|
||||
return '#c2c2c2'
|
||||
}, [dark, ids, item.id])
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
|
@ -97,14 +85,7 @@ export default function PlanItem({
|
|||
title={title}
|
||||
description={description}
|
||||
onLongPress={longPress}
|
||||
right={() => (
|
||||
<Menu anchor={anchor} visible={show} onDismiss={() => setShow(false)}>
|
||||
<Menu.Item icon="edit" onPress={edit} title="Edit" />
|
||||
<Menu.Item icon="content-copy" onPress={copy} title="Copy" />
|
||||
<Divider />
|
||||
<Menu.Item icon="delete" onPress={remove} title="Delete" />
|
||||
</Menu>
|
||||
)}
|
||||
style={{backgroundColor}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import PlanItem from './PlanItem'
|
|||
export default function PlanList() {
|
||||
const [term, setTerm] = useState('')
|
||||
const [plans, setPlans] = useState<Plan[]>()
|
||||
const [ids, setIds] = useState<number[]>([])
|
||||
const navigation = useNavigation<NavigationProp<PlanPageParams>>()
|
||||
|
||||
const refresh = useCallback(async (value: string) => {
|
||||
|
@ -43,9 +44,9 @@ export default function PlanList() {
|
|||
|
||||
const renderItem = useCallback(
|
||||
({item}: {item: Plan}) => (
|
||||
<PlanItem item={item} key={item.id} onRemove={() => refresh(term)} />
|
||||
<PlanItem ids={ids} setIds={setIds} item={item} key={item.id} />
|
||||
),
|
||||
[refresh, term],
|
||||
[ids],
|
||||
)
|
||||
|
||||
const onAdd = () =>
|
||||
|
@ -53,7 +54,7 @@ export default function PlanList() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<DrawerHeader name="Plans" />
|
||||
<DrawerHeader name="Plans" ids={ids} />
|
||||
<Page onAdd={onAdd} term={term} search={search}>
|
||||
{plans?.length === 0 ? (
|
||||
<List.Item
|
||||
|
|
61
SetItem.tsx
61
SetItem.tsx
|
@ -1,57 +1,54 @@
|
|||
import {NavigationProp, useNavigation} from '@react-navigation/native'
|
||||
import {useCallback, useState} from 'react'
|
||||
import {GestureResponderEvent, Image} from 'react-native'
|
||||
import {Divider, List, Menu, Text} from 'react-native-paper'
|
||||
import {setRepo} from './db'
|
||||
import {format} from 'date-fns'
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {Image} from 'react-native'
|
||||
import {List, Text} from 'react-native-paper'
|
||||
import GymSet from './gym-set'
|
||||
import {HomePageParams} from './home-page-params'
|
||||
import Settings from './settings'
|
||||
import useDark from './use-dark'
|
||||
import {format} from 'date-fns'
|
||||
|
||||
export default function SetItem({
|
||||
item,
|
||||
onRemove,
|
||||
settings,
|
||||
ids,
|
||||
setIds,
|
||||
}: {
|
||||
item: GymSet
|
||||
onRemove: () => void
|
||||
settings: Settings
|
||||
ids: number[]
|
||||
setIds: (value: number[]) => void
|
||||
}) {
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
const [anchor, setAnchor] = useState({x: 0, y: 0})
|
||||
const dark = useDark()
|
||||
const navigation = useNavigation<NavigationProp<HomePageParams>>()
|
||||
|
||||
const remove = useCallback(async () => {
|
||||
console.log(`${SetItem.name}.remove:`, {id: item.id})
|
||||
if (typeof item.id === 'number') await setRepo.delete(item.id)
|
||||
setShowMenu(false)
|
||||
onRemove()
|
||||
}, [setShowMenu, onRemove, item.id])
|
||||
const longPress = useCallback(() => {
|
||||
if (ids.length > 0) return
|
||||
setIds([item.id])
|
||||
}, [ids.length, item.id, setIds])
|
||||
|
||||
const copy = useCallback(() => {
|
||||
const set: GymSet = {...item}
|
||||
delete set.id
|
||||
setShowMenu(false)
|
||||
navigation.navigate('EditSet', {set})
|
||||
}, [navigation, item])
|
||||
const press = useCallback(() => {
|
||||
if (ids.length === 0) return navigation.navigate('EditSet', {set: item})
|
||||
const removing = ids.find(id => id === item.id)
|
||||
if (removing) setIds(ids.filter(id => id !== item.id))
|
||||
else setIds([...ids, item.id])
|
||||
}, [ids, item, navigation, setIds])
|
||||
|
||||
const longPress = useCallback(
|
||||
(e: GestureResponderEvent) => {
|
||||
setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY})
|
||||
setShowMenu(true)
|
||||
},
|
||||
[setShowMenu, setAnchor],
|
||||
)
|
||||
const backgroundColor = useMemo(() => {
|
||||
if (!ids.includes(item.id)) return
|
||||
if (dark) return '#c2c2c2'
|
||||
return '#c2c2c2'
|
||||
}, [dark, ids, item.id])
|
||||
|
||||
return (
|
||||
<>
|
||||
<List.Item
|
||||
onPress={() => navigation.navigate('EditSet', {set: item})}
|
||||
onPress={press}
|
||||
title={item.name}
|
||||
description={`${item.reps} x ${item.weight}${item.unit || 'kg'}`}
|
||||
onLongPress={longPress}
|
||||
style={{backgroundColor}}
|
||||
left={() =>
|
||||
settings.images &&
|
||||
item.image && (
|
||||
|
@ -69,14 +66,6 @@ export default function SetItem({
|
|||
{format(new Date(item.created), settings.date || 'P')}
|
||||
</Text>
|
||||
)}
|
||||
<Menu
|
||||
anchor={anchor}
|
||||
visible={showMenu}
|
||||
onDismiss={() => setShowMenu(false)}>
|
||||
<Menu.Item icon="content-copy" onPress={copy} title="Copy" />
|
||||
<Divider />
|
||||
<Menu.Item icon="delete" onPress={remove} title="Delete" />
|
||||
</Menu>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -24,6 +24,7 @@ export default function SetList() {
|
|||
const [term, setTerm] = useState('')
|
||||
const [end, setEnd] = useState(false)
|
||||
const [settings, setSettings] = useState<Settings>()
|
||||
const [ids, setIds] = useState<number[]>([])
|
||||
const navigation = useNavigation<NavigationProp<HomePageParams>>()
|
||||
|
||||
const refresh = useCallback(async (value: string) => {
|
||||
|
@ -58,9 +59,11 @@ export default function SetList() {
|
|||
item={item}
|
||||
key={item.id}
|
||||
onRemove={() => refresh(term)}
|
||||
ids={ids}
|
||||
setIds={setIds}
|
||||
/>
|
||||
),
|
||||
[refresh, term, settings],
|
||||
[refresh, term, settings, ids],
|
||||
)
|
||||
|
||||
const next = useCallback(async () => {
|
||||
|
@ -101,7 +104,7 @@ export default function SetList() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<DrawerHeader name="Home" />
|
||||
<DrawerHeader name="Home" ids={ids} />
|
||||
<Page onAdd={onAdd} term={term} search={search}>
|
||||
{sets?.length === 0 ? (
|
||||
<List.Item
|
||||
|
|
|
@ -5,4 +5,7 @@ export type HomePageParams = {
|
|||
EditSet: {
|
||||
set: GymSet
|
||||
}
|
||||
EditSets: {
|
||||
ids: number[]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user