Pause converting to typeorm due to odd error

ERROR  TypeError: Cannot read property 'getItem' of undefined

This error is located at:
    in FlatList (created by SetList)
    in RCTView (created by View)
    in View (created by Page)
    in Page (created by SetList)
    in SetList (created by SceneView)
...

I found an open issue on the react-native github which seems
related https://github.com/facebook/react-native/issues/31523
but after following all of their suggestions I still have the
same error. I tried:
- Removing @babel/plugin-proposal-class-properties & @babel/plugin-transform-flow-strip-types
- Adding @babel/plugin-transform-flow-strip-types
This commit is contained in:
Brandon Presley 2022-10-31 13:20:36 +13:00
parent 111ee4201f
commit b7f1c2192e
37 changed files with 721 additions and 586 deletions

View File

@ -4,4 +4,4 @@ module.exports = {
bracketSpacing: false,
singleQuote: true,
trailingComma: 'all',
};
}

View File

@ -13,11 +13,10 @@ import {
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {Color} from './color';
import {lightColors} from './colors';
import {runMigrations} from './db';
import {runMigrations, settingsRepo} from './db';
import MassiveSnack from './MassiveSnack';
import Routes from './Routes';
import Settings from './settings';
import {getSettings} from './settings.service';
import {SettingsContext} from './use-settings';
export const CombinedDefaultTheme = {
@ -51,7 +50,7 @@ const App = () => {
useEffect(() => {
runMigrations().then(async () => {
const gotSettings = await getSettings();
const gotSettings = await settingsRepo.findOne({where: {}});
console.log(`${App.name}.runMigrations:`, {gotSettings});
setSettings(gotSettings);
if (gotSettings.color) setColor(gotSettings.color);

View File

@ -6,25 +6,40 @@ import {
import {useCallback, useState} from 'react';
import {FlatList, Image} from 'react-native';
import {List} from 'react-native-paper';
import {getBestReps, getBestWeights} from './best.service';
import {BestPageParams} from './BestPage';
import {setRepo} from './db';
import DrawerHeader from './DrawerHeader';
import GymSet from './gym-set';
import Page from './Page';
import Set from './set';
import {useSettings} from './use-settings';
export default function BestList() {
const [bests, setBests] = useState<Set[]>();
const [bests, setBests] = useState<GymSet[]>();
const [term, setTerm] = useState('');
const navigation = useNavigation<NavigationProp<BestPageParams>>();
const {settings} = useSettings();
const refresh = useCallback(async (value: string) => {
const weights = await getBestWeights(value);
const weights = await setRepo
.createQueryBuilder()
.select()
.addSelect('MAX(weight)', 'weight')
.where('name LIKE :name', {name: `%${value}%`})
.andWhere('NOT hidden')
.groupBy('name')
.getMany();
console.log(`${BestList.name}.refresh:`, {length: weights.length});
let newBest: Set[] = [];
let newBest: GymSet[] = [];
for (const set of weights) {
const reps = await getBestReps(set.name, set.weight);
const reps = await setRepo
.createQueryBuilder()
.select()
.addSelect('MAX(reps)', 'reps')
.where('name = :name', {name: set.name})
.andWhere('weight = :weight', {weight: set.weight})
.andWhere('NOT hidden')
.groupBy('name')
.getMany();
newBest.push(...reps);
}
setBests(newBest);
@ -44,7 +59,7 @@ export default function BestList() {
[refresh],
);
const renderItem = ({item}: {item: Set}) => (
const renderItem = ({item}: {item: GymSet}) => (
<List.Item
key={item.name}
title={item.name}

View File

@ -1,13 +1,13 @@
import {createStackNavigator} from '@react-navigation/stack';
import BestList from './BestList';
import Set from './set';
import GymSet from './gym-set';
import ViewBest from './ViewBest';
const Stack = createStackNavigator<BestPageParams>();
export type BestPageParams = {
BestList: {};
ViewBest: {
best: Set;
best: GymSet;
};
};

View File

@ -4,7 +4,7 @@ import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts';
import {CombinedDarkTheme, CombinedDefaultTheme} from './App';
import {useColor} from './color';
import {MARGIN, PADDING} from './constants';
import Set from './set';
import GymSet from './gym-set';
import useDark from './use-dark';
export default function Chart({
@ -14,7 +14,7 @@ export default function Chart({
yFormat,
}: {
yData: number[];
xData: Set[];
xData: GymSet[];
xFormat: (value: any, index: number) => string;
yFormat: (value: any) => string;
}) {

View File

@ -4,18 +4,18 @@ import DocumentPicker from 'react-native-document-picker';
import {FileSystem} from 'react-native-file-access';
import {Divider, IconButton, Menu} from 'react-native-paper';
import ConfirmDialog from './ConfirmDialog';
import {AppDataSource, planRepo} from './db';
import {DrawerParamList} from './drawer-param-list';
import GymSet from './gym-set';
import {useSnackbar} from './MassiveSnack';
import {Plan} from './plan';
import {addPlans, deletePlans, getAllPlans} from './plan.service';
import Set from './set';
import {addSets, deleteSets, getAllSets} from './set.service';
import useDark from './use-dark';
import {write} from './write';
const setFields =
'id,name,reps,weight,created,unit,hidden,sets,minutes,seconds';
const planFields = 'id,days,workouts';
const setRepo = AppDataSource.manager.getRepository(GymSet);
export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const [showMenu, setShowMenu] = useState(false);
@ -25,14 +25,14 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const dark = useDark();
const exportSets = useCallback(async () => {
const sets = await getAllSets();
const sets = await setRepo.find({});
const data = [setFields]
.concat(
sets.map(set =>
setFields
.split(',')
.map(fieldString => {
const field = fieldString as keyof Set;
const field = fieldString as keyof GymSet;
if (field === 'unit') return set[field] || 'kg';
return set[field];
})
@ -45,7 +45,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
}, []);
const exportPlans = useCallback(async () => {
const plans: Plan[] = await getAllPlans();
const plans = await planRepo.find({});
const data = [planFields]
.concat(plans.map(set => `"${set.id}","${set.days}","${set.workouts}"`))
.join('\n');
@ -62,14 +62,14 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const uploadSets = useCallback(async () => {
const result = await DocumentPicker.pickSingle();
const file = await FileSystem.readFile(result.uri);
console.log(`${DrawerMenu.name}.${uploadSets.name}:`, file.length);
console.log(`${DrawerMenu.name}.uploadSets:`, file.length);
const lines = file.split('\n');
console.log(lines[0]);
if (!setFields.includes(lines[0])) return toast('Invalid csv.', 3000);
const values = lines
.slice(1)
.filter(line => line)
.map(set => {
.map(line => {
let [
,
setName,
@ -81,15 +81,22 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
sets,
minutes,
seconds,
] = set.split(',');
unit = unit || 'kg';
hidden = hidden || '0';
return `('${setName}',${reps},${weight},'${created}','${unit}',${hidden},${
sets ?? 3
},${minutes ?? 3},${seconds ?? 30})`;
})
.join(',');
await addSets(setFields.split(',').slice(1).join(','), values);
] = line.split(',');
const set: GymSet = {
name: setName,
reps: +reps,
weight: +weight,
created,
unit: unit ?? 'kg',
hidden: !!Number(hidden),
sets: +sets,
minutes: +minutes,
seconds: +seconds,
};
return set;
});
console.log(`${DrawerMenu.name}.uploadSets:`, {values});
await setRepo.insert(values);
toast('Data imported.', 3000);
reset({index: 0, routes: [{name}]});
}, [reset, name, toast]);
@ -108,10 +115,13 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const [, days, workouts] = set
.split('","')
.map(cell => cell.replace(/"/g, ''));
return `('${days}','${workouts}')`;
})
.join(',');
await addPlans(values);
const plan: Plan = {
days,
workouts,
};
return plan;
});
await planRepo.insert(values);
toast('Data imported.', 3000);
}, [toast]);
@ -125,8 +135,8 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const remove = useCallback(async () => {
setShowMenu(false);
setShowRemove(false);
if (name === 'Home') await deleteSets();
else if (name === 'Plans') await deletePlans();
if (name === 'Home') await setRepo.delete({});
else if (name === 'Plans') await planRepo.delete({});
toast('All data has been deleted.', 4000);
reset({index: 0, routes: [{name}]});
}, [reset, name, toast]);

View File

@ -8,10 +8,9 @@ import {useCallback, useEffect, useState} from 'react';
import {ScrollView, StyleSheet, View} from 'react-native';
import {Button, Text} from 'react-native-paper';
import {MARGIN, PADDING} from './constants';
import {planRepo, setRepo} from './db';
import {DrawerParamList} from './drawer-param-list';
import {PlanPageParams} from './plan-page-params';
import {addPlan, updatePlan} from './plan.service';
import {getNames} from './set.service';
import StackHeader from './StackHeader';
import Switch from './Switch';
import {DAYS} from './time';
@ -29,9 +28,14 @@ export default function EditPlan() {
const navigation = useNavigation<NavigationProp<DrawerParamList>>();
useEffect(() => {
getNames().then(n => {
console.log(EditPlan.name, {n});
setNames(n);
setRepo
.createQueryBuilder()
.select('name')
.distinct(true)
.getRawMany()
.then(values => {
console.log(EditPlan.name, {values});
setNames(values.map(value => value.name));
});
}, []);
@ -40,14 +44,7 @@ export default function EditPlan() {
if (!days || !workouts) return;
const newWorkouts = workouts.filter(workout => workout).join(',');
const newDays = days.filter(day => day).join(',');
if (typeof plan.id === 'undefined')
await addPlan({days: newDays, workouts: newWorkouts});
else
await updatePlan({
days: newDays,
workouts: newWorkouts,
id: plan.id,
});
await planRepo.save({days: newDays, workouts: newWorkouts, id: plan.id});
navigation.goBack();
}, [days, workouts, plan, navigation]);

View File

@ -2,10 +2,10 @@ import {RouteProp, useNavigation, useRoute} from '@react-navigation/native';
import {useCallback} from 'react';
import {NativeModules, View} from 'react-native';
import {PADDING} from './constants';
import {getNow, setRepo} from './db';
import GymSet from './gym-set';
import {HomePageParams} from './home-page-params';
import {useSnackbar} from './MassiveSnack';
import Set from './set';
import {addSet, getSet, updateSet} from './set.service';
import SetForm from './SetForm';
import StackHeader from './StackHeader';
import {useSettings} from './use-settings';
@ -20,7 +20,7 @@ export default function EditSet() {
const startTimer = useCallback(
async (name: string) => {
if (!settings.alarm) return;
const {minutes, seconds} = await getSet(name);
const {minutes, seconds} = await setRepo.findOne({where: {name}});
const milliseconds = (minutes ?? 3) * 60 * 1000 + (seconds ?? 0) * 1000;
NativeModules.AlarmModule.timer(
milliseconds,
@ -32,37 +32,32 @@ export default function EditSet() {
[settings],
);
const update = useCallback(
async (value: Set) => {
console.log(`${EditSet.name}.update`, value);
await updateSet(value);
navigation.goBack();
},
[navigation],
);
const add = useCallback(
async (value: Set) => {
console.log(`${EditSet.name}.add`, {set: value});
async (value: GymSet) => {
startTimer(value.name);
await addSet(value);
if (!settings.notify) return navigation.goBack();
const [{now}] = await getNow();
value.created = now;
value.hidden = false;
console.log(`${EditSet.name}.add`, {set: value});
const result = await setRepo.save(value);
console.log({result});
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.", 3000);
navigation.goBack();
},
[navigation, startTimer, set, toast, settings],
[startTimer, set, toast, settings],
);
const save = useCallback(
async (value: Set) => {
if (typeof set.id === 'number') return update(value);
return add(value);
async (value: GymSet) => {
if (typeof set.id === 'number') await setRepo.save(value);
else await add(value);
navigation.goBack();
},
[update, add, set.id],
[add, set.id, navigation],
);
return (

View File

@ -3,12 +3,12 @@ import {useCallback, useRef, useState} from 'react';
import {ScrollView, TextInput, View} from 'react-native';
import DocumentPicker from 'react-native-document-picker';
import {Button, Card, TouchableRipple} from 'react-native-paper';
import {Like} from 'typeorm';
import ConfirmDialog from './ConfirmDialog';
import {MARGIN, PADDING} from './constants';
import {getNow, planRepo, setRepo} from './db';
import MassiveInput from './MassiveInput';
import {useSnackbar} from './MassiveSnack';
import {updatePlanWorkouts} from './plan.service';
import {addSet, updateManySet, updateSetImage} from './set.service';
import StackHeader from './StackHeader';
import {useSettings} from './use-settings';
import {WorkoutsPageParams} from './WorkoutsPage';
@ -36,21 +36,29 @@ export default function EditWorkout() {
const {settings} = useSettings();
const update = async () => {
await updateManySet({
oldName: params.value.name,
newName: name || params.value.name,
sets: sets ?? '3',
seconds: seconds?.toString() ?? '30',
minutes: minutes?.toString() ?? '3',
await setRepo.update(
{name: params.value.name},
{
name: name || params.value.name,
sets: Number(sets),
minutes: +minutes,
seconds: +seconds,
steps,
});
await updatePlanWorkouts(params.value.name, name || params.value.name);
if (uri || removeImage) await updateSetImage(params.value.name, uri || '');
image: removeImage ? '' : uri,
},
);
await planRepo.query(
`UPDATE plans
SET workouts = REPLACE(workouts, $1, $2)
WHERE workouts LIKE $3`,
[params.value.name, name, `%${params.value.name}%`],
);
navigation.goBack();
};
const add = async () => {
await addSet({
const [{now}] = await getNow();
await setRepo.save({
name,
reps: 0,
weight: 0,
@ -60,6 +68,7 @@ export default function EditWorkout() {
seconds: seconds ? +seconds : 30,
sets: sets ? +sets : 3,
steps,
created: now,
});
navigation.goBack();
};

View File

@ -7,9 +7,9 @@ import {useCallback, useMemo, useState} from 'react';
import {GestureResponderEvent, Text} from 'react-native';
import {Divider, List, Menu} from 'react-native-paper';
import {getBestSet} from './best.service';
import {planRepo} from './db';
import {Plan} from './plan';
import {PlanPageParams} from './plan-page-params';
import {deletePlan} from './plan.service';
import {DAYS} from './time';
export default function PlanItem({
@ -33,7 +33,7 @@ export default function PlanItem({
);
const remove = useCallback(async () => {
if (typeof item.id === 'number') await deletePlan(item.id);
if (typeof item.id === 'number') await planRepo.delete(item.id);
setShow(false);
onRemove();
}, [setShow, item.id, onRemove]);
@ -42,6 +42,7 @@ export default function PlanItem({
const workouts = item.workouts.split(',');
const first = workouts[0];
const set = await getBestSet(first);
console.log(`${PlanItem.name}.start:`, {set});
setShow(false);
navigation.navigate('StartPlan', {plan: item, set});
}, [item, navigation]);

View File

@ -6,11 +6,12 @@ import {
import {useCallback, useState} from 'react';
import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import {Like} from 'typeorm';
import {planRepo} from './db';
import DrawerHeader from './DrawerHeader';
import Page from './Page';
import {Plan} from './plan';
import {PlanPageParams} from './plan-page-params';
import {getPlans} from './plan.service';
import PlanItem from './PlanItem';
export default function PlanList() {
@ -19,7 +20,11 @@ export default function PlanList() {
const navigation = useNavigation<NavigationProp<PlanPageParams>>();
const refresh = useCallback(async (value: string) => {
getPlans(value).then(setPlans);
planRepo
.find({
where: [{days: Like(`%${value}%`)}, {workouts: Like(`%${value}%`)}],
})
.then(setPlans);
}, []);
useFocusEffect(

View File

@ -4,18 +4,18 @@ import DocumentPicker from 'react-native-document-picker';
import {Button, Card, TouchableRipple} from 'react-native-paper';
import ConfirmDialog from './ConfirmDialog';
import {MARGIN} from './constants';
import {setRepo} from './db';
import GymSet from './gym-set';
import MassiveInput from './MassiveInput';
import {useSnackbar} from './MassiveSnack';
import Set from './set';
import {getSets} from './set.service';
import {useSettings} from './use-settings';
export default function SetForm({
save,
set,
}: {
set: Set;
save: (set: Set) => void;
set: GymSet;
save: (set: GymSet) => void;
}) {
const [name, setName] = useState(set.name);
const [reps, setReps] = useState(set.reps.toString());
@ -39,9 +39,8 @@ export default function SetForm({
if (!name) return;
let image = newImage;
if (!newImage && !removeImage)
image = await getSets({term: name, limit: 1, offset: 0}).then(
([gotSet]) => gotSet?.image,
);
image = await setRepo.findOne({where: {name}}).then(s => s.image);
console.log(`${SetForm.name}.handleSubmit:`, {image});
save({
name,

View File

@ -2,9 +2,9 @@ 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 GymSet from './gym-set';
import {HomePageParams} from './home-page-params';
import Set from './set';
import {deleteSet} from './set.service';
import {format} from './time';
import useDark from './use-dark';
import {useSettings} from './use-settings';
@ -13,7 +13,7 @@ export default function SetItem({
item,
onRemove,
}: {
item: Set;
item: GymSet;
onRemove: () => void;
}) {
const [showMenu, setShowMenu] = useState(false);
@ -23,13 +23,13 @@ export default function SetItem({
const navigation = useNavigation<NavigationProp<HomePageParams>>();
const remove = useCallback(async () => {
if (typeof item.id === 'number') await deleteSet(item.id);
if (typeof item.id === 'number') await setRepo.delete(item.id);
setShowMenu(false);
onRemove();
}, [setShowMenu, onRemove, item.id]);
const copy = useCallback(() => {
const set: Set = {...item};
const set: GymSet = {...item};
delete set.id;
setShowMenu(false);
navigation.navigate('EditSet', {set});

View File

@ -3,35 +3,37 @@ import {
useFocusEffect,
useNavigation,
} from '@react-navigation/native';
import {useCallback, useState} from 'react';
import React, {useCallback, useEffect, useState} from 'react';
import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import {Like} from 'typeorm';
import {getNow, setRepo} from './db';
import DrawerHeader from './DrawerHeader';
import GymSet from './gym-set';
import {HomePageParams} from './home-page-params';
import Page from './Page';
import Set from './set';
import {defaultSet, getSets, getToday} from './set.service';
import SetItem from './SetItem';
const limit = 15;
export default function SetList() {
const [sets, setSets] = useState<Set[]>();
const [set, setSet] = useState<Set>();
const [sets, setSets] = useState<GymSet[]>([]);
const [set, setSet] = useState<GymSet>();
const [offset, setOffset] = useState(0);
const [term, setTerm] = useState('');
const [end, setEnd] = useState(false);
const navigation = useNavigation<NavigationProp<HomePageParams>>();
useEffect(() => console.log({sets}), [sets]);
const refresh = useCallback(async (value: string) => {
const todaysSet = await getToday();
if (todaysSet) setSet({...todaysSet});
const newSets = await getSets({
term: `%${value}%`,
limit,
offset: 0,
const newSets = await setRepo.find({
where: {name: Like(`%${value}%`), hidden: 0 as any},
take: limit,
skip: 0,
order: {created: 'DESC'},
});
console.log(`${SetList.name}.refresh:`, {newSets});
setSet(newSets[0]);
if (newSets.length === 0) return setSets([]);
setSets(newSets);
setOffset(0);
@ -45,7 +47,7 @@ export default function SetList() {
);
const renderItem = useCallback(
({item}: {item: Set}) => (
({item}: {item: GymSet}) => (
<SetItem item={item} key={item.id} onRemove={() => refresh(term)} />
),
[refresh, term],
@ -55,22 +57,33 @@ export default function SetList() {
if (end) return;
const newOffset = offset + limit;
console.log(`${SetList.name}.next:`, {offset, newOffset, term});
const newSets = await getSets({
term: `%${term}%`,
limit,
offset: newOffset,
const newSets = await setRepo.find({
where: {name: Like(`%${term}%`), hidden: 0 as any},
take: limit,
skip: newOffset,
order: {created: 'DESC'},
});
if (newSets.length === 0) return setEnd(true);
if (!sets) return;
setSets([...sets, ...newSets]);
// setSets([...sets, ...newSets]);
if (newSets.length < limit) return setEnd(true);
setOffset(newOffset);
}, [term, end, offset, sets]);
const onAdd = useCallback(async () => {
console.log(`${SetList.name}.onAdd`, {set});
const [{now}] = await getNow();
navigation.navigate('EditSet', {
set: set || {...defaultSet},
set: set || {
hidden: false,
minutes: 3,
name: '',
reps: 0,
seconds: 30,
sets: 3,
weight: 0,
created: now,
},
});
}, [navigation, set]);
@ -96,7 +109,10 @@ export default function SetList() {
data={sets}
style={{flex: 1}}
renderItem={renderItem}
keyExtractor={s => s.id!.toString()}
getItem={(data: any, index: number) => {
console.log({data, index});
return data[index];
}}
onEndReached={next}
/>
)}

View File

@ -8,12 +8,12 @@ import {useColor} from './color';
import {darkColors, lightColors} from './colors';
import ConfirmDialog from './ConfirmDialog';
import {MARGIN} from './constants';
import {settingsRepo} from './db';
import DrawerHeader from './DrawerHeader';
import Input from './input';
import {useSnackbar} from './MassiveSnack';
import Page from './Page';
import Settings from './settings';
import {updateSettings} from './settings.service';
import Switch from './Switch';
import {useSettings} from './use-settings';
@ -50,8 +50,8 @@ export default function SettingsPage() {
const update = useCallback(
(value: boolean, field: keyof Settings) => {
updateSettings({...settings, [field]: +value});
setSettings({...settings, [field]: +value});
settingsRepo.update({}, {[field]: value});
setSettings({...settings, [field]: value});
},
[settings, setSettings],
);
@ -81,7 +81,7 @@ export default function SettingsPage() {
copyTo: 'documentDirectory',
});
if (!fileCopyUri) return;
updateSettings({sound: fileCopyUri} as Settings);
settingsRepo.update({}, {sound: fileCopyUri});
setSettings({...settings, sound: fileCopyUri});
toast('This song will now play after rest timers complete.', 4000);
}, [toast, setSettings, settings]);
@ -150,28 +150,28 @@ export default function SettingsPage() {
);
const switches: Input<boolean>[] = [
{name: 'Rest timers', value: !!alarm, onChange: changeAlarmEnabled},
{name: 'Vibrate', value: !!vibrate, onChange: changeVibrate},
{name: 'Disable sound', value: !!noSound, onChange: changeNoSound},
{name: 'Record notifications', value: !!notify, onChange: changeNotify},
{name: 'Show images', value: !!images, onChange: changeImages},
{name: 'Show unit', value: !!showUnit, onChange: changeUnit},
{name: 'Show steps', value: !!steps, onChange: changeSteps},
{name: 'Show date', value: !!showDate, onChange: changeShowDate},
{name: 'Show sets', value: !!showSets, onChange: changeShowSets},
{name: 'Rest timers', value: alarm, onChange: changeAlarmEnabled},
{name: 'Vibrate', value: vibrate, onChange: changeVibrate},
{name: 'Disable sound', value: noSound, onChange: changeNoSound},
{name: 'Record notifications', value: notify, onChange: changeNotify},
{name: 'Show images', value: images, onChange: changeImages},
{name: 'Show unit', value: showUnit, onChange: changeUnit},
{name: 'Show steps', value: steps, onChange: changeSteps},
{name: 'Show date', value: showDate, onChange: changeShowDate},
{name: 'Show sets', value: showSets, onChange: changeShowSets},
];
const changeTheme = useCallback(
(value: string) => {
updateSettings({...settings, theme: value as any});
setSettings({...settings, theme: value as any});
settingsRepo.update({}, {theme: value});
setSettings({...settings, theme: value});
},
[settings, setSettings],
);
const changeDate = useCallback(
(value: string) => {
updateSettings({...settings, date: value as any});
settingsRepo.update({}, {date: value});
setSettings({...settings, date: value as any});
},
[settings, setSettings],

View File

@ -6,11 +6,12 @@ import {Button} from 'react-native-paper';
import {getBestSet} from './best.service';
import {PADDING} from './constants';
import CountMany from './count-many';
import {AppDataSource, getNow, setRepo} from './db';
import GymSet from './gym-set';
import MassiveInput from './MassiveInput';
import {useSnackbar} from './MassiveSnack';
import {PlanPageParams} from './plan-page-params';
import Set from './set';
import {addSet, countMany} from './set.service';
import {countMany} from './set.service';
import SetForm from './SetForm';
import StackHeader from './StackHeader';
import StartPlanItem from './StartPlanItem';
@ -26,7 +27,7 @@ export default function StartPlan() {
const {toast} = useSnackbar();
const [minutes, setMinutes] = useState(set.minutes);
const [seconds, setSeconds] = useState(set.seconds);
const [best, setBest] = useState<Set>(set);
const [best, setBest] = useState<GymSet>(set);
const [selected, setSelected] = useState(0);
const {settings} = useSettings();
const [counts, setCounts] = useState<CountMany[]>();
@ -41,7 +42,23 @@ export default function StartPlan() {
});
const refresh = useCallback(() => {
return countMany(workouts).then(newCounts => {
const questions = workouts.map(_ => '(?)').join(',');
const condition = `
sets.name = workouts.name
AND sets.created LIKE STRFTIME('%Y-%m-%d%%', 'now', 'localtime')
AND NOT sets.hidden
`;
return AppDataSource.manager
.createQueryBuilder()
.select('COUNT(sets.id)', 'total')
.addSelect('workouts')
.from(`(SELECT 0 AS name UNION values ${questions})`, 'workouts')
.leftJoin('sets', condition)
.groupBy('workouts.name')
.limit(-1)
.offset(1)
.getRawMany()
.then(newCounts => {
setCounts(newCounts);
console.log(`${StartPlan.name}.focus:`, {newCounts});
});
@ -55,7 +72,8 @@ export default function StartPlan() {
const handleSubmit = async () => {
console.log(`${SetForm.name}.handleSubmit:`, {reps, weight, unit, best});
await addSet({
const [{now}] = await getNow();
await setRepo.save({
name,
weight: +weight,
reps: +reps,
@ -64,6 +82,7 @@ export default function StartPlan() {
steps: set.steps,
image: set.image,
unit,
created: now,
});
await refresh();
if (
@ -92,11 +111,12 @@ export default function StartPlan() {
const select = useCallback(
async (index: number) => {
setSelected(index);
console.log(`${StartPlan.name}.next:`, {name});
console.log(`${StartPlan.name}.next:`, {name, index});
if (!counts) return;
const workout = counts[index];
console.log(`${StartPlan.name}.next:`, {workout});
const newBest = await getBestSet(workout.name);
console.log(`${StartPlan.name}.next:`, {newBest});
setMinutes(newBest.minutes);
setSeconds(newBest.seconds);
setName(newBest.name);

View File

@ -3,7 +3,7 @@ import {GestureResponderEvent, ListRenderItemInfo, View} from 'react-native';
import {List, Menu, RadioButton} from 'react-native-paper';
import {useColor} from './color';
import CountMany from './count-many';
import {deleteFirst} from './set.service';
import {setRepo} from './db';
interface Props extends ListRenderItemInfo<CountMany> {
onSelect: (index: number) => void;
@ -18,10 +18,15 @@ export default function StartPlanItem(props: Props) {
const [showMenu, setShowMenu] = useState(false);
const undo = useCallback(async () => {
await deleteFirst(item.name);
const first = await setRepo.findOne({
where: {name: item.name, hidden: 0 as any},
order: {created: 'desc'},
});
console.log({first});
await setRepo.delete(first.id);
setShowMenu(false);
onUndo();
}, [setShowMenu, item.name, onUndo]);
}, [setShowMenu, onUndo, item.name]);
const longPress = useCallback(
(e: GestureResponderEvent) => {

View File

@ -2,13 +2,14 @@ import {Picker} from '@react-native-picker/picker';
import {RouteProp, useRoute} from '@react-navigation/native';
import {useEffect, useState} from 'react';
import {View} from 'react-native';
import {getOneRepMax, getVolumes, getWeightsBy} from './best.service';
import {getOneRepMax} from './best.service';
import {BestPageParams} from './BestPage';
import Chart from './Chart';
import {PADDING} from './constants';
import {setRepo} from './db';
import GymSet from './gym-set';
import {Metrics} from './metrics';
import {Periods} from './periods';
import Set from './set';
import StackHeader from './StackHeader';
import {formatMonth} from './time';
import useDark from './use-dark';
@ -17,7 +18,7 @@ import Volume from './volume';
export default function ViewBest() {
const {params} = useRoute<RouteProp<BestPageParams, 'ViewBest'>>();
const dark = useDark();
const [weights, setWeights] = useState<Set[]>([]);
const [weights, setWeights] = useState<GymSet[]>([]);
const [volumes, setVolumes] = useState<Volume[]>([]);
const [metric, setMetric] = useState(Metrics.Weight);
const [period, setPeriod] = useState(Periods.Monthly);
@ -25,12 +26,34 @@ export default function ViewBest() {
useEffect(() => {
console.log(`${ViewBest.name}.useEffect`, {metric});
console.log(`${ViewBest.name}.useEffect`, {period});
let difference = '-7 days';
if (period === Periods.Monthly) difference = '-1 months';
else if (period === Periods.Yearly) difference = '-1 years';
let group = '%Y-%m-%d';
if (period === Periods.Yearly) group = '%Y-%m';
const builder = setRepo
.createQueryBuilder()
.select("STRFTIME('%Y-%m-%d', created)", 'created')
.addSelect('unit')
.where('name = :name', {name: params.best.name})
.andWhere('NOT hidden')
.andWhere("DATE(created) >= DATE('now', 'weekday 0', :difference)", {
difference,
})
.groupBy('name')
.addGroupBy(`STRFTIME('${group}', created)`);
switch (metric) {
case Metrics.Weight:
getWeightsBy(params.best.name, period).then(setWeights);
builder
.addSelect('MAX(weight)', 'weight')
.getRawMany()
.then(setWeights);
break;
case Metrics.Volume:
getVolumes(params.best.name, period).then(setVolumes);
builder
.addSelect('SUM(weight * reps)', 'value')
.getRawMany()
.then(setVolumes);
break;
default:
getOneRepMax({name: params.best.name, period}).then(setWeights);

View File

@ -3,8 +3,8 @@ import {useCallback, useMemo, useState} from 'react';
import {GestureResponderEvent, Image} from 'react-native';
import {List, Menu, Text} from 'react-native-paper';
import ConfirmDialog from './ConfirmDialog';
import Set from './set';
import {deleteSetsBy} from './set.service';
import {setRepo} from './db';
import GymSet from './gym-set';
import {useSettings} from './use-settings';
import {WorkoutsPageParams} from './WorkoutsPage';
@ -12,7 +12,7 @@ export default function WorkoutItem({
item,
onRemove,
}: {
item: Set;
item: GymSet;
onRemove: () => void;
}) {
const [showMenu, setShowMenu] = useState(false);
@ -22,7 +22,7 @@ export default function WorkoutItem({
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>();
const remove = useCallback(async () => {
await deleteSetsBy(item.name);
await setRepo.delete({name: item.name});
setShowMenu(false);
onRemove();
}, [setShowMenu, onRemove, item.name]);

View File

@ -8,27 +8,30 @@ import {FlatList} from 'react-native';
import {List} from 'react-native-paper';
import DrawerHeader from './DrawerHeader';
import Page from './Page';
import Set from './set';
import {getDistinctSets} from './set.service';
import GymSet from './gym-set';
import SetList from './SetList';
import WorkoutItem from './WorkoutItem';
import {WorkoutsPageParams} from './WorkoutsPage';
import {setRepo} from './db';
const limit = 15;
export default function WorkoutList() {
const [workouts, setWorkouts] = useState<Set[]>();
const [workouts, setWorkouts] = useState<GymSet[]>();
const [offset, setOffset] = useState(0);
const [term, setTerm] = useState('');
const [end, setEnd] = useState(false);
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>();
const refresh = useCallback(async (value: string) => {
const newWorkouts = await getDistinctSets({
term: `%${value}%`,
limit,
offset: 0,
});
const newWorkouts = await setRepo
.createQueryBuilder()
.select()
.where('name LIKE :name', {name: `%${value}%`})
.groupBy('name')
.orderBy('name')
.limit(limit)
.getMany();
console.log(`${WorkoutList.name}`, {newWorkout: newWorkouts[0]});
setWorkouts(newWorkouts);
setOffset(0);
@ -42,7 +45,7 @@ export default function WorkoutList() {
);
const renderItem = useCallback(
({item}: {item: Set}) => (
({item}: {item: GymSet}) => (
<WorkoutItem item={item} key={item.name} onRemove={() => refresh(term)} />
),
[refresh, term],
@ -57,11 +60,15 @@ export default function WorkoutList() {
newOffset,
term,
});
const newWorkouts = await getDistinctSets({
term: `%${term}%`,
limit,
offset: newOffset,
});
const newWorkouts = await setRepo
.createQueryBuilder()
.select()
.where('name LIKE :name', {name: `%${term}%`})
.groupBy('name')
.orderBy('name')
.limit(limit)
.offset(newOffset)
.getMany();
if (newWorkouts.length === 0) return setEnd(true);
if (!workouts) return;
setWorkouts([...workouts, ...newWorkouts]);
@ -71,7 +78,7 @@ export default function WorkoutList() {
const onAdd = useCallback(async () => {
navigation.navigate('EditWorkout', {
value: {name: '', sets: 3, image: '', steps: '', reps: 0, weight: 0},
value: new GymSet(),
});
}, [navigation]);

View File

@ -1,12 +1,12 @@
import {createStackNavigator} from '@react-navigation/stack';
import EditWorkout from './EditWorkout';
import Set from './set';
import GymSet from './gym-set';
import WorkoutList from './WorkoutList';
export type WorkoutsPageParams = {
WorkoutList: {};
EditWorkout: {
value: Set;
value: GymSet;
};
};

View File

@ -1,6 +1,12 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['react-native-reanimated/plugin', 'react-native-paper/babel'],
plugins: [
'@babel/plugin-transform-flow-strip-types',
['@babel/plugin-proposal-class-properties', { loose: true }],
['@babel/plugin-proposal-decorators', { legacy: true }],
'react-native-reanimated/plugin',
'react-native-paper/babel',
],
env: {
production: {
plugins: ['transform-remove-console'],

View File

@ -1,8 +1,6 @@
import {db} from './db';
import {db, setRepo} from './db';
import GymSet from './gym-set';
import {Periods} from './periods';
import Set from './set';
import {defaultSet} from './set.service';
import Volume from './volume';
export const getOneRepMax = async ({
name,
@ -29,89 +27,15 @@ export const getOneRepMax = async ({
return result.rows.raw();
};
export const getBestSet = async (name: string): Promise<Set> => {
const bestWeight = `
SELECT name, reps, unit, MAX(weight) AS weight
FROM sets
WHERE name = ?
GROUP BY name;
`;
const bestReps = `
SELECT name, MAX(reps) as reps, unit, weight, sets, minutes, seconds, image
FROM sets
WHERE name = ? AND weight = ?
GROUP BY name;
`;
const [weightResult] = await db.executeSql(bestWeight, [name]);
if (!weightResult.rows.length) return {...defaultSet};
const [repsResult] = await db.executeSql(bestReps, [
name,
weightResult.rows.item(0).weight,
]);
return repsResult.rows.item(0);
};
export const getWeightsBy = async (
name: string,
period: Periods,
): Promise<Set[]> => {
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(?, created)
`;
let difference = '-7 days';
if (period === Periods.Monthly) difference = '-1 months';
else if (period === Periods.Yearly) difference = '-1 years';
let group = '%Y-%m-%d';
if (period === Periods.Yearly) group = '%Y-%m';
const [result] = await db.executeSql(select, [name, difference, group]);
return result.rows.raw();
};
export const getVolumes = async (
name: string,
period: Periods,
): Promise<Volume[]> => {
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 getBestWeights = async (search: string): Promise<Set[]> => {
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<Set[]> => {
const select = `
SELECT name, MAX(reps) as reps, unit, weight, image
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 getBestSet = async (name: string): Promise<GymSet> => {
return setRepo
.createQueryBuilder()
.select()
.addSelect('MAX(weight)', 'weight')
.where('name = :name', {name})
.groupBy('name')
.addGroupBy('reps')
.orderBy('weight', 'DESC')
.addOrderBy('reps', 'DESC')
.getOne();
};

22
db.ts
View File

@ -3,6 +3,10 @@ import {
openDatabase,
SQLiteDatabase,
} from 'react-native-sqlite-storage';
import {DataSource} from 'typeorm';
import GymSet from './gym-set';
import {Plan} from './plan';
import Settings from './settings';
enablePromise(true);
@ -127,7 +131,25 @@ const migrations = [
export let db: SQLiteDatabase;
export const AppDataSource = new DataSource({
type: 'react-native',
database: 'massive.db',
location: 'default',
entities: [GymSet, Plan, Settings],
});
export const setRepo = AppDataSource.manager.getRepository(GymSet);
export const planRepo = AppDataSource.manager.getRepository(Plan);
export const settingsRepo = AppDataSource.manager.getRepository(Settings);
export const getNow = (): Promise<{now: string}[]> => {
return AppDataSource.manager.query(
"SELECT STRFTIME('%Y-%m-%dT%H:%M:%S','now','localtime') AS now",
);
};
export const runMigrations = async () => {
await AppDataSource.initialize();
db = await openDatabase({name: 'massive.db'});
await db.executeSql(`
CREATE TABLE IF NOT EXISTS migrations(

40
gym-set.ts Normal file
View File

@ -0,0 +1,40 @@
import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm';
@Entity('sets')
export default class GymSet {
@PrimaryGeneratedColumn()
id?: number;
@Column('text')
name: string;
@Column('int')
reps: number;
@Column('int')
weight: number;
@Column('int')
sets = 3;
@Column('int')
minutes = 3;
@Column('int')
seconds = 30;
@Column('boolean')
hidden = false;
@Column('text')
created?: string;
@Column('text')
unit?: string;
@Column('text')
image?: string;
@Column('text')
steps?: string;
}

View File

@ -1,8 +1,8 @@
import Set from './set';
import GymSet from './gym-set';
export type HomePageParams = {
Sets: {};
EditSet: {
set: Set;
set: GymSet;
};
};

View File

@ -1,7 +1,7 @@
import {AppRegistry} from 'react-native';
import 'react-native-gesture-handler';
import 'react-native-sqlite-storage';
import App from './App';
import {name as appName} from './app.json';
import {AppRegistry} from 'react-native'
import 'react-native-gesture-handler'
import 'react-native-sqlite-storage'
import App from './App'
import {name as appName} from './app.json'
AppRegistry.registerComponent(appName, () => App);
AppRegistry.registerComponent(appName, () => App)

View File

@ -11,6 +11,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --quiet"
},
"dependencies": {
"@babel/plugin-transform-flow-strip-types": "^7.19.0",
"@babel/preset-env": "^7.19.1",
"@react-native-masked-view/masked-view": "^0.2.7",
"@react-native-picker/picker": "^2.4.4",
@ -45,12 +46,15 @@
"react-native-svg-charts": "^5.4.0",
"react-native-vector-icons": "^9.2.0",
"react-native-view-shot": "^3.4.0",
"react-test-renderer": "^18.2.0"
"react-test-renderer": "^18.2.0",
"typeorm": "^0.3.10"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/plugin-proposal-decorators": "^7.20.0",
"@babel/runtime": "^7.12.5",
"@react-native-community/eslint-config": "^2.0.0",
"@types/node": "^18.11.7",
"@types/react-native": "^0.69.0",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.29.0",

View File

@ -1,5 +1,5 @@
import {Plan} from './plan';
import Set from './set';
import GymSet from './gym-set';
export type PlanPageParams = {
PlanList: {};
@ -8,6 +8,6 @@ export type PlanPageParams = {
};
StartPlan: {
plan: Plan;
set: Set;
set: GymSet;
};
};

View File

@ -1,60 +0,0 @@
import {db} from './db';
import {Plan} from './plan';
import {DAYS} from './time';
export const getPlans = async (search: string): Promise<Plan[]> => {
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 getTodaysPlan = async (): Promise<Plan[]> => {
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 updatePlanWorkouts = 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 updatePlan = 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 deletePlan = async (id: number) => {
return db.executeSql(`DELETE FROM plans WHERE id = ?`, [id]);
};
export const getAllPlans = async (): Promise<Plan[]> => {
const select = `SELECT * from plans`;
const [result] = await db.executeSql(select);
return result.rows.raw();
};

10
plan.ts
View File

@ -1,5 +1,13 @@
export interface Plan {
import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm';
@Entity('plans')
export class Plan {
@PrimaryGeneratedColumn()
id?: number;
@Column('text')
days: string;
@Column('text')
workouts: string;
}

View File

@ -1,163 +1,5 @@
import CountMany from './count-many';
import {db} from './db';
import Set from './set';
export const updateSet = async (value: Set) => {
const update = `
UPDATE sets
SET name = ?, reps = ?, weight = ?, unit = ?, image = ?
WHERE id = ?
`;
return db.executeSql(update, [
value.name,
value.reps,
value.weight,
value.unit,
value.image,
value.id,
]);
};
export const addSets = async (columns: string, values: string) => {
console.log({columns, values});
const insert = `
INSERT INTO sets(${columns})
VALUES ${values}
`;
return db.executeSql(insert);
};
export const addSet = async (value: Set) => {
const keys = Object.keys(value) as (keyof Set)[];
const questions = keys.map(() => '?').join(',');
const insert = `
INSERT INTO sets(${keys.join(',')},created)
VALUES (${questions},strftime('%Y-%m-%dT%H:%M:%S','now','localtime'))
`;
const values = keys.map(key => value[key]);
return db.executeSql(insert, values);
};
export const deleteSets = async () => {
return db.executeSql(`DELETE FROM sets`);
};
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 getAllSets = async (): Promise<Set[]> => {
const select = `SELECT * from sets`;
const [result] = await db.executeSql(select);
return result.rows.raw();
};
interface PageParams {
term: string;
limit: number;
offset: number;
}
export const getSet = async (name: string): Promise<Set> => {
const select = `
SELECT *
FROM sets
WHERE name = ?
LIMIT 1
`;
const [result] = await db.executeSql(select, [name]);
return result.rows.item(0);
};
export const getSets = async ({
term,
limit,
offset,
}: PageParams): Promise<Set[]> => {
const select = `
SELECT id, name, reps, weight, sets, minutes, seconds,
created, unit, image, steps
FROM sets
WHERE name LIKE ? AND NOT hidden
ORDER BY STRFTIME('%Y-%m-%d %H:%M', created) DESC
LIMIT ? OFFSET ?
`;
const [result] = await db.executeSql(select, [`%${term}%`, limit, offset]);
return result.rows.raw();
};
export const defaultSet: Set = {
name: '',
reps: 10,
weight: 20,
unit: 'kg',
};
export const updateManySet = async ({
oldName,
newName,
minutes,
seconds,
sets,
steps,
}: {
oldName: string;
newName: string;
minutes: string;
seconds: string;
sets: string;
steps?: string;
}) => {
const update = `
UPDATE sets SET name = ?, minutes = ?, seconds = ?, sets = ?, steps = ?
WHERE name = ?
`;
return db.executeSql(update, [
newName,
minutes,
seconds,
sets,
steps,
oldName,
]);
};
export const updateSetImage = async (name: string, image: string) => {
const update = `UPDATE sets SET image = ? WHERE name = ?`;
return db.executeSql(update, [image, name]);
};
export const getNames = async (): Promise<string[]> => {
const [result] = await db.executeSql('SELECT DISTINCT name FROM sets');
const values: {name: string}[] = result.rows.raw();
return values.map(value => value.name);
};
export const getToday = async (): Promise<Set | undefined> => {
const select = `
SELECT name, reps, weight, sets, minutes, seconds, unit, image FROM sets
WHERE NOT hidden
AND created LIKE strftime('%Y-%m-%d%%', 'now', 'localtime')
ORDER BY created DESC
LIMIT 1
`;
const [result] = await db.executeSql(select);
return result.rows.item(0);
};
export const countToday = async (name: string): Promise<number> => {
const select = `
SELECT COUNT(*) as total FROM sets
WHERE created LIKE strftime('%Y-%m-%d%%', 'now', 'localtime')
AND name = ? AND NOT hidden
`;
const [result] = await db.executeSql(select, [name]);
return Number(result.rows.item(0)?.total);
};
export const countMany = async (names: string[]): Promise<CountMany[]> => {
const questions = names.map(_ => '(?)').join(',');
@ -175,32 +17,3 @@ export const countMany = async (names: string[]): Promise<CountMany[]> => {
const [result] = await db.executeSql(select, names);
return result.rows.raw();
};
export const getDistinctSets = async ({
term,
limit,
offset,
}: PageParams): Promise<Set[]> => {
const select = `
SELECT name, image, sets, minutes, seconds, steps
FROM sets
WHERE sets.name LIKE ?
GROUP BY sets.name
ORDER BY sets.name
LIMIT ? OFFSET ?
`;
const [result] = await db.executeSql(select, [term, limit, offset]);
return result.rows.raw();
};
export const deleteFirst = async (name: string) => {
const remove = `
DELETE FROM sets WHERE id IN (
SELECT id FROM sets
WHERE name = ?
ORDER BY created DESC
LIMIT 1
)
`;
return db.executeSql(remove, [name]);
};

14
set.ts
View File

@ -1,14 +0,0 @@
export default interface Set {
id?: number;
name: string;
reps: number;
weight: number;
sets?: number;
minutes?: number;
seconds?: number;
created?: string;
unit?: string;
hidden?: boolean;
image?: string;
steps?: string;
}

View File

@ -1,23 +0,0 @@
import {db} from './db';
import Settings from './settings';
export const getSettings = async (): Promise<Settings> => {
const [result] = await db.executeSql(`SELECT * FROM settings LIMIT 1`);
return result.rows.item(0);
};
export const updateSettings = async (value: Settings) => {
console.log(`${updateSettings.name}`, {value});
const keys = Object.keys(value) as (keyof Settings)[];
const sets = keys.map(key => `${key}=?`).join(',');
const update = `UPDATE settings SET ${sets}`;
const values = keys.map(key => value[key]);
return db.executeSql(update, values);
};
export const getNext = async (): Promise<string | undefined> => {
const [result] = await db.executeSql(
`SELECT nextAlarm FROM settings LIMIT 1`,
);
return result.rows.item(0)?.nextAlarm;
};

View File

@ -1,15 +1,43 @@
export default interface Settings {
alarm: number;
vibrate: number;
import {Column, Entity, PrimaryColumn} from 'typeorm';
@Entity()
export default class Settings {
@PrimaryColumn('boolean')
alarm: boolean;
@Column('boolean')
vibrate: boolean;
@Column('text')
sound: string;
notify: number;
images: number;
showUnit: number;
@Column('boolean')
notify: boolean;
@Column('boolean')
images: boolean;
@Column('boolean')
showUnit: boolean;
@Column('text')
color: string;
steps: number;
@Column('boolean')
steps: boolean;
@Column('text')
date: string;
showDate: number;
theme: 'system' | 'dark' | 'light';
showSets: number;
noSound: number;
@Column('boolean')
showDate: boolean;
@Column('text')
theme: string;
@Column('boolean')
showSets: boolean;
@Column('boolean')
noSound: boolean;
}

View File

@ -13,8 +13,10 @@
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
"strict": false,
"skipLibCheck": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
"exclude": [
"node_modules",

304
yarn.lock
View File

@ -242,7 +242,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/helper-replace-supers@npm:^7.18.6, @babel/helper-replace-supers@npm:^7.18.9":
"@babel/helper-replace-supers@npm:^7.18.6, @babel/helper-replace-supers@npm:^7.18.9, @babel/helper-replace-supers@npm:^7.19.1":
version: 7.19.1
resolution: "@babel/helper-replace-supers@npm:7.19.1"
dependencies:
@ -409,6 +409,21 @@ __metadata:
languageName: node
linkType: hard
"@babel/plugin-proposal-decorators@npm:^7.20.0":
version: 7.20.0
resolution: "@babel/plugin-proposal-decorators@npm:7.20.0"
dependencies:
"@babel/helper-create-class-features-plugin": ^7.19.0
"@babel/helper-plugin-utils": ^7.19.0
"@babel/helper-replace-supers": ^7.19.1
"@babel/helper-split-export-declaration": ^7.18.6
"@babel/plugin-syntax-decorators": ^7.19.0
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: b882bf758c9e098ead68a05e3e912f427a5b286c7174a582ccf38cf02a679ce1372282a45fb1987b2ffd7e9923fc38e6913fa54e165f5640c334c2c6a74e761f
languageName: node
linkType: hard
"@babel/plugin-proposal-dynamic-import@npm:^7.18.6":
version: 7.18.6
resolution: "@babel/plugin-proposal-dynamic-import@npm:7.18.6"
@ -615,6 +630,17 @@ __metadata:
languageName: node
linkType: hard
"@babel/plugin-syntax-decorators@npm:^7.19.0":
version: 7.19.0
resolution: "@babel/plugin-syntax-decorators@npm:7.19.0"
dependencies:
"@babel/helper-plugin-utils": ^7.19.0
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: 105a13d581a8643ba145d4d0d31f34a492b352defa5b155e785702da6ce9c3ff0c1843ba9bee176e35f6e38afa19dc7bd12c120220af0495de4b128f1dd27f6e
languageName: node
linkType: hard
"@babel/plugin-syntax-dynamic-import@npm:^7.0.0, @babel/plugin-syntax-dynamic-import@npm:^7.8.3":
version: 7.8.3
resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3"
@ -924,7 +950,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/plugin-transform-flow-strip-types@npm:^7.0.0, @babel/plugin-transform-flow-strip-types@npm:^7.18.6":
"@babel/plugin-transform-flow-strip-types@npm:^7.0.0, @babel/plugin-transform-flow-strip-types@npm:^7.18.6, @babel/plugin-transform-flow-strip-types@npm:^7.19.0":
version: 7.19.0
resolution: "@babel/plugin-transform-flow-strip-types@npm:7.19.0"
dependencies:
@ -2342,6 +2368,13 @@ __metadata:
languageName: node
linkType: hard
"@sqltools/formatter@npm:^1.2.2":
version: 1.2.5
resolution: "@sqltools/formatter@npm:1.2.5"
checksum: 9b8354e715467d660daa5afe044860b5686bbb1a5cb67a60866b932effafbf5e8b429f19a8ae67cd412065a4f067161f227e182f3664a0245339d5eb1e26e355
languageName: node
linkType: hard
"@testing-library/jest-native@npm:^5.1.2":
version: 5.1.2
resolution: "@testing-library/jest-native@npm:5.1.2"
@ -2544,7 +2577,7 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:*":
"@types/node@npm:*, @types/node@npm:^18.11.7":
version: 18.11.7
resolution: "@types/node@npm:18.11.7"
checksum: 69d630825cf6fbf580d08d76a4d4836ef8c34ed4fe0842221ade87d275f517099cbfabe8e22397208e564bd24926db97699ab9db5c091383269a432b336665e2
@ -3054,6 +3087,13 @@ __metadata:
languageName: node
linkType: hard
"any-promise@npm:^1.0.0":
version: 1.3.0
resolution: "any-promise@npm:1.3.0"
checksum: 0ee8a9bdbe882c90464d75d1f55cf027f5458650c4bd1f0467e65aec38ccccda07ca5844969ee77ed46d04e7dded3eaceb027e8d32f385688523fe305fa7e1de
languageName: node
linkType: hard
"anymatch@npm:^3.0.3":
version: 3.1.2
resolution: "anymatch@npm:3.1.2"
@ -3064,6 +3104,13 @@ __metadata:
languageName: node
linkType: hard
"app-root-path@npm:^3.0.0":
version: 3.1.0
resolution: "app-root-path@npm:3.1.0"
checksum: e3db3957aee197143a0f6c75e39fe89b19e7244f28b4f2944f7276a9c526d2a7ab2d115b4b2d70a51a65a9a3ca17506690e5b36f75a068a7e5a13f8c092389ba
languageName: node
linkType: hard
"appdirsjs@npm:^1.2.4":
version: 1.2.7
resolution: "appdirsjs@npm:1.2.7"
@ -3538,6 +3585,16 @@ __metadata:
languageName: node
linkType: hard
"buffer@npm:^6.0.3":
version: 6.0.3
resolution: "buffer@npm:6.0.3"
dependencies:
base64-js: ^1.3.1
ieee754: ^1.2.1
checksum: 5ad23293d9a731e4318e420025800b42bf0d264004c0286c8cc010af7a270c7a0f6522e84f54b9ad65cbd6db20b8badbfd8d2ebf4f80fa03dab093b89e68c3f9
languageName: node
linkType: hard
"bytes@npm:3.0.0":
version: 3.0.0
resolution: "bytes@npm:3.0.0"
@ -3735,6 +3792,22 @@ __metadata:
languageName: node
linkType: hard
"cli-highlight@npm:^2.1.11":
version: 2.1.11
resolution: "cli-highlight@npm:2.1.11"
dependencies:
chalk: ^4.0.0
highlight.js: ^10.7.1
mz: ^2.4.0
parse5: ^5.1.1
parse5-htmlparser2-tree-adapter: ^6.0.0
yargs: ^16.0.0
bin:
highlight: bin/highlight
checksum: 0a60e60545e39efea78c1732a25b91692017ec40fb6e9497208dc0eeeae69991d3923a8d6e4edd0543db3c395ed14529a33dd4d0353f1679c5b6dded792a8496
languageName: node
linkType: hard
"cli-spinners@npm:^2.5.0":
version: 2.7.0
resolution: "cli-spinners@npm:2.7.0"
@ -4186,6 +4259,13 @@ __metadata:
languageName: node
linkType: hard
"date-fns@npm:^2.28.0":
version: 2.29.3
resolution: "date-fns@npm:2.29.3"
checksum: e01cf5b62af04e05dfff921bb9c9933310ed0e1ae9a81eb8653452e64dc841acf7f6e01e1a5ae5644d0337e9a7f936175fd2cb6819dc122fdd9c5e86c56be484
languageName: node
linkType: hard
"dayjs@npm:^1.8.15":
version: 1.11.6
resolution: "dayjs@npm:1.11.6"
@ -4417,6 +4497,13 @@ __metadata:
languageName: node
linkType: hard
"dotenv@npm:^16.0.0":
version: 16.0.3
resolution: "dotenv@npm:16.0.3"
checksum: afcf03f373d7a6d62c7e9afea6328e62851d627a4e73f2e12d0a8deae1cd375892004f3021883f8aec85932cd2834b091f568ced92b4774625b321db83b827f8
languageName: node
linkType: hard
"ee-first@npm:1.1.1":
version: 1.1.1
resolution: "ee-first@npm:1.1.1"
@ -5425,7 +5512,7 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6":
"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.2.0":
version: 7.2.3
resolution: "glob@npm:7.2.3"
dependencies:
@ -5622,6 +5709,13 @@ __metadata:
languageName: node
linkType: hard
"highlight.js@npm:^10.7.1":
version: 10.7.3
resolution: "highlight.js@npm:10.7.3"
checksum: defeafcd546b535d710d8efb8e650af9e3b369ef53e28c3dc7893eacfe263200bba4c5fcf43524ae66d5c0c296b1af0870523ceae3e3104d24b7abf6374a4fea
languageName: node
linkType: hard
"hoist-non-react-statics@npm:^3.3.0":
version: 3.3.2
resolution: "hoist-non-react-statics@npm:3.3.2"
@ -5704,7 +5798,7 @@ __metadata:
languageName: node
linkType: hard
"ieee754@npm:^1.1.13":
"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1":
version: 1.2.1
resolution: "ieee754@npm:1.2.1"
checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e
@ -5790,7 +5884,7 @@ __metadata:
languageName: node
linkType: hard
"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3":
"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3":
version: 2.0.4
resolution: "inherits@npm:2.0.4"
checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1
@ -7182,6 +7276,8 @@ __metadata:
resolution: "massive@workspace:."
dependencies:
"@babel/core": ^7.12.9
"@babel/plugin-proposal-decorators": ^7.20.0
"@babel/plugin-transform-flow-strip-types": ^7.19.0
"@babel/preset-env": ^7.19.1
"@babel/runtime": ^7.12.5
"@react-native-community/eslint-config": ^2.0.0
@ -7194,6 +7290,7 @@ __metadata:
"@testing-library/react-native": ^11.3.0
"@types/d3-shape": ^3.1.0
"@types/jest": ^29.2.0
"@types/node": ^18.11.7
"@types/react-native": ^0.69.0
"@types/react-native-sqlite-storage": ^5.0.2
"@types/react-native-svg-charts": ^5.0.12
@ -7225,6 +7322,7 @@ __metadata:
react-native-vector-icons: ^9.2.0
react-native-view-shot: ^3.4.0
react-test-renderer: ^18.2.0
typeorm: ^0.3.10
typescript: ^4.8.4
languageName: unknown
linkType: soft
@ -7848,6 +7946,17 @@ __metadata:
languageName: node
linkType: hard
"mz@npm:^2.4.0":
version: 2.7.0
resolution: "mz@npm:2.7.0"
dependencies:
any-promise: ^1.0.0
object-assign: ^4.0.1
thenify-all: ^1.0.0
checksum: 8427de0ece99a07e9faed3c0c6778820d7543e3776f9a84d22cf0ec0a8eb65f6e9aee9c9d353ff9a105ff62d33a9463c6ca638974cc652ee8140cd1e35951c87
languageName: node
linkType: hard
"nanoid@npm:^3.1.23":
version: 3.3.4
resolution: "nanoid@npm:3.3.4"
@ -8046,7 +8155,7 @@ __metadata:
languageName: node
linkType: hard
"object-assign@npm:^4.1.1":
"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1":
version: 4.1.1
resolution: "object-assign@npm:4.1.1"
checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f
@ -8340,6 +8449,29 @@ __metadata:
languageName: node
linkType: hard
"parse5-htmlparser2-tree-adapter@npm:^6.0.0":
version: 6.0.1
resolution: "parse5-htmlparser2-tree-adapter@npm:6.0.1"
dependencies:
parse5: ^6.0.1
checksum: 1848378b355d027915645c13f13f982e60502d201f53bc2067a508bf2dba4aac08219fc781dcd160167f5f50f0c73f58d20fa4fb3d90ee46762c20234fa90a6d
languageName: node
linkType: hard
"parse5@npm:^5.1.1":
version: 5.1.1
resolution: "parse5@npm:5.1.1"
checksum: 613a714af4c1101d1cb9f7cece2558e35b9ae8a0c03518223a4a1e35494624d9a9ad5fad4c13eab66a0e0adccd9aa3d522fc8f5f9cc19789e0579f3fa0bdfc65
languageName: node
linkType: hard
"parse5@npm:^6.0.1":
version: 6.0.1
resolution: "parse5@npm:6.0.1"
checksum: 7d569a176c5460897f7c8f3377eff640d54132b9be51ae8a8fa4979af940830b2b0c296ce75e5bd8f4041520aadde13170dbdec44889975f906098ea0002f4bd
languageName: node
linkType: hard
"parseurl@npm:~1.3.3":
version: 1.3.3
resolution: "parseurl@npm:1.3.3"
@ -9021,6 +9153,13 @@ __metadata:
languageName: node
linkType: hard
"reflect-metadata@npm:^0.1.13":
version: 0.1.13
resolution: "reflect-metadata@npm:0.1.13"
checksum: 798d379a7b6f6455501145419505c97dd11cbc23857a386add2b9ef15963ccf15a48d9d15507afe01d4cd74116df8a213247200bac00320bd7c11ddeaa5e8fb4
languageName: node
linkType: hard
"regenerate-unicode-properties@npm:^10.1.0":
version: 10.1.0
resolution: "regenerate-unicode-properties@npm:10.1.0"
@ -9326,7 +9465,7 @@ __metadata:
languageName: node
linkType: hard
"safe-buffer@npm:~5.2.0":
"safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0":
version: 5.2.1
resolution: "safe-buffer@npm:5.2.1"
checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491
@ -9360,6 +9499,13 @@ __metadata:
languageName: node
linkType: hard
"sax@npm:>=0.6.0":
version: 1.2.4
resolution: "sax@npm:1.2.4"
checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe
languageName: node
linkType: hard
"scheduler@npm:^0.22.0":
version: 0.22.0
resolution: "scheduler@npm:0.22.0"
@ -9480,6 +9626,18 @@ __metadata:
languageName: node
linkType: hard
"sha.js@npm:^2.4.11":
version: 2.4.11
resolution: "sha.js@npm:2.4.11"
dependencies:
inherits: ^2.0.1
safe-buffer: ^5.0.1
bin:
sha.js: ./bin.js
checksum: ebd3f59d4b799000699097dadb831c8e3da3eb579144fd7eb7a19484cbcbb7aca3c68ba2bb362242eb09e33217de3b4ea56e4678184c334323eca24a58e3ad07
languageName: node
linkType: hard
"shallow-clone@npm:^3.0.0":
version: 3.0.1
resolution: "shallow-clone@npm:3.0.1"
@ -10031,6 +10189,24 @@ __metadata:
languageName: node
linkType: hard
"thenify-all@npm:^1.0.0":
version: 1.6.0
resolution: "thenify-all@npm:1.6.0"
dependencies:
thenify: ">= 3.1.0 < 4"
checksum: dba7cc8a23a154cdcb6acb7f51d61511c37a6b077ec5ab5da6e8b874272015937788402fd271fdfc5f187f8cb0948e38d0a42dcc89d554d731652ab458f5343e
languageName: node
linkType: hard
"thenify@npm:>= 3.1.0 < 4":
version: 3.3.1
resolution: "thenify@npm:3.3.1"
dependencies:
any-promise: ^1.0.0
checksum: 84e1b804bfec49f3531215f17b4a6e50fd4397b5f7c1bccc427b9c656e1ecfb13ea79d899930184f78bc2f57285c54d9a50a590c8868f4f0cef5c1d9f898b05e
languageName: node
linkType: hard
"throat@npm:^5.0.0":
version: 5.0.0
resolution: "throat@npm:5.0.0"
@ -10123,7 +10299,7 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:^2.0.1":
"tslib@npm:^2.0.1, tslib@npm:^2.3.1":
version: 2.4.0
resolution: "tslib@npm:2.4.0"
checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113
@ -10178,6 +10354,88 @@ __metadata:
languageName: node
linkType: hard
"typeorm@npm:^0.3.10":
version: 0.3.10
resolution: "typeorm@npm:0.3.10"
dependencies:
"@sqltools/formatter": ^1.2.2
app-root-path: ^3.0.0
buffer: ^6.0.3
chalk: ^4.1.0
cli-highlight: ^2.1.11
date-fns: ^2.28.0
debug: ^4.3.3
dotenv: ^16.0.0
glob: ^7.2.0
js-yaml: ^4.1.0
mkdirp: ^1.0.4
reflect-metadata: ^0.1.13
sha.js: ^2.4.11
tslib: ^2.3.1
uuid: ^8.3.2
xml2js: ^0.4.23
yargs: ^17.3.1
peerDependencies:
"@google-cloud/spanner": ^5.18.0
"@sap/hana-client": ^2.12.25
better-sqlite3: ^7.1.2
hdb-pool: ^0.1.6
ioredis: ^5.0.4
mongodb: ^3.6.0
mssql: ^7.3.0
mysql2: ^2.2.5
oracledb: ^5.1.0
pg: ^8.5.1
pg-native: ^3.0.0
pg-query-stream: ^4.0.0
redis: ^3.1.1 || ^4.0.0
sql.js: ^1.4.0
sqlite3: ^5.0.3
ts-node: ^10.7.0
typeorm-aurora-data-api-driver: ^2.0.0
peerDependenciesMeta:
"@google-cloud/spanner":
optional: true
"@sap/hana-client":
optional: true
better-sqlite3:
optional: true
hdb-pool:
optional: true
ioredis:
optional: true
mongodb:
optional: true
mssql:
optional: true
mysql2:
optional: true
oracledb:
optional: true
pg:
optional: true
pg-native:
optional: true
pg-query-stream:
optional: true
redis:
optional: true
sql.js:
optional: true
sqlite3:
optional: true
ts-node:
optional: true
typeorm-aurora-data-api-driver:
optional: true
bin:
typeorm: cli.js
typeorm-ts-node-commonjs: cli-ts-node-commonjs.js
typeorm-ts-node-esm: cli-ts-node-esm.js
checksum: 8a4ce43b427e9224d9e8f278d8c490d116f04d3cd7cd3ac9e4ed7e0541f82b3c88925194cbf38533dc6d9adf40886778f3caf0565aa50998454bb2047e8afe1f
languageName: node
linkType: hard
"typescript@npm:^4.8.4":
version: 4.8.4
resolution: "typescript@npm:4.8.4"
@ -10374,6 +10632,15 @@ __metadata:
languageName: node
linkType: hard
"uuid@npm:^8.3.2":
version: 8.3.2
resolution: "uuid@npm:8.3.2"
bin:
uuid: dist/bin/uuid
checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df
languageName: node
linkType: hard
"v8-to-istanbul@npm:^9.0.1":
version: 9.0.1
resolution: "v8-to-istanbul@npm:9.0.1"
@ -10580,6 +10847,23 @@ __metadata:
languageName: node
linkType: hard
"xml2js@npm:^0.4.23":
version: 0.4.23
resolution: "xml2js@npm:0.4.23"
dependencies:
sax: ">=0.6.0"
xmlbuilder: ~11.0.0
checksum: ca0cf2dfbf6deeaae878a891c8fbc0db6fd04398087084edf143cdc83d0509ad0fe199b890f62f39c4415cf60268a27a6aed0d343f0658f8779bd7add690fa98
languageName: node
linkType: hard
"xmlbuilder@npm:~11.0.0":
version: 11.0.1
resolution: "xmlbuilder@npm:11.0.1"
checksum: 7152695e16f1a9976658215abab27e55d08b1b97bca901d58b048d2b6e106b5af31efccbdecf9b07af37c8377d8e7e821b494af10b3a68b0ff4ae60331b415b0
languageName: node
linkType: hard
"xtend@npm:~4.0.1":
version: 4.0.2
resolution: "xtend@npm:4.0.2"
@ -10651,7 +10935,7 @@ __metadata:
languageName: node
linkType: hard
"yargs@npm:^16.1.1":
"yargs@npm:^16.0.0, yargs@npm:^16.1.1":
version: 16.2.0
resolution: "yargs@npm:16.2.0"
dependencies: