From 259d36d67f451485ed5045318b106d6db971e7af Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Sun, 4 Sep 2022 15:28:21 +1200 Subject: [PATCH] Move all database operations into db.ts --- BestList.tsx | 34 ++---- BestPage.tsx | 4 +- DrawerMenu.tsx | 38 +++--- EditPlan.tsx | 22 +--- EditSet.tsx | 25 ++-- EditWorkout.tsx | 34 ++---- PlanItem.tsx | 4 +- PlanList.tsx | 13 +-- SetForm.tsx | 8 +- SetItem.tsx | 4 +- SetList.tsx | 106 ++++------------- SettingsPage.tsx | 47 +++----- ViewBest.tsx | 72 ++---------- WorkoutItem.tsx | 15 +-- WorkoutList.tsx | 38 +++--- best.ts | 6 - db.ts | 294 ++++++++++++++++++++++++++++++++++++++++++++++- metrics.ts | 4 + periods.ts | 5 + plan.ts | 2 +- volume.ts | 6 + 21 files changed, 444 insertions(+), 337 deletions(-) delete mode 100644 best.ts create mode 100644 metrics.ts create mode 100644 periods.ts create mode 100644 volume.ts diff --git a/BestList.tsx b/BestList.tsx index a8dc320..a0db7ff 100644 --- a/BestList.tsx +++ b/BestList.tsx @@ -6,39 +6,21 @@ import { import React, {useCallback, useEffect, useState} from 'react'; import {FlatList, StyleSheet, View} from 'react-native'; import {List, Searchbar} from 'react-native-paper'; -import Best from './best'; import {BestPageParams} from './BestPage'; -import {db} from './db'; +import {getBestReps, getBestWeights} from './db'; +import Set from './set'; export default function BestList() { - const [bests, setBests] = useState([]); + const [bests, setBests] = useState([]); const [search, setSearch] = useState(''); const [refreshing, setRefresing] = useState(false); const navigation = useNavigation>(); const refresh = useCallback(async () => { - const bestWeight = ` - SELECT name, reps, unit, MAX(weight) AS weight - FROM sets - WHERE name LIKE ? AND NOT hidden - GROUP BY name; - `; - const bestReps = ` - SELECT name, MAX(reps) as reps, unit, weight - FROM sets - WHERE name = ? AND weight = ? AND NOT hidden - GROUP BY name; - `; - const [weight] = await db.executeSql(bestWeight, [`%${search}%`]); - if (!weight) return setBests([]); - let newBest: Best[] = []; - for (let i = 0; i < weight.rows.length; i++) { - const [reps] = await db.executeSql(bestReps, [ - weight.rows.item(i).name, - weight.rows.item(i).weight, - ]); - newBest = newBest.concat(reps.rows.raw()); - } + const weights = await getBestWeights(search); + let newBest: Set[] = []; + for (const set of weights) + newBest.push(...(await getBestReps(search, set.weight))); setBests(newBest); }, [search]); @@ -55,7 +37,7 @@ export default function BestList() { refresh(); }, [search, refresh]); - const renderItem = ({item}: {item: Best}) => ( + const renderItem = ({item}: {item: Set}) => ( (); export type BestPageParams = { BestList: {}; ViewBest: { - best: Best; + best: Set; }; }; diff --git a/DrawerMenu.tsx b/DrawerMenu.tsx index ae52603..ad8f9da 100644 --- a/DrawerMenu.tsx +++ b/DrawerMenu.tsx @@ -5,9 +5,15 @@ import {FileSystem} from 'react-native-file-access'; import {Divider, IconButton, Menu} from 'react-native-paper'; import {DrawerParamList, SnackbarContext} from './App'; import ConfirmDialog from './ConfirmDialog'; -import {db} from './db'; +import { + addPlans, + addSets, + deletePlans, + deleteSets, + getAllPlans, + getAllSets, +} from './db'; import {Plan} from './plan'; -import Set from './set'; import {write} from './write'; const setFields = 'id,name,reps,weight,created,unit,hidden'; @@ -20,9 +26,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { const {reset} = useNavigation>(); const exportSets = useCallback(async () => { - const [result] = await db.executeSql('SELECT * FROM sets'); - if (result.rows.length === 0) return; - const sets: Set[] = result.rows.raw(); + const sets = await getAllSets(); const data = [setFields] .concat( sets.map( @@ -36,13 +40,11 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { }, []); const exportPlans = useCallback(async () => { - const [result] = await db.executeSql('SELECT * FROM plans'); - if (result.rows.length === 0) return; - const sets: Plan[] = result.rows.raw(); + const plans: Plan[] = await getAllPlans(); const data = [planFields] - .concat(sets.map(set => `"${set.id}","${set.days}","${set.workouts}"`)) + .concat(plans.map(set => `"${set.id}","${set.days}","${set.workouts}"`)) .join('\n'); - console.log(`${DrawerMenu.name}.exportPlans`, {length: sets.length}); + console.log(`${DrawerMenu.name}.exportPlans`, {length: plans.length}); await write('plans.csv', data); }, []); @@ -66,12 +68,10 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { return `('${cells[1]}',${cells[2]},${cells[3]},'${cells[4]}','${cells[5]}',${cells[6]})`; }) .join(','); - await db.executeSql( - `INSERT INTO sets(name,reps,weight,created,unit,hidden) VALUES ${values}`, - ); + await addSets(values); toast('Data imported.', 3000); reset({index: 0, routes: [{name}]}); - }, [db, reset, name, toast]); + }, [reset, name, toast]); const uploadPlans = useCallback(async () => { const result = await DocumentPicker.pickSingle(); @@ -88,9 +88,9 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { return `('${cells[1]}','${cells[2]}')`; }) .join(','); - await db.executeSql(`INSERT INTO plans(days,workouts) VALUES ${values}`); + await addPlans(values); toast('Data imported.', 3000); - }, [db, toast]); + }, [toast]); const upload = useCallback(async () => { setShowMenu(false); @@ -102,11 +102,11 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { const remove = useCallback(async () => { setShowMenu(false); setShowRemove(false); - if (name === 'Home') await db.executeSql(`DELETE FROM sets`); - else if (name === 'Plans') await db.executeSql(`DELETE FROM plans`); + if (name === 'Home') await deleteSets(); + else if (name === 'Plans') await deletePlans(); toast('All data has been deleted.', 4000); reset({index: 0, routes: [{name}]}); - }, [db, reset, name, toast]); + }, [reset, name, toast]); if (name === 'Home' || name === 'Plans') return ( diff --git a/EditPlan.tsx b/EditPlan.tsx index 85cc697..7ed1b03 100644 --- a/EditPlan.tsx +++ b/EditPlan.tsx @@ -9,7 +9,7 @@ import React, {useCallback, useEffect, useState} from 'react'; import {ScrollView, StyleSheet, Text, View} from 'react-native'; import {Button, IconButton} from 'react-native-paper'; import {DrawerParamList} from './App'; -import {db} from './db'; +import {addPlan, getNames, setPlan} from './db'; import MassiveSwitch from './MassiveSwitch'; import {PlanPageParams} from './PlanPage'; import {DAYS} from './time'; @@ -36,14 +36,7 @@ export default function EditPlan() { ); useEffect(() => { - const refresh = async () => { - const [namesResult] = await db.executeSql( - 'SELECT DISTINCT name FROM sets', - ); - if (!namesResult.rows.length) return setNames([]); - setNames(namesResult.rows.raw().map(({name}) => name)); - }; - refresh(); + getNames().then(setNames); }, []); const save = useCallback(async () => { @@ -51,16 +44,9 @@ export default function EditPlan() { if (!days || !workouts) return; const newWorkouts = workouts.filter(workout => workout).join(','); const newDays = days.filter(day => day).join(','); - if (!params.plan.id) - await db.executeSql(`INSERT INTO plans(days, workouts) VALUES (?, ?)`, [ - newDays, - newWorkouts, - ]); + if (!params.plan.id) await addPlan({days: newDays, workouts: newWorkouts}); else - await db.executeSql( - `UPDATE plans SET days = ?, workouts = ? WHERE id = ?`, - [newDays, newWorkouts, params.plan.id], - ); + await setPlan({days: newDays, workouts: newWorkouts, id: params.plan.id}); navigation.goBack(); }, [days, workouts, params, navigation]); diff --git a/EditSet.tsx b/EditSet.tsx index 0dae118..e4f56ae 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -8,11 +8,10 @@ import React, {useCallback, useContext} from 'react'; import {NativeModules, View} from 'react-native'; import {IconButton} from 'react-native-paper'; import {SnackbarContext} from './App'; -import {db} from './db'; +import {addSet, getSettings, setSet} from './db'; import {HomePageParams} from './HomePage'; import Set from './set'; import SetForm from './SetForm'; -import Settings from './settings'; export default function EditSet() { const {params} = useRoute>(); @@ -32,8 +31,7 @@ export default function EditSet() { ); const startTimer = useCallback(async () => { - const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`); - const settings: Settings = result.rows.item(0); + const settings = await getSettings(); if (!settings.alarm) return; const milliseconds = settings.minutes * 60 * 1000 + settings.seconds * 1000; NativeModules.AlarmModule.timer( @@ -46,10 +44,7 @@ export default function EditSet() { const update = useCallback( async (set: Set) => { console.log(`${EditSet.name}.update`, set); - await db.executeSql( - `UPDATE sets SET name = ?, reps = ?, weight = ?, unit = ? WHERE id = ?`, - [set.name, set.reps, set.weight, set.unit, set.id], - ); + await setSet(set); navigation.goBack(); }, [navigation], @@ -57,19 +52,13 @@ export default function EditSet() { const add = useCallback( async (set: Set) => { - const {name, reps, weight, unit, image} = set; - const insert = ` - INSERT INTO sets(name, reps, weight, created, unit, image) - VALUES (?,?,?,strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'),?, ?) - `; startTimer(); - await db.executeSql(insert, [name, reps, weight, unit, image]); - const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`); - const settings: Settings = result.rows.item(0); + await addSet(set); + const settings = await getSettings(); if (settings.notify === 0) return navigation.goBack(); if ( - weight > params.set.weight || - (reps > params.set.reps && weight === params.set.weight) + set.weight > params.set.weight || + (set.reps > params.set.reps && set.weight === params.set.weight) ) toast("Great work King, that's a new record!", 3000); navigation.goBack(); diff --git a/EditWorkout.tsx b/EditWorkout.tsx index 6b38911..a623ff9 100644 --- a/EditWorkout.tsx +++ b/EditWorkout.tsx @@ -9,13 +9,14 @@ import {Image, ScrollView, View} from 'react-native'; import DocumentPicker from 'react-native-document-picker'; import {Button, IconButton} from 'react-native-paper'; import {set} from 'react-native-reanimated'; -import {db} from './db'; +import {addSet, getSets, setSetImage, setSetName, setWorkouts} from './db'; import MassiveInput from './MassiveInput'; +import Set from './set'; import {WorkoutsPageParams} from './WorkoutsPage'; export default function EditWorkout() { const [name, setName] = useState(''); - const [uri, setUri] = useState(''); + const [uri, setUri] = useState(); const {params} = useRoute>(); const navigation = useNavigation(); @@ -28,39 +29,24 @@ export default function EditWorkout() { headerRight: null, title: params.value.name ? params.value.name : 'New workout', }); - db.executeSql(`SELECT image FROM sets WHERE name = ? LIMIT 1`, [ - params.value.name, - ]).then(([result]) => setUri(result.rows.item(0)?.image)); + getSets({search: params.value.name, limit: 1, offset: 0}).then(sets => + setUri(sets[0]?.image), + ); }, [navigation, params.value.name]), ); const update = useCallback(async () => { console.log(`${EditWorkout.name}.update`, set); if (name) { - await db.executeSql(`UPDATE sets SET name = ? WHERE name = ?`, [ - name, - params.value.name, - ]); - await db.executeSql( - `UPDATE plans SET workouts = REPLACE(workouts, ?, ?) - WHERE workouts LIKE ?`, - [params.value.name, name, `%${params.value.name}%`], - ); + await setSetName(params.value.name, name); + await setWorkouts(params.value.name, name); } - if (uri) - await db.executeSql(`UPDATE sets SET image = ? WHERE name = ?`, [ - uri, - params.value.name, - ]); + if (uri) await setSetImage(params.value.name, uri); navigation.goBack(); }, [navigation, params.value.name, name, uri]); const add = useCallback(async () => { - const insert = ` - INSERT INTO sets(name, reps, weight, created, unit, hidden) - VALUES (?,0,0,strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'),'kg',true) - `; - await db.executeSql(insert, [name]); + await addSet({name, reps: 0, weight: 0, hidden: true} as Set); navigation.goBack(); }, [navigation, name]); diff --git a/PlanItem.tsx b/PlanItem.tsx index 78ff03e..4a9dea7 100644 --- a/PlanItem.tsx +++ b/PlanItem.tsx @@ -2,7 +2,7 @@ import {NavigationProp, useNavigation} from '@react-navigation/native'; import React, {useCallback, useState} from 'react'; import {GestureResponderEvent} from 'react-native'; import {List, Menu} from 'react-native-paper'; -import {db} from './db'; +import {deletePlan} from './db'; import {Plan} from './plan'; import {PlanPageParams} from './PlanPage'; @@ -18,7 +18,7 @@ export default function PlanItem({ const navigation = useNavigation>(); const remove = useCallback(async () => { - await db.executeSql(`DELETE FROM plans WHERE id = ?`, [item.id]); + if (item.id) await deletePlan(item.id); setShow(false); onRemove(); }, [setShow, item.id, onRemove]); diff --git a/PlanList.tsx b/PlanList.tsx index 0178565..1ba6a28 100644 --- a/PlanList.tsx +++ b/PlanList.tsx @@ -6,7 +6,7 @@ import { import React, {useCallback, useEffect, useState} from 'react'; import {FlatList, StyleSheet, View} from 'react-native'; import {List, Searchbar} from 'react-native-paper'; -import {db} from './db'; +import {getPlans} from './db'; import DrawerMenu from './DrawerMenu'; import MassiveFab from './MassiveFab'; import {Plan} from './plan'; @@ -20,14 +20,7 @@ export default function PlanList() { const navigation = useNavigation>(); const refresh = useCallback(async () => { - const selectPlans = ` - SELECT * from plans - WHERE days LIKE ? OR workouts LIKE ? - `; - const getPlans = ({s}: {s: string}) => - db.executeSql(selectPlans, [`%${s}%`, `%${s}%`]); - const [plansResult] = await getPlans({s: search}); - setPlans(plansResult.rows.raw()); + getPlans(search).then(setPlans); }, [search]); useFocusEffect( @@ -57,7 +50,7 @@ export default function PlanList() { style={{height: '100%'}} data={plans} renderItem={renderItem} - keyExtractor={set => set.id.toString()} + keyExtractor={set => set.id?.toString() || ''} refreshing={refreshing} onRefresh={() => { setRefresing(true); diff --git a/SetForm.tsx b/SetForm.tsx index 03e1bca..bfbb13a 100644 --- a/SetForm.tsx +++ b/SetForm.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useRef, useState} from 'react'; import {ScrollView} from 'react-native'; import {Button, Text} from 'react-native-paper'; -import {db} from './db'; +import {getSets} from './db'; import MassiveInput from './MassiveInput'; import Set from './set'; @@ -29,9 +29,9 @@ export default function SetForm({ useEffect(() => { console.log('SetForm.useEffect:', {uri, name: set.name}); if (!uri) - db.executeSql(`SELECT image FROM sets WHERE name = ? LIMIT 1`, [ - set.name, - ]).then(([result]) => setUri(result.rows.item(0)?.image)); + getSets({search: set.name, limit: 1, offset: 0}).then(sets => + setUri(sets[0]?.image), + ); }, [uri, set.name]); const handleSubmit = () => { diff --git a/SetItem.tsx b/SetItem.tsx index 5048e04..52e2fba 100644 --- a/SetItem.tsx +++ b/SetItem.tsx @@ -2,7 +2,7 @@ import {NavigationProp, useNavigation} from '@react-navigation/native'; import React, {useCallback, useState} from 'react'; import {GestureResponderEvent, Image} from 'react-native'; import {Divider, List, Menu, Text} from 'react-native-paper'; -import {db} from './db'; +import {deleteSet} from './db'; import {HomePageParams} from './HomePage'; import Set from './set'; @@ -26,7 +26,7 @@ export default function SetItem({ const navigation = useNavigation>(); const remove = useCallback(async () => { - await db.executeSql(`DELETE FROM sets WHERE id = ?`, [item.id]); + await deleteSet(item.id); setShowMenu(false); onRemove(); }, [setShowMenu, onRemove, item.id]); diff --git a/SetList.tsx b/SetList.tsx index d76d0b2..8d580f1 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -6,24 +6,21 @@ import { import React, {useCallback, useEffect, useState} from 'react'; import {FlatList, StyleSheet, View} from 'react-native'; import {List, Searchbar} from 'react-native-paper'; -import {db} from './db'; +import { + defaultSet, + getBest, + getSets, + getSettings, + getTodaysPlan, + getTodaysSets, +} from './db'; import DrawerMenu from './DrawerMenu'; import {HomePageParams} from './HomePage'; import MassiveFab from './MassiveFab'; -import {Plan} from './plan'; import Set from './set'; import SetItem from './SetItem'; -import Settings from './settings'; -import {DAYS} from './time'; const limit = 15; -const defaultSet = { - name: '', - id: 0, - reps: 10, - weight: 20, - unit: 'kg', -}; export default function SetList() { const [sets, setSets] = useState(); @@ -37,21 +34,13 @@ export default function SetList() { const [images, setImages] = useState(true); const navigation = useNavigation>(); - const selectSets = ` - SELECT * from sets - WHERE name LIKE ? AND NOT hidden - ORDER BY created DESC - LIMIT ? OFFSET ? - `; - const refresh = useCallback(async () => { - const [result] = await db.executeSql(selectSets, [`%${search}%`, limit, 0]); - if (!result) return setSets([]); - console.log(`${SetList.name}.refresh:`, {search, limit}); - setSets(result.rows.raw()); + const newSets = await getSets({search: `%${search}%`, limit, offset: 0}); + if (newSets.length === 0) return setSets([]); + setSets(newSets); setOffset(0); setEnd(false); - }, [search, selectSets]); + }, [search]); const refreshLoader = useCallback(async () => { setRefreshing(true); @@ -62,52 +51,8 @@ export default function SetList() { refresh(); }, [search, refresh]); - const getTodaysPlan = useCallback(async (): Promise => { - const today = DAYS[new Date().getDay()]; - const [result] = await db.executeSql( - `SELECT * FROM plans WHERE days LIKE ? LIMIT 1`, - [`%${today}%`], - ); - return result.rows.raw(); - }, [db]); - - const getTodaysSets = useCallback(async (): Promise => { - const today = new Date().toISOString().split('T')[0]; - const [result] = await db.executeSql( - `SELECT * FROM sets WHERE created LIKE ? ORDER BY created DESC`, - [`${today}%`], - ); - return result.rows.raw(); - }, [db]); - - const getBest = useCallback( - async (query: string): Promise => { - const bestWeight = ` - SELECT name, reps, unit, MAX(weight) AS weight - FROM sets - WHERE name = ? AND NOT hidden - GROUP BY name; - `; - const bestReps = ` - SELECT name, MAX(reps) as reps, unit, weight - FROM sets - WHERE name = ? AND weight = ? AND NOT hidden - GROUP BY name; - `; - const [weightResult] = await db.executeSql(bestWeight, [query]); - if (!weightResult.rows.length) return {...defaultSet}; - const [repsResult] = await db.executeSql(bestReps, [ - query, - weightResult.rows.item(0).weight, - ]); - return repsResult.rows.item(0); - }, - [db], - ); - const predict = useCallback(async () => { - const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`); - const settings: Settings = result.rows.item(0); + const settings = await getSettings(); if (!settings.predict) return; const todaysPlan = await getTodaysPlan(); console.log(`${SetList.name}.predict:`, {todaysPlan}); @@ -130,7 +75,7 @@ export default function SetList() { console.log(`${SetList.name}.predict:`, {best}); setSet({...best}); setWorkouts(todaysWorkouts); - }, [getTodaysSets, getTodaysPlan, getBest, db]); + }, []); useFocusEffect( useCallback(() => { @@ -139,11 +84,8 @@ export default function SetList() { navigation.getParent()?.setOptions({ headerRight: () => , }); - db.executeSql('SELECT * FROM settings LIMIT 1').then(([result]) => { - const settings: Settings = result.rows.item(0); - setImages(!!settings.images); - }); - }, [refresh, predict, navigation, db]), + getSettings().then(settings => setImages(!!settings.images)); + }, [refresh, predict, navigation]), ); const renderItem = useCallback( @@ -171,15 +113,17 @@ export default function SetList() { newOffset, search, }); - const [result] = await db - .executeSql(selectSets, [`%${search}%`, limit, newOffset]) - .finally(() => setRefreshing(false)); - if (result.rows.length === 0) return setEnd(true); + const newSets = await getSets({ + search: `%${search}%`, + limit, + offset: newOffset, + }); + if (newSets.length === 0) return setEnd(true); if (!sets) return; - setSets([...sets, ...result.rows.raw()]); - if (result.rows.length < limit) return setEnd(true); + setSets([...sets, ...newSets]); + if (newSets.length < limit) return setEnd(true); setOffset(newOffset); - }, [search, end, offset, sets, db, selectSets]); + }, [search, end, offset, sets]); const onAdd = useCallback(async () => { navigation.navigate('EditSet', { diff --git a/SettingsPage.tsx b/SettingsPage.tsx index e586567..e30d02f 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -10,15 +10,14 @@ import DocumentPicker from 'react-native-document-picker'; import {Button, Searchbar, Text} from 'react-native-paper'; import {SnackbarContext} from './App'; import ConfirmDialog from './ConfirmDialog'; -import {db} from './db'; +import {getSettings, setSettings} from './db'; import MassiveInput from './MassiveInput'; import MassiveSwitch from './MassiveSwitch'; -import Settings from './settings'; export default function SettingsPage() { const [vibrate, setVibrate] = useState(true); const [minutes, setMinutes] = useState(''); - const [maxSets, setMaxSets] = useState('3'); + const [sets, setMaxSets] = useState('3'); const [seconds, setSeconds] = useState(''); const [alarm, setAlarm] = useState(false); const [predict, setPredict] = useState(false); @@ -31,8 +30,7 @@ export default function SettingsPage() { const {toast} = useContext(SnackbarContext); const refresh = useCallback(async () => { - const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`); - const settings: Settings = result.rows.item(0); + const settings = await getSettings(); console.log('SettingsPage.refresh:', {settings}); setMinutes(settings.minutes.toString()); setSeconds(settings.seconds.toString()); @@ -51,31 +49,18 @@ export default function SettingsPage() { }, [refresh]); useEffect(() => { - db.executeSql( - `UPDATE settings SET vibrate=?,minutes=?,sets=?,seconds=?,alarm=?,predict=?,sound=?,notify=?,images=?`, - [ - vibrate, - minutes, - maxSets, - seconds, - alarm, - predict, - sound, - notify, - images, - ], - ); - }, [ - vibrate, - minutes, - maxSets, - seconds, - alarm, - predict, - sound, - notify, - images, - ]); + setSettings({ + vibrate: +vibrate, + minutes: +minutes, + seconds: +seconds, + alarm: +alarm, + predict: +predict, + sound, + notify: +notify, + images: +images, + sets: +sets, + }); + }, [vibrate, minutes, sets, seconds, alarm, predict, sound, notify, images]); const changeAlarmEnabled = useCallback( (enabled: boolean) => { @@ -124,7 +109,7 @@ export default function SettingsPage() { element: ( { setMaxSets(value); diff --git a/ViewBest.tsx b/ViewBest.tsx index 38783b5..3c5efb4 100644 --- a/ViewBest.tsx +++ b/ViewBest.tsx @@ -12,27 +12,12 @@ import Share from 'react-native-share'; import ViewShot from 'react-native-view-shot'; import {BestPageParams} from './BestPage'; import Chart from './Chart'; -import {db} from './db'; +import {getVolumes, getWeights} from './db'; +import {Metrics} from './metrics'; +import {Periods} from './periods'; import Set from './set'; import {formatMonth} from './time'; - -interface Volume { - name: string; - created: string; - value: number; - unit: string; -} - -enum Metrics { - Weight = 'Best weight per day', - Volume = 'Volume per day', -} - -enum Periods { - Weekly = 'This week', - Monthly = 'This month', - Yearly = 'This year', -} +import Volume from './volume'; export default function ViewBest() { const {params} = useRoute>(); @@ -72,51 +57,14 @@ export default function ViewBest() { }, [navigation, params.best]), ); - const refreshWeight = useCallback(async () => { - const select = ` - SELECT max(weight) AS weight, - STRFTIME('%Y-%m-%d', created) as created, unit - FROM sets - WHERE name = ? AND NOT hidden - AND DATE(created) >= DATE('now', 'weekday 0', ?) - GROUP BY name, STRFTIME('%Y-%m-%d', created) - `; - let difference = '-7 days'; - if (period === Periods.Monthly) difference = '-1 months'; - else if (period === Periods.Yearly) difference = '-1 years'; - const [result] = await db.executeSql(select, [ - params.best.name, - difference, - ]); - if (result.rows.length === 0) return; - setWeights(result.rows.raw()); - }, [params.best.name, period]); - - const refreshVolume = useCallback(async () => { - const select = ` - SELECT sum(weight * reps) AS value, - STRFTIME('%Y-%m-%d', created) as created, unit - FROM sets - WHERE name = ? AND NOT hidden - AND DATE(created) >= DATE('now', 'weekday 0', ?) - GROUP BY name, STRFTIME('%Y-%m-%d', created) - `; - let difference = '-7 days'; - if (period === Periods.Monthly) difference = '-1 months'; - else if (period === Periods.Yearly) difference = '-1 years'; - const [result] = await db.executeSql(select, [ - params.best.name, - difference, - ]); - if (result.rows.length === 0) return; - setVolumes(result.rows.raw()); - }, [params.best.name, period]); - useEffect(() => { - if (metric === Metrics.Weight) refreshWeight(); - else if (metric === Metrics.Volume) refreshVolume(); + if (metric === Metrics.Weight) + getWeights(params.best.name, period).then(setWeights); + else if (metric === Metrics.Volume) + getVolumes(params.best.name, period).then(setVolumes); + console.log(`${ViewBest.name}.useEffect`, {metric, period}); - }, [params.best.name, metric, period, refreshVolume, refreshWeight]); + }, [params.best.name, metric, period]); return ( diff --git a/WorkoutItem.tsx b/WorkoutItem.tsx index 0148969..70ab0dc 100644 --- a/WorkoutItem.tsx +++ b/WorkoutItem.tsx @@ -3,7 +3,7 @@ import React, {useCallback, useEffect, useState} from 'react'; import {GestureResponderEvent, Image} from 'react-native'; import {List, Menu, Text} from 'react-native-paper'; import ConfirmDialog from './ConfirmDialog'; -import {db} from './db'; +import {deleteSetsBy, getSets} from './db'; import Workout from './workout'; import {WorkoutsPageParams} from './WorkoutsPage'; @@ -17,20 +17,17 @@ export default function WorkoutItem({ const [showMenu, setShowMenu] = useState(false); const [anchor, setAnchor] = useState({x: 0, y: 0}); const [showRemove, setShowRemove] = useState(''); - const [uri, setUri] = useState(''); + const [uri, setUri] = useState(); const navigation = useNavigation>(); useEffect(() => { - db.executeSql(`SELECT image FROM sets WHERE name = ? LIMIT 1`, [ - item.name, - ]).then(([result]) => { - setUri(result.rows.item(0)?.image); - console.log(WorkoutItem.name, item.name, result.rows.item(0)?.image); - }); + getSets({search: item.name, limit: 1, offset: 0}).then(sets => + setUri(sets[0]?.image), + ); }, [item.name]); const remove = useCallback(async () => { - await db.executeSql(`DELETE FROM sets WHERE name = ?`, [item.name]); + await deleteSetsBy(item.name); setShowMenu(false); onRemoved(); }, [setShowMenu, onRemoved, item.name]); diff --git a/WorkoutList.tsx b/WorkoutList.tsx index 33a13f9..513ab82 100644 --- a/WorkoutList.tsx +++ b/WorkoutList.tsx @@ -6,7 +6,7 @@ import { import React, {useCallback, useEffect, useState} from 'react'; import {FlatList, StyleSheet, View} from 'react-native'; import {List, Searchbar} from 'react-native-paper'; -import {db} from './db'; +import {getWorkouts} from './db'; import MassiveFab from './MassiveFab'; import SetList from './SetList'; import Workout from './workout'; @@ -23,22 +23,16 @@ export default function WorkoutList() { const [end, setEnd] = useState(false); const navigation = useNavigation>(); - const select = ` - SELECT DISTINCT sets.name - FROM sets - WHERE sets.name LIKE ? - ORDER BY sets.name - LIMIT ? OFFSET ? - `; - const refresh = useCallback(async () => { - const [result] = await db.executeSql(select, [`%${search}%`, limit, 0]); - if (!result) return setWorkouts([]); - console.log(`${WorkoutList.name}.refresh:`, {search, limit}); - setWorkouts(result.rows.raw()); + const newWorkouts = await getWorkouts({ + search: `%${search}%`, + limit, + offset: 0, + }); + setWorkouts(newWorkouts); setOffset(0); setEnd(false); - }, [search, select]); + }, [search]); const refreshLoader = useCallback(async () => { setRefreshing(true); @@ -72,15 +66,17 @@ export default function WorkoutList() { newOffset, search, }); - const [result] = await db - .executeSql(select, [`%${search}%`, limit, newOffset]) - .finally(() => setRefreshing(false)); - if (result.rows.length === 0) return setEnd(true); + const newWorkouts = await getWorkouts({ + search: `%${search}%`, + limit, + offset: newOffset, + }).finally(() => setRefreshing(false)); + if (newWorkouts.length === 0) return setEnd(true); if (!workouts) return; - setWorkouts([...workouts, ...result.rows.raw()]); - if (result.rows.length < limit) return setEnd(true); + setWorkouts([...workouts, ...newWorkouts]); + if (newWorkouts.length < limit) return setEnd(true); setOffset(newOffset); - }, [search, end, offset, workouts, select]); + }, [search, end, offset, workouts]); const onAdd = useCallback(async () => { navigation.navigate('EditWorkout', { diff --git a/best.ts b/best.ts deleted file mode 100644 index 1014eb0..0000000 --- a/best.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface Best { - name: string; - reps: number; - weight: number; - unit: string; -} diff --git a/db.ts b/db.ts index dc04ce6..d8860de 100644 --- a/db.ts +++ b/db.ts @@ -3,6 +3,13 @@ import { openDatabase, SQLiteDatabase, } from 'react-native-sqlite-storage'; +import {Periods} from './periods'; +import {Plan} from './plan'; +import Set from './set'; +import Settings from './settings'; +import {DAYS} from './time'; +import Volume from './volume'; +import Workout from './workout'; enablePromise(true); @@ -86,5 +93,290 @@ export const migrations = async () => { await db.executeSql(addImages).catch(() => null); const [result] = await db.executeSql(selectSettings); if (result.rows.length === 0) await db.executeSql(insertSettings); - return db; +}; + +export const getSettings = async () => { + const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`); + const settings: Settings = result.rows.item(0); + return settings; +}; + +export const setSettings = async (value: Settings) => { + const update = ` + UPDATE settings + SET vibrate=?,minutes=?,sets=?,seconds=?,alarm=?, + predict=?,sound=?,notify=?,images=? + `; + return db.executeSql(update, [ + value.vibrate, + value.minutes, + value.sets, + value.seconds, + value.alarm, + value.predict, + value.sound, + value.notify, + value.images, + ]); +}; + +export const setSet = async (value: Set) => { + const update = ` + UPDATE sets + SET name = ?, reps = ?, weight = ?, unit = ? + WHERE id = ? + `; + return db.executeSql(update, [ + value.name, + value.reps, + value.weight, + value.unit, + value.id, + ]); +}; + +export const addSets = async (values: string) => { + const insert = ` + INSERT INTO sets(name,reps,weight,created,unit,hidden) + VALUES ${values} + `; + return db.executeSql(insert); +}; + +export const addSet = async (value: Set) => { + const insert = ` + INSERT INTO sets(name, reps, weight, created, unit, image) + VALUES (?,?,?,strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'),?, ?) + `; + const {name, reps, weight, unit, image} = value; + return db.executeSql(insert, [name, reps, weight, unit, image]); +}; + +export const setWorkouts = async (oldName: string, newName: string) => { + const update = ` + UPDATE plans SET workouts = REPLACE(workouts, ?, ?) + WHERE workouts LIKE ? + `; + return db.executeSql(update, [oldName, newName, `%${oldName}%`]); +}; + +export const setPlan = async (value: Plan) => { + const update = `UPDATE plans SET days = ?, workouts = ? WHERE id = ?`; + return db.executeSql(update, [value.days, value.workouts, value.id]); +}; + +export const addPlan = async (value: Plan) => { + const insert = `INSERT INTO plans(days, workouts) VALUES (?, ?)`; + return db.executeSql(insert, [value.days, value.workouts]); +}; + +export const addPlans = async (values: string) => { + const insert = ` + INSERT INTO plans(days,workouts) VALUES ${values} + `; + return db.executeSql(insert); +}; + +export const deletePlans = async () => { + return db.executeSql(`DELETE FROM plans`); +}; + +export const deleteSets = async () => { + return db.executeSql(`DELETE FROM sets`); +}; + +export const deletePlan = async (id: number) => { + return db.executeSql(`DELETE FROM plans WHERE id = ?`, [id]); +}; + +export const deleteSet = async (id: number) => { + return db.executeSql(`DELETE FROM sets WHERE id = ?`, [id]); +}; + +export const deleteSetsBy = async (name: string) => { + return db.executeSql(`DELETE FROM sets WHERE name = ?`, [name]); +}; + +export const getAllPlans = async (): Promise => { + const select = `SELECT * from plans`; + const [result] = await db.executeSql(select); + return result.rows.raw(); +}; + +export const getAllSets = async (): Promise => { + const select = `SELECT * from sets`; + const [result] = await db.executeSql(select); + return result.rows.raw(); +}; + +export interface PageParams { + search: string; + limit: number; + offset: number; +} + +export const getSets = async ({ + search, + limit, + offset, +}: PageParams): Promise => { + const select = ` + SELECT * from sets + WHERE name LIKE ? AND NOT hidden + ORDER BY created DESC + LIMIT ? OFFSET ? + `; + const [result] = await db.executeSql(select, [`%${search}%`, limit, offset]); + return result.rows.raw(); +}; + +export const getTodaysPlan = async (): Promise => { + const today = DAYS[new Date().getDay()]; + const [result] = await db.executeSql( + `SELECT * FROM plans WHERE days LIKE ? LIMIT 1`, + [`%${today}%`], + ); + return result.rows.raw(); +}; + +export const getTodaysSets = async (): Promise => { + const today = new Date().toISOString().split('T')[0]; + const [result] = await db.executeSql( + `SELECT * FROM sets WHERE created LIKE ? ORDER BY created DESC`, + [`${today}%`], + ); + return result.rows.raw(); +}; + +export const defaultSet = { + name: '', + id: 0, + reps: 10, + weight: 20, + unit: 'kg', +}; + +export const getBest = async (query: string): Promise => { + const bestWeight = ` + SELECT name, reps, unit, MAX(weight) AS weight + FROM sets + WHERE name = ? AND NOT hidden + GROUP BY name; + `; + const bestReps = ` + SELECT name, MAX(reps) as reps, unit, weight + FROM sets + WHERE name = ? AND weight = ? AND NOT hidden + GROUP BY name; + `; + const [weightResult] = await db.executeSql(bestWeight, [query]); + if (!weightResult.rows.length) return {...defaultSet}; + const [repsResult] = await db.executeSql(bestReps, [ + query, + weightResult.rows.item(0).weight, + ]); + return repsResult.rows.item(0); +}; + +export const setSetName = async (oldName: string, newName: string) => { + const update = `UPDATE sets SET name = ? WHERE name = ?`; + return db.executeSql(update, [newName, oldName]); +}; + +export const setSetImage = async (name: string, image: string) => { + const update = `UPDATE sets SET image = ? WHERE name = ?`; + return db.executeSql(update, [name, image]); +}; + +export const getWeights = async ( + name: string, + period: Periods, +): Promise => { + const select = ` + SELECT max(weight) AS weight, + STRFTIME('%Y-%m-%d', created) as created, unit + FROM sets + WHERE name = ? AND NOT hidden + AND DATE(created) >= DATE('now', 'weekday 0', ?) + GROUP BY name, STRFTIME('%Y-%m-%d', created) + `; + let difference = '-7 days'; + if (period === Periods.Monthly) difference = '-1 months'; + else if (period === Periods.Yearly) difference = '-1 years'; + const [result] = await db.executeSql(select, [name, difference]); + return result.rows.raw(); +}; + +export const getVolumes = async ( + name: string, + period: Periods, +): Promise => { + const select = ` + SELECT sum(weight * reps) AS value, + STRFTIME('%Y-%m-%d', created) as created, unit + FROM sets + WHERE name = ? AND NOT hidden + AND DATE(created) >= DATE('now', 'weekday 0', ?) + GROUP BY name, STRFTIME('%Y-%m-%d', created) + `; + let difference = '-7 days'; + if (period === Periods.Monthly) difference = '-1 months'; + else if (period === Periods.Yearly) difference = '-1 years'; + const [result] = await db.executeSql(select, [name, difference]); + return result.rows.raw(); +}; + +export const getNames = async (): Promise => { + const [result] = await db.executeSql('SELECT DISTINCT name FROM sets'); + return result.rows.raw(); +}; + +export const getBestWeights = async (search: string): Promise => { + const select = ` + SELECT name, reps, unit, MAX(weight) AS weight + FROM sets + WHERE name LIKE ? AND NOT hidden + GROUP BY name; + `; + const [result] = await db.executeSql(select, [`%${search}%`]); + return result.rows.raw(); +}; + +export const getBestReps = async ( + name: string, + weight: number, +): Promise => { + const select = ` + SELECT name, MAX(reps) as reps, unit, weight + FROM sets + WHERE name = ? AND weight = ? AND NOT hidden + GROUP BY name; + `; + const [result] = await db.executeSql(select, [name, weight]); + return result.rows.raw(); +}; + +export const getPlans = async (search: string): Promise => { + const select = ` + SELECT * from plans + WHERE days LIKE ? OR workouts LIKE ? + `; + const [result] = await db.executeSql(select, [`%${search}%`, `%${search}%`]); + return result.rows.raw(); +}; + +export const getWorkouts = async ({ + search, + limit, + offset, +}: PageParams): Promise => { + const select = ` + SELECT DISTINCT sets.name + FROM sets + WHERE sets.name LIKE ? + ORDER BY sets.name + LIMIT ? OFFSET ? + `; + const [result] = await db.executeSql(select, [search, limit, offset]); + return result.rows.raw(); }; diff --git a/metrics.ts b/metrics.ts new file mode 100644 index 0000000..0c2998b --- /dev/null +++ b/metrics.ts @@ -0,0 +1,4 @@ +export enum Metrics { + Weight = 'Best weight per day', + Volume = 'Volume per day', +} diff --git a/periods.ts b/periods.ts new file mode 100644 index 0000000..6f87e33 --- /dev/null +++ b/periods.ts @@ -0,0 +1,5 @@ +export enum Periods { + Weekly = 'This week', + Monthly = 'This month', + Yearly = 'This year', +} diff --git a/plan.ts b/plan.ts index 9866dbd..bc46c99 100644 --- a/plan.ts +++ b/plan.ts @@ -1,5 +1,5 @@ export interface Plan { - id: number; + id?: number; days: string; workouts: string; } diff --git a/volume.ts b/volume.ts new file mode 100644 index 0000000..786b73e --- /dev/null +++ b/volume.ts @@ -0,0 +1,6 @@ +export default interface Volume { + name: string; + created: string; + value: number; + unit: string; +}