Add weight page

This commit is contained in:
Brandon Presley 2023-10-21 11:57:31 +13:00
parent 7928cab4c1
commit ff7cd2fe54
12 changed files with 282 additions and 344 deletions

View File

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

View File

@ -8,130 +8,53 @@ import {
} from "@react-navigation/native"; } from "@react-navigation/native";
import { format } from "date-fns"; import { format } from "date-fns";
import { useCallback, useRef, useState } from "react"; import { useCallback, useRef, useState } from "react";
import { NativeModules, TextInput, View } from "react-native"; import { TextInput, View } from "react-native";
import DocumentPicker from "react-native-document-picker"; import { Button, IconButton } from "react-native-paper";
import { Button, Card, IconButton, TouchableRipple } from "react-native-paper";
import AppInput from "./AppInput"; import AppInput from "./AppInput";
import ConfirmDialog from "./ConfirmDialog"; import ConfirmDialog from "./ConfirmDialog";
import { MARGIN, PADDING } from "./constants"; import { MARGIN, PADDING } from "./constants";
import { getNow, setRepo, settingsRepo } from "./db"; import { getNow, settingsRepo, weightRepo } from "./db";
import GymSet, {
GYM_SET_CREATED,
GYM_SET_DELETED,
GYM_SET_UPDATED,
} from "./gym-set";
import { HomePageParams } from "./home-page-params";
import Settings from "./settings"; import Settings from "./settings";
import StackHeader from "./StackHeader"; import StackHeader from "./StackHeader";
import { toast } from "./toast"; import Weight from "./weight";
import { fixNumeric } from "./fix-numeric"; import { WeightPageParams } from "./WeightPage";
import { emitter } from "./emitter";
export default function EditWeight() { export default function EditWeight() {
const { params } = useRoute<RouteProp<HomePageParams, "EditSet">>(); const { params } = useRoute<RouteProp<WeightPageParams, "EditWeight">>();
const { set } = params; const { weight } = params;
const { navigate } = useNavigation<NavigationProp<HomePageParams>>(); const { navigate } = useNavigation<NavigationProp<WeightPageParams>>();
const [settings, setSettings] = useState<Settings>({} as Settings); const [settings, setSettings] = useState<Settings>({} as Settings);
const [name, setName] = useState(set.name); const [value, setValue] = useState(weight.value?.toString());
const [reps, setReps] = useState(set.reps?.toString()); const [unit, setUnit] = useState(weight.unit);
const [weight, setWeight] = useState(set.weight?.toString());
const [newImage, setNewImage] = useState(set.image);
const [unit, setUnit] = useState(set.unit);
const [showDelete, setShowDelete] = useState(false);
const [created, setCreated] = useState<Date>( const [created, setCreated] = useState<Date>(
set.created ? new Date(set.created) : new Date() weight.created ? new Date(weight.created) : new Date()
); );
const [showDelete, setShowDelete] = useState(false);
const [createdDirty, setCreatedDirty] = useState(false); const [createdDirty, setCreatedDirty] = useState(false);
const [showRemove, setShowRemove] = useState(false);
const [removeImage, setRemoveImage] = useState(false);
const weightRef = useRef<TextInput>(null);
const repsRef = useRef<TextInput>(null);
const unitRef = useRef<TextInput>(null); const unitRef = useRef<TextInput>(null);
const [selection, setSelection] = useState({
start: 0,
end: set.reps?.toString().length,
});
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
settingsRepo.findOne({ where: {} }).then(setSettings); settingsRepo.findOne({ where: {} }).then(setSettings);
}, []) }, [])
); );
const startTimer = useCallback(
async (value: string) => {
if (!settings.alarm) return;
const first = await setRepo.findOne({ where: { name: value } });
const milliseconds =
(first?.minutes ?? 3) * 60 * 1000 + (first?.seconds ?? 0) * 1000;
if (milliseconds) NativeModules.AlarmModule.timer(milliseconds);
},
[settings]
);
const notify = (value: Partial<GymSet>) => {
if (!settings.notify) return navigate("Sets");
if (
value.weight > set.weight ||
(value.reps > set.reps && value.weight === set.weight)
) {
toast("Great work King! That's a new record.");
}
};
const added = async (value: GymSet) => {
console.log(`${EditSet.name}.added:`, value);
emitter.emit(GYM_SET_CREATED);
startTimer(value.name);
};
const handleSubmit = async () => { const handleSubmit = async () => {
if (!name) return; if (!value) return;
const newSet: Partial<GymSet> = { const newWeight: Partial<Weight> = {
id: set.id, id: weight.id,
name, value: Number(value),
reps: Number(reps),
weight: Number(weight),
unit, unit,
minutes: Number(set.minutes ?? 3),
seconds: Number(set.seconds ?? 30),
sets: set.sets ?? 3,
hidden: false,
}; };
newSet.image = newImage; if (createdDirty) newWeight.created = created.toISOString();
if (!newImage && !removeImage) { else if (typeof weight.id !== "number") newWeight.created = await getNow();
newSet.image = await setRepo
.findOne({ where: { name } })
.then((s) => s?.image);
}
if (createdDirty) newSet.created = created.toISOString(); await weightRepo.save(newWeight);
if (typeof set.id !== "number") newSet.created = await getNow(); navigate("Weights");
const saved = await setRepo.save(newSet);
notify(newSet);
if (typeof set.id !== "number") return added(saved);
else emitter.emit(GYM_SET_UPDATED, saved);
navigate("Sets");
}; };
const changeImage = useCallback(async () => {
const { fileCopyUri } = await DocumentPicker.pickSingle({
type: DocumentPicker.types.images,
copyTo: "documentDirectory",
});
if (fileCopyUri) setNewImage(fileCopyUri);
}, []);
const handleRemove = useCallback(async () => {
setNewImage("");
setRemoveImage(true);
setShowRemove(false);
}, []);
const pickDate = useCallback(() => { const pickDate = useCallback(() => {
DateTimePickerAndroid.open({ DateTimePickerAndroid.open({
value: created, value: created,
@ -139,108 +62,44 @@ export default function EditWeight() {
if (date === created) return; if (date === created) return;
setCreated(date); setCreated(date);
setCreatedDirty(true); setCreatedDirty(true);
DateTimePickerAndroid.open({
value: date,
onChange: (__, time) => setCreated(time),
mode: "time",
});
}, },
mode: "date", mode: "date",
}); });
}, [created]); }, [created]);
const remove = async () => { const remove = async () => {
await setRepo.delete(set.id); if (!weight.id) return;
emitter.emit(GYM_SET_DELETED); await weightRepo.delete(weight.id);
navigate("Sets"); navigate("Weights");
}; };
return ( return (
<> <>
<StackHeader title={typeof set.id === "number" ? "Edit set" : "Add set"}> <StackHeader
{typeof set.id === "number" ? ( title={typeof weight.id === "number" ? "Edit weight" : "Add weight"}
>
{typeof weight.id === "number" ? (
<IconButton onPress={() => setShowDelete(true)} icon="delete" /> <IconButton onPress={() => setShowDelete(true)} icon="delete" />
) : null} ) : null}
</StackHeader> </StackHeader>
<ConfirmDialog <ConfirmDialog
title="Delete set" title="Delete weight"
show={showDelete} show={showDelete}
onOk={remove} onOk={remove}
setShow={setShowDelete} setShow={setShowDelete}
> >
<>Are you sure you want to delete {name}</> <>Are you sure you want to delete {value}</>
</ConfirmDialog> </ConfirmDialog>
<View style={{ padding: PADDING, flex: 1 }}> <View style={{ padding: PADDING, flex: 1 }}>
<AppInput <AppInput
label="Name" label="Value"
value={name} value={value}
onChangeText={setName} onChangeText={setValue}
autoCorrect={false} keyboardType="numeric"
autoFocus={!name} onSubmitEditing={() => unitRef.current?.focus()}
onSubmitEditing={() => repsRef.current?.focus()}
/> />
<View style={{ flexDirection: "row" }}>
<AppInput
style={{
flex: 1,
marginBottom: MARGIN,
}}
label="Reps"
keyboardType="numeric"
value={reps}
onChangeText={(newReps) => {
const fixed = fixNumeric(newReps);
setReps(fixed);
if (fixed.length !== newReps.length)
toast("Reps must be a number");
}}
onSubmitEditing={() => weightRef.current?.focus()}
selection={selection}
onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
innerRef={repsRef}
/>
<IconButton
icon="add"
onPress={() => setReps((Number(reps) + 1).toString())}
/>
<IconButton
icon="remove"
onPress={() => setReps((Number(reps) - 1).toString())}
/>
</View>
<View
style={{
flexDirection: "row",
marginBottom: MARGIN,
}}
>
<AppInput
style={{ flex: 1 }}
label="Weight"
keyboardType="numeric"
value={weight}
onChangeText={(newWeight) => {
const fixed = fixNumeric(newWeight);
setWeight(fixed);
if (fixed.length !== newWeight.length)
toast("Weight must be a number");
}}
onSubmitEditing={handleSubmit}
innerRef={weightRef}
/>
<IconButton
icon="add"
onPress={() => setWeight((Number(weight) + 2.5).toString())}
/>
<IconButton
icon="remove"
onPress={() => setWeight((Number(weight) - 2.5).toString())}
/>
</View>
{settings.showUnit && ( {settings.showUnit && (
<AppInput <AppInput
autoCapitalize="none" autoCapitalize="none"
@ -258,46 +117,17 @@ export default function EditWeight() {
onPressOut={pickDate} onPressOut={pickDate}
/> />
)} )}
{settings.images && newImage && (
<TouchableRipple
style={{ marginBottom: MARGIN }}
onPress={changeImage}
onLongPress={() => setShowRemove(true)}
>
<Card.Cover source={{ uri: newImage }} />
</TouchableRipple>
)}
{settings.images && !newImage && (
<Button
style={{ marginBottom: MARGIN }}
onPress={changeImage}
icon="add-photo-alternate"
>
Image
</Button>
)}
</View> </View>
<Button <Button
disabled={!name} disabled={!value}
mode="outlined" mode="outlined"
icon="save" icon="content-save"
style={{ margin: MARGIN }} style={{ margin: MARGIN }}
onPress={handleSubmit} onPress={handleSubmit}
> >
Save Save
</Button> </Button>
<ConfirmDialog
title="Remove image"
onOk={handleRemove}
show={showRemove}
setShow={setShowRemove}
>
Are you sure you want to remove the image?
</ConfirmDialog>
</> </>
); );
} }

View File

@ -36,7 +36,9 @@ export default function Routes() {
<Drawer.Screen <Drawer.Screen
name="Graphs" name="Graphs"
component={GraphsPage} component={GraphsPage}
options={{ drawerIcon: () => <IconButton icon="chart-line" /> }} options={{
drawerIcon: () => <IconButton icon="chart-bell-curve-cumulative" />,
}}
/> />
<Drawer.Screen <Drawer.Screen
name="Workouts" name="Workouts"

View File

@ -68,10 +68,10 @@ export default function StartPlanItem(props: Props) {
navigate("EditSet", { set: first }); navigate("EditSet", { set: first });
}, [item.name, navigate]); }, [item.name, navigate]);
const view = () => { const view = useCallback(() => {
setShowMenu(false); setShowMenu(false);
navigateHome("Sets", { search: item.name }); navigateHome("Sets", { search: item.name });
}; }, [item.name, navigateHome]);
const left = useCallback( const left = useCallback(
() => ( () => (
@ -106,7 +106,7 @@ export default function StartPlanItem(props: Props) {
</Menu> </Menu>
</View> </View>
), ),
[anchor, showMenu, edit, undo] [anchor, showMenu, edit, undo, view]
); );
return ( return (

90
ViewWeightGraph.tsx Normal file
View File

@ -0,0 +1,90 @@
import { format } from "date-fns";
import { useEffect, useMemo, useState } from "react";
import { View } from "react-native";
import { FileSystem } from "react-native-file-access";
import { IconButton, List } from "react-native-paper";
import Share from "react-native-share";
import { captureScreen } from "react-native-view-shot";
import Chart from "./Chart";
import { PADDING } from "./constants";
import { weightRepo } from "./db";
import { Periods } from "./periods";
import Select from "./Select";
import StackHeader from "./StackHeader";
import Weight from "./weight";
export default function ViewWeightGraph() {
const [weights, setWeights] = useState<Weight[]>();
const [period, setPeriod] = useState(Periods.Monthly);
useEffect(() => {
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";
weightRepo
.createQueryBuilder()
.select("STRFTIME('%Y-%m-%d', created)", "created")
.addSelect("unit")
.addSelect("value")
.where("DATE(created) >= DATE('now', 'weekday 0', :difference)", {
difference,
})
.groupBy(`STRFTIME('${group}', created)`)
.getRawMany()
.then(setWeights);
}, [period]);
const charts = useMemo(() => {
if (weights?.length === 0) {
return <List.Item title="No data yet." />;
}
return (
<Chart
yData={weights?.map((set) => set.value) || []}
yFormat={(value) => `${value}${weights?.[0].unit}`}
xData={weights || []}
xFormat={(_value, index) =>
format(new Date(weights?.[index].created), "d/M")
}
/>
);
}, [weights]);
return (
<>
<StackHeader title="Weight graph">
<IconButton
onPress={() =>
captureScreen().then(async (uri) => {
const base64 = await FileSystem.readFile(uri, "base64");
const url = `data:image/jpeg;base64,${base64}`;
Share.open({
type: "image/jpeg",
url,
});
})
}
icon="share"
/>
</StackHeader>
<View style={{ padding: PADDING }}>
<Select
label="Period"
items={[
{ value: Periods.Weekly, label: Periods.Weekly },
{ value: Periods.Monthly, label: Periods.Monthly },
{ value: Periods.Yearly, label: Periods.Yearly },
]}
onChange={(value) => setPeriod(value as Periods)}
value={period}
/>
{charts}
</View>
</>
);
}

33
WeightItem.tsx Normal file
View File

@ -0,0 +1,33 @@
import { NavigationProp, useNavigation } from "@react-navigation/native";
import { format } from "date-fns";
import { useCallback } from "react";
import { List, Text } from "react-native-paper";
import Settings from "./settings";
import Weight from "./weight";
import { WeightPageParams } from "./WeightPage";
export default function WeightItem({
item,
settings,
}: {
item: Weight;
settings: Settings;
}) {
const navigation = useNavigation<NavigationProp<WeightPageParams>>();
const press = useCallback(() => {
navigation.navigate("EditWeight", { weight: item });
}, [item, navigation]);
const description = useCallback(() => {
return <Text>{format(new Date(item.created), settings.date || "P")}</Text>;
}, [item.created, settings.date]);
return (
<List.Item
onPress={press}
title={`${item.value}${item.unit || "kg"}`}
description={description}
/>
);
}

View File

@ -1,174 +1,114 @@
import { import {
NavigationProp, NavigationProp,
RouteProp, useFocusEffect,
useNavigation, useNavigation,
useRoute,
} from "@react-navigation/native"; } from "@react-navigation/native";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useState } from "react";
import { FlatList } from "react-native"; import { FlatList } from "react-native";
import { List } from "react-native-paper"; import { IconButton, List } from "react-native-paper";
import { Like } from "typeorm"; import { Like } from "typeorm";
import { LIMIT } from "./constants"; import { LIMIT } from "./constants";
import { getNow, setRepo, settingsRepo } from "./db"; import { getNow, settingsRepo, weightRepo } from "./db";
import DrawerHeader from "./DrawerHeader"; import DrawerHeader from "./DrawerHeader";
import { emitter } from "./emitter";
import GymSet, {
defaultSet,
GYM_SET_CREATED,
GYM_SET_DELETED,
GYM_SET_UPDATED,
} from "./gym-set";
import { HomePageParams } from "./home-page-params";
import ListMenu from "./ListMenu";
import Page from "./Page"; import Page from "./Page";
import SetItem from "./SetItem"; import Settings from "./settings";
import Settings, { SETTINGS } from "./settings"; import { default as Weight, defaultWeight } from "./weight";
import WeightItem from "./WeightItem";
import { WeightPageParams } from "./WeightPage";
export default function WeightList() { export default function WeightList() {
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [sets, setSets] = useState<GymSet[]>(); const [weights, setWeights] = useState<Weight[]>();
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
const [end, setEnd] = useState(false); const [end, setEnd] = useState(false);
const [settings, setSettings] = useState<Settings>(); const [settings, setSettings] = useState<Settings>();
const [ids, setIds] = useState<number[]>([]); const { navigate } = useNavigation<NavigationProp<WeightPageParams>>();
const navigation = useNavigation<NavigationProp<HomePageParams>>(); const [term, setTerm] = useState("");
const { params } = useRoute<RouteProp<HomePageParams, "Sets">>();
const [term, setTerm] = useState(params?.search || "");
const reset = useCallback( const reset = useCallback(
async (value: string) => { async (value: string) => {
const newSets = await setRepo.find({ const newWeights = await weightRepo.find({
where: { name: Like(`%${value.trim()}%`), hidden: 0 as any }, where: [
{
value: isNaN(Number(term)) ? undefined : Number(term),
},
{
created: Like(`%${term}%`),
},
],
take: LIMIT, take: LIMIT,
skip: 0, skip: 0,
order: { created: "DESC" }, order: { created: "DESC" },
}); });
console.log(`${SetList.name}.reset:`, { value, offset }); console.log(`${WeightList.name}.reset:`, { value, offset });
setSets(newSets); setWeights(newWeights);
setEnd(false); setEnd(false);
}, },
[offset] [offset, term]
); );
useEffect(() => { useFocusEffect(
settingsRepo.findOne({ where: {} }).then(setSettings); useCallback(() => {
reset(""); settingsRepo.findOne({ where: {} }).then(setSettings);
/* eslint-disable react-hooks/exhaustive-deps */ reset(term);
}, []); }, [term, reset])
);
useEffect(() => {
const updated = (gymSet: GymSet) => {
if (!sets) console.log({ sets });
console.log(`${SetList.name}.updated:`, { gymSet, length: sets.length });
const newSets = sets.map((set) => {
if (set.id !== gymSet.id) return set;
if (gymSet.created === undefined) gymSet.created = set.created;
return gymSet;
});
setSets(newSets);
};
const descriptions = [
emitter.addListener(SETTINGS, () => {
settingsRepo.findOne({ where: {} }).then(setSettings);
}),
emitter.addListener(GYM_SET_UPDATED, updated),
emitter.addListener(GYM_SET_CREATED, () => reset("")),
emitter.addListener(GYM_SET_DELETED, () => reset("")),
];
return () => descriptions.forEach((description) => description.remove());
}, [sets]);
const search = (value: string) => { const search = (value: string) => {
console.log(`${SetList.name}.search:`, value); console.log(`${WeightList.name}.search:`, value);
setTerm(value); setTerm(value);
setOffset(0); setOffset(0);
reset(value); reset(value);
}; };
useEffect(() => {
console.log(`${SetList.name}.useEffect:`, params);
if (params?.search) search(params.search);
}, [params]);
const renderItem = useCallback( const renderItem = useCallback(
({ item }: { item: GymSet }) => ( ({ item }: { item: Weight }) => (
<SetItem <WeightItem settings={settings} item={item} key={item.id} />
settings={settings}
item={item}
key={item.id}
ids={ids}
setIds={setIds}
/>
), ),
[settings, ids] [settings]
); );
const next = async () => { const next = async () => {
console.log({ end, refreshing }); console.log({ end, refreshing });
if (end || refreshing) return; if (end || refreshing) return;
const newOffset = offset + LIMIT; const newOffset = offset + LIMIT;
console.log(`${SetList.name}.next:`, { offset, newOffset, term }); console.log(`${WeightList.name}.next:`, { offset, newOffset, term });
const newSets = await setRepo.find({ const newWeights = await weightRepo.find({
where: { name: Like(`%${term}%`), hidden: 0 as any }, where: [
{
value: Number(term),
},
{
created: Like(`%${term}%`),
},
],
take: LIMIT, take: LIMIT,
skip: newOffset, skip: newOffset,
order: { created: "DESC" }, order: { created: "DESC" },
}); });
if (newSets.length === 0) return setEnd(true); if (newWeights.length === 0) return setEnd(true);
if (!sets) return; if (!weights) return;
const map = new Map<number, GymSet>(); const map = new Map<number, Weight>();
for (const set of sets) map.set(set.id, set); for (const weight of weights) map.set(weight.id, weight);
for (const set of newSets) map.set(set.id, set); for (const weight of newWeights) map.set(weight.id, weight);
const unique = Array.from(map.values()); const unique = Array.from(map.values());
setSets(unique); setWeights(unique);
if (newSets.length < LIMIT) return setEnd(true); if (newWeights.length < LIMIT) return setEnd(true);
setOffset(newOffset); setOffset(newOffset);
}; };
const onAdd = useCallback(async () => { const onAdd = useCallback(async () => {
const now = await getNow(); const now = await getNow();
let set = sets?.[0]; let weight = weights?.[0];
if (!set) set = { ...defaultSet }; if (!weight) weight = { ...defaultWeight };
set.created = now; weight.created = now;
delete set.id; delete weight.id;
navigation.navigate("EditSet", { set }); navigate("EditWeight", { weight });
}, [navigation, sets]); }, [navigate, weights]);
const edit = useCallback(() => {
navigation.navigate("EditSets", { ids });
setIds([]);
}, [ids, navigation]);
const copy = useCallback(async () => {
const set = await setRepo.findOne({
where: { id: ids.pop() },
});
delete set.id;
delete set.created;
navigation.navigate("EditSet", { set });
setIds([]);
}, [ids, navigation]);
const clear = useCallback(() => {
setIds([]);
}, []);
const remove = async () => {
setIds([]);
await setRepo.delete(ids.length > 0 ? ids : {});
return reset(term);
};
const select = useCallback(() => {
if (!sets) return;
if (ids.length === sets.length) return setIds([]);
setIds(sets.map((set) => set.id));
}, [sets, ids]);
const getContent = () => { const getContent = () => {
if (!settings) return null; if (!settings) return null;
if (sets?.length === 0) if (weights?.length === 0)
return ( return (
<List.Item <List.Item
title="No sets yet" title="No sets yet"
@ -177,7 +117,7 @@ export default function WeightList() {
); );
return ( return (
<FlatList <FlatList
data={sets ?? []} data={weights ?? []}
style={{ flex: 1 }} style={{ flex: 1 }}
renderItem={renderItem} renderItem={renderItem}
onEndReached={next} onEndReached={next}
@ -194,14 +134,10 @@ export default function WeightList() {
return ( return (
<> <>
<DrawerHeader name={ids.length > 0 ? `${ids.length} selected` : "Home"}> <DrawerHeader name="Weight">
<ListMenu <IconButton
onClear={clear} onPress={() => navigate("ViewWeightGraph")}
onCopy={copy} icon="chart-bell-curve-cumulative"
onDelete={remove}
onEdit={edit}
ids={ids}
onSelect={select}
/> />
</DrawerHeader> </DrawerHeader>

View File

@ -1,12 +1,15 @@
import { createStackNavigator } from "@react-navigation/stack"; import { createStackNavigator } from "@react-navigation/stack";
import EditSet from "./EditSet"; import EditWeight from "./EditWeight";
import SetList from "./SetList"; import ViewWeightGraph from "./ViewWeightGraph";
import Weight from "./weight";
import WeightList from "./WeightList";
export type WeightPageParams = { export type WeightPageParams = {
Weights: {}; Weights: {};
EditWeight: { EditWeight: {
weight: any; weight: Weight;
}; };
ViewWeightGraph: {};
}; };
const Stack = createStackNavigator<WeightPageParams>(); const Stack = createStackNavigator<WeightPageParams>();
@ -16,8 +19,9 @@ export default function WeightPage() {
<Stack.Navigator <Stack.Navigator
screenOptions={{ headerShown: false, animationEnabled: false }} screenOptions={{ headerShown: false, animationEnabled: false }}
> >
<Stack.Screen name="Weights" component={SetList} /> <Stack.Screen name="Weights" component={WeightList} />
<Stack.Screen name="EditWeight" component={EditSet} /> <Stack.Screen name="EditWeight" component={EditWeight} />
<Stack.Screen name="ViewWeightGraph" component={ViewWeightGraph} />
</Stack.Navigator> </Stack.Navigator>
); );
} }

View File

@ -26,14 +26,16 @@ import { dropMigrations1667190214743 } from "./migrations/1667190214743-drop-mig
import { splitColor1669420187764 } from "./migrations/1669420187764-split-color"; import { splitColor1669420187764 } from "./migrations/1669420187764-split-color";
import { addBackup1678334268359 } from "./migrations/1678334268359-add-backup"; import { addBackup1678334268359 } from "./migrations/1678334268359-add-backup";
import { planTitle1692654882408 } from "./migrations/1692654882408-plan-title"; import { planTitle1692654882408 } from "./migrations/1692654882408-plan-title";
import { weight1697766633971 } from "./migrations/1697766633971-weight";
import { Plan } from "./plan"; import { Plan } from "./plan";
import Settings from "./settings"; import Settings from "./settings";
import Weight from "./weight";
export const AppDataSource = new DataSource({ export const AppDataSource = new DataSource({
type: "react-native", type: "react-native",
database: "massive.db", database: "massive.db",
location: "default", location: "default",
entities: [GymSet, Plan, Settings], entities: [GymSet, Plan, Settings, Weight],
migrationsRun: true, migrationsRun: true,
migrationsTableName: "typeorm_migrations", migrationsTableName: "typeorm_migrations",
migrations: [ migrations: [
@ -63,5 +65,6 @@ export const AppDataSource = new DataSource({
splitColor1669420187764, splitColor1669420187764,
addBackup1678334268359, addBackup1678334268359,
planTitle1692654882408, planTitle1692654882408,
weight1697766633971,
], ],
}); });

2
db.ts
View File

@ -2,10 +2,12 @@ import { AppDataSource } from "./data-source";
import GymSet from "./gym-set"; import GymSet from "./gym-set";
import { Plan } from "./plan"; import { Plan } from "./plan";
import Settings from "./settings"; import Settings from "./settings";
import Weight from "./weight";
export const setRepo = AppDataSource.manager.getRepository(GymSet); export const setRepo = AppDataSource.manager.getRepository(GymSet);
export const planRepo = AppDataSource.manager.getRepository(Plan); export const planRepo = AppDataSource.manager.getRepository(Plan);
export const settingsRepo = AppDataSource.manager.getRepository(Settings); export const settingsRepo = AppDataSource.manager.getRepository(Settings);
export const weightRepo = AppDataSource.manager.getRepository(Weight);
export const getNow = async (): Promise<string> => { export const getNow = async (): Promise<string> => {
const query = await AppDataSource.manager.query( const query = await AppDataSource.manager.query(

View File

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class weight1697766633971 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS weights (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value INTEGER NOT NULL,
created TEXT NOT NULL,
unit TEXT DEFAULT 'kg'
)`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("DROP TABLE weights");
}
}

22
weight.ts Normal file
View File

@ -0,0 +1,22 @@
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity("weights")
export default class Weight {
@PrimaryGeneratedColumn()
id?: number;
@Column("int")
value: number;
@Column("text")
created: string;
@Column("text")
unit: string;
}
export const defaultWeight: Weight = {
created: "",
unit: "kg",
value: 0,
};