diff --git a/EditFood.tsx b/EditFood.tsx new file mode 100644 index 0000000..faa34c1 --- /dev/null +++ b/EditFood.tsx @@ -0,0 +1,231 @@ +import { DateTimePickerAndroid } from '@react-native-community/datetimepicker' +import { + RouteProp, + useFocusEffect, + useNavigation, + useRoute, +} from '@react-navigation/native' +import { format } from 'date-fns' +import { useCallback, useRef, useState } from 'react' +import { NativeModules, TextInput, View } from 'react-native' +import DocumentPicker from 'react-native-document-picker' +import { Button, Card, TouchableRipple } from 'react-native-paper' +import AppInput from './AppInput' +import ConfirmDialog from './ConfirmDialog' +import { MARGIN, PADDING } from './constants' +import { getNow, setRepo, settingsRepo } from './db' +import GymSet from './gym-set' +import { HomePageParams } from './home-page-params' +import Settings from './settings' +import StackHeader from './StackHeader' +import { toast } from './toast' + +export default function EditFood() { + const { params } = useRoute>() + const { set } = params + const navigation = useNavigation() + const [settings, setSettings] = useState({} as Settings) + const [name, setName] = useState(set.name) + const [reps, setReps] = useState(set.reps?.toString()) + const [weight, setWeight] = useState(set.weight?.toString()) + const [newImage, setNewImage] = useState(set.image) + const [unit, setUnit] = useState(set.unit) + const [created, setCreated] = useState( + set.created ? new Date(set.created) : new Date(), + ) + const [showRemove, setShowRemove] = useState(false) + const [removeImage, setRemoveImage] = useState(false) + const weightRef = useRef(null) + const repsRef = useRef(null) + const unitRef = useRef(null) + + const [selection, setSelection] = useState({ + start: 0, + end: set.reps?.toString().length, + }) + + useFocusEffect( + useCallback(() => { + settingsRepo.findOne({ where: {} }).then(setSettings) + }, []), + ) + + const startTimer = useCallback( + async (value: string) => { + if (!settings.alarm) return + const first = await setRepo.findOne({ where: { name: value } }) + const milliseconds = (first?.minutes ?? 3) * 60 * 1000 + + (first?.seconds ?? 0) * 1000 + if (milliseconds) NativeModules.AlarmModule.timer(milliseconds) + }, + [settings], + ) + + const added = useCallback( + async (value: GymSet) => { + startTimer(value.name) + console.log(`${EditFood.name}.add`, { set: value }) + if (!settings.notify) return + if ( + value.weight > set.weight || + (value.reps > set.reps && value.weight === set.weight) + ) { + toast('Great work King! That\'s a new record.') + } + }, + [startTimer, set, settings], + ) + + const handleSubmit = async () => { + console.log(`${EditFood.name}.handleSubmit:`, { set, uri: newImage, name }) + if (!name) return + let image = newImage + if (!newImage && !removeImage) { + image = await setRepo.findOne({ where: { name } }).then((s) => s?.image) + } + + console.log(`${EditFood.name}.handleSubmit:`, { image }) + const now = await getNow() + const saved = await setRepo.save({ + id: set.id, + name, + created: created?.toISOString() || now, + reps: Number(reps), + weight: Number(weight), + unit, + image, + minutes: Number(set.minutes ?? 3), + seconds: Number(set.seconds ?? 30), + sets: set.sets ?? 3, + hidden: false, + }) + if (typeof set.id !== 'number') added(saved) + 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('') + setRemoveImage(true) + setShowRemove(false) + }, []) + + const pickDate = useCallback(() => { + DateTimePickerAndroid.open({ + value: created, + onChange: (_, date) => { + if (date === created) return + setCreated(date) + DateTimePickerAndroid.open({ + value: date, + onChange: (__, time) => setCreated(time), + mode: 'time', + }) + }, + mode: 'date', + }) + }, [created]) + + return ( + <> + + + + repsRef.current?.focus()} + /> + + weightRef.current?.focus()} + selection={selection} + onSelectionChange={(e) => setSelection(e.nativeEvent.selection)} + innerRef={repsRef} + /> + + + + {settings.showUnit && ( + + )} + + {typeof set.id === 'number' && settings.showDate && ( + + )} + + {settings.images && newImage && ( + setShowRemove(true)} + > + + + )} + + {settings.images && !newImage && ( + + )} + + + + + + Are you sure you want to remove the image? + + + ) +} diff --git a/FoodList.tsx b/FoodList.tsx new file mode 100644 index 0000000..99e1964 --- /dev/null +++ b/FoodList.tsx @@ -0,0 +1,162 @@ +import { + NavigationProp, + useFocusEffect, + useNavigation, +} from '@react-navigation/native' +import { useCallback, useState } from 'react' +import { FlatList } from 'react-native' +import { List } from 'react-native-paper' +import { Like } from 'typeorm' +import DrawerHeader from './DrawerHeader' +import { FoodPageParams } from './FoodPage' +import ListMenu from './ListMenu' +import Page from './Page' +import SetItem from './SetItem' +import { foodRepo, getNow, settingsRepo } from './db' +import Food, { defaultFood } from './food' +import GymSet from './gym-set' +import Settings from './settings' + +const limit = 15 + +export default function FoodList() { + const [foods, setFoods] = useState([]) + const [offset, setOffset] = useState(0) + 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) => { + const newSets = await foodRepo.find({ + where: { name: Like(`%${value.trim()}%`) }, + take: limit, + skip: 0, + order: { created: 'DESC' }, + }) + console.log(`${FoodList.name}.refresh:`, { value, limit }) + setFoods(newSets) + setOffset(0) + setEnd(false) + }, []) + + useFocusEffect( + useCallback(() => { + refresh(term) + settingsRepo.findOne({ where: {} }).then(setSettings) + }, [refresh, term]), + ) + + const renderItem = useCallback( + ({ item }: { item: GymSet }) => ( + refresh(term)} + ids={ids} + setIds={setIds} + /> + ), + [refresh, term, settings, ids], + ) + + const next = useCallback(async () => { + if (end) return + const newOffset = offset + limit + console.log(`${FoodList.name}.next:`, { offset, newOffset, term }) + const newSets = await foodRepo.find({ + where: { name: Like(`%${term}%`) }, + take: limit, + skip: newOffset, + order: { created: 'DESC' }, + }) + if (newSets.length === 0) return setEnd(true) + if (!foods) return + setFoods([...foods, ...newSets]) + if (newSets.length < limit) return setEnd(true) + setOffset(newOffset) + }, [term, end, offset, foods]) + + const onAdd = useCallback(async () => { + const now = await getNow() + let food = foods[0] + if (!food) food = { ...defaultFood } + food.created = now + delete food.id + navigation.navigate('EditFood', { food }) + }, [navigation, foods]) + + const search = useCallback( + (value: string) => { + setTerm(value) + refresh(value) + }, + [refresh], + ) + + const edit = useCallback(() => { + navigation.navigate('EditFood', { ids }) + setIds([]) + }, [ids, navigation]) + + const copy = useCallback(async () => { + const food = await foodRepo.findOne({ + where: { id: ids.pop() }, + }) + delete food.id + delete food.created + navigation.navigate('EditFood', { food }) + setIds([]) + }, [ids, navigation]) + + const clear = useCallback(() => { + setIds([]) + }, []) + + const remove = useCallback(async () => { + setIds([]) + await foodRepo.delete(ids.length > 0 ? ids : {}) + await refresh(term) + }, [ids, refresh, term]) + + const select = useCallback(() => { + setIds(foods.map((set) => set.id)) + }, [foods]) + + return ( + <> + 0 ? `${ids.length} selected` : 'Home'}> + + + + + {foods?.length === 0 + ? ( + + ) + : ( + settings && ( + + ) + )} + + + ) +} diff --git a/FoodPage.tsx b/FoodPage.tsx new file mode 100644 index 0000000..6fb32a5 --- /dev/null +++ b/FoodPage.tsx @@ -0,0 +1,24 @@ +import { createStackNavigator } from '@react-navigation/stack' +import FoodList from './FoodList' +import EditFood from './EditFood' +import Food from './food' + +export type FoodPageParams = { + Food: {} + EditFood: { + food: Food + } +} + +const Stack = createStackNavigator() + +export default function FoodPage() { + return ( + + + + + ) +} diff --git a/Routes.tsx b/Routes.tsx index 1498496..e1d049d 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -47,6 +47,11 @@ export default function Routes() { component={TimerPage} options={{ drawerIcon: () => }} /> + }} + />