diff --git a/ConfirmDialog.tsx b/ConfirmDialog.tsx index 7b68b97..f1d9781 100644 --- a/ConfirmDialog.tsx +++ b/ConfirmDialog.tsx @@ -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 ( setShow(false)}> @@ -22,7 +29,7 @@ export default function ConfirmDialog({ - + diff --git a/DrawerHeader.tsx b/DrawerHeader.tsx index 3f41d3a..238e671 100644 --- a/DrawerHeader.tsx +++ b/DrawerHeader.tsx @@ -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>() const dark = useDark() @@ -17,7 +23,7 @@ export default function DrawerHeader({name}: {name: keyof DrawerParamList}) { onPress={navigation.openDrawer} /> - + ) } diff --git a/DrawerMenu.tsx b/DrawerMenu.tsx index 8a11afb..8203900 100644 --- a/DrawerMenu.tsx +++ b/DrawerMenu.tsx @@ -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>() + const {navigate} = useNavigation>() 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' && ( + + )} + - 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? + )} ) diff --git a/EditSets.tsx b/EditSets.tsx new file mode 100644 index 0000000..8e14db5 --- /dev/null +++ b/EditSets.tsx @@ -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>() + const {ids} = params + const navigation = useNavigation() + const [settings, setSettings] = useState({} 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(null) + const repsRef = useRef(null) + const unitRef = useRef(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 = {} + 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 ( + <> + + + + repsRef.current?.focus()} + /> + + weightRef.current?.focus()} + selection={selection} + onSelectionChange={e => setSelection(e.nativeEvent.selection)} + autoFocus={!!name} + innerRef={repsRef} + /> + + + + {settings.showUnit && ( + + )} + + {settings.images && newImage && ( + setShowRemove(true)}> + + + )} + + Are you sure you want to remove the image? + + + {settings.images && !newImage && ( + + )} + + + + + ) +} diff --git a/HomePage.tsx b/HomePage.tsx index 891eaac..580fc7f 100644 --- a/HomePage.tsx +++ b/HomePage.tsx @@ -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}}> + ) } diff --git a/PlanItem.tsx b/PlanItem.tsx index 4b0c77a..4e35d61 100644 --- a/PlanItem.tsx +++ b/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() + const dark = useDark() const days = useMemo(() => item.days.split(','), [item.days]) const navigation = useNavigation>() @@ -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 ( ( - setShow(false)}> - - - - - - )} + style={{backgroundColor}} /> ) } diff --git a/PlanList.tsx b/PlanList.tsx index b20fd38..eb7c04c 100644 --- a/PlanList.tsx +++ b/PlanList.tsx @@ -17,6 +17,7 @@ import PlanItem from './PlanItem' export default function PlanList() { const [term, setTerm] = useState('') const [plans, setPlans] = useState() + const [ids, setIds] = useState([]) const navigation = useNavigation>() const refresh = useCallback(async (value: string) => { @@ -43,9 +44,9 @@ export default function PlanList() { const renderItem = useCallback( ({item}: {item: Plan}) => ( - refresh(term)} /> + ), - [refresh, term], + [ids], ) const onAdd = () => @@ -53,7 +54,7 @@ export default function PlanList() { return ( <> - + {plans?.length === 0 ? ( 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>() - 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 ( <> 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')} )} - setShowMenu(false)}> - - - - )} /> diff --git a/SetList.tsx b/SetList.tsx index eb67bbc..0955f27 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -24,6 +24,7 @@ export default function SetList() { const [term, setTerm] = useState('') const [end, setEnd] = useState(false) const [settings, setSettings] = useState() + const [ids, setIds] = useState([]) const navigation = useNavigation>() 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 ( <> - + {sets?.length === 0 ? (