diff --git a/Chart.tsx b/Chart.tsx index 6588fd8..82897f3 100644 --- a/Chart.tsx +++ b/Chart.tsx @@ -4,7 +4,6 @@ import { View } from "react-native"; import { Grid, LineChart, XAxis, YAxis } from "react-native-svg-charts"; import { CombinedDarkTheme, CombinedDefaultTheme } from "./App"; import { MARGIN, PADDING } from "./constants"; -import GymSet from "./gym-set"; import useDark from "./use-dark"; export default function Chart({ @@ -14,7 +13,7 @@ export default function Chart({ yFormat, }: { yData: number[]; - xData: GymSet[]; + xData: unknown[]; xFormat: (value: any, index: number) => string; yFormat: (value: any) => string; }) { diff --git a/EditWeight.tsx b/EditWeight.tsx index c5e5215..d617aef 100644 --- a/EditWeight.tsx +++ b/EditWeight.tsx @@ -8,130 +8,53 @@ import { } from "@react-navigation/native"; import { format } from "date-fns"; import { useCallback, useRef, useState } from "react"; -import { NativeModules, TextInput, View } from "react-native"; -import DocumentPicker from "react-native-document-picker"; -import { Button, Card, IconButton, TouchableRipple } from "react-native-paper"; +import { TextInput, View } from "react-native"; +import { Button, IconButton } from "react-native-paper"; import AppInput from "./AppInput"; import ConfirmDialog from "./ConfirmDialog"; import { MARGIN, PADDING } from "./constants"; -import { getNow, setRepo, settingsRepo } from "./db"; -import GymSet, { - GYM_SET_CREATED, - GYM_SET_DELETED, - GYM_SET_UPDATED, -} from "./gym-set"; -import { HomePageParams } from "./home-page-params"; +import { getNow, settingsRepo, weightRepo } from "./db"; import Settings from "./settings"; import StackHeader from "./StackHeader"; -import { toast } from "./toast"; -import { fixNumeric } from "./fix-numeric"; -import { emitter } from "./emitter"; +import Weight from "./weight"; +import { WeightPageParams } from "./WeightPage"; export default function EditWeight() { - const { params } = useRoute>(); - const { set } = params; - const { navigate } = useNavigation>(); + const { params } = useRoute>(); + const { weight } = params; + const { navigate } = useNavigation>(); const [settings, setSettings] = useState({} as Settings); - const [name, setName] = useState(set.name); - const [reps, setReps] = useState(set.reps?.toString()); - const [weight, setWeight] = useState(set.weight?.toString()); - const [newImage, setNewImage] = useState(set.image); - const [unit, setUnit] = useState(set.unit); - const [showDelete, setShowDelete] = useState(false); + const [value, setValue] = useState(weight.value?.toString()); + const [unit, setUnit] = useState(weight.unit); const [created, setCreated] = useState( - 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 [showRemove, setShowRemove] = useState(false); - const [removeImage, setRemoveImage] = useState(false); - const weightRef = useRef(null); - const repsRef = useRef(null); const unitRef = useRef(null); - const [selection, setSelection] = useState({ - start: 0, - end: set.reps?.toString().length, - }); - useFocusEffect( useCallback(() => { settingsRepo.findOne({ where: {} }).then(setSettings); }, []) ); - const startTimer = useCallback( - async (value: string) => { - if (!settings.alarm) return; - const first = await setRepo.findOne({ where: { name: value } }); - const milliseconds = - (first?.minutes ?? 3) * 60 * 1000 + (first?.seconds ?? 0) * 1000; - if (milliseconds) NativeModules.AlarmModule.timer(milliseconds); - }, - [settings] - ); - - const notify = (value: Partial) => { - 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 () => { - if (!name) return; + if (!value) return; - const newSet: Partial = { - id: set.id, - name, - reps: Number(reps), - weight: Number(weight), + const newWeight: Partial = { + id: weight.id, + value: Number(value), unit, - minutes: Number(set.minutes ?? 3), - seconds: Number(set.seconds ?? 30), - sets: set.sets ?? 3, - hidden: false, }; - newSet.image = newImage; - if (!newImage && !removeImage) { - newSet.image = await setRepo - .findOne({ where: { name } }) - .then((s) => s?.image); - } + if (createdDirty) newWeight.created = created.toISOString(); + else if (typeof weight.id !== "number") newWeight.created = await getNow(); - if (createdDirty) newSet.created = created.toISOString(); - if (typeof set.id !== "number") newSet.created = await getNow(); - - 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"); + await weightRepo.save(newWeight); + navigate("Weights"); }; - const changeImage = useCallback(async () => { - const { fileCopyUri } = await DocumentPicker.pickSingle({ - type: DocumentPicker.types.images, - copyTo: "documentDirectory", - }); - if (fileCopyUri) setNewImage(fileCopyUri); - }, []); - - const handleRemove = useCallback(async () => { - setNewImage(""); - setRemoveImage(true); - setShowRemove(false); - }, []); - const pickDate = useCallback(() => { DateTimePickerAndroid.open({ value: created, @@ -139,108 +62,44 @@ export default function EditWeight() { if (date === created) return; setCreated(date); setCreatedDirty(true); - DateTimePickerAndroid.open({ - value: date, - onChange: (__, time) => setCreated(time), - mode: "time", - }); }, mode: "date", }); }, [created]); const remove = async () => { - await setRepo.delete(set.id); - emitter.emit(GYM_SET_DELETED); - navigate("Sets"); + if (!weight.id) return; + await weightRepo.delete(weight.id); + navigate("Weights"); }; return ( <> - - {typeof set.id === "number" ? ( + + {typeof weight.id === "number" ? ( setShowDelete(true)} icon="delete" /> ) : null} - <>Are you sure you want to delete {name} + <>Are you sure you want to delete {value} repsRef.current?.focus()} + label="Value" + value={value} + onChangeText={setValue} + keyboardType="numeric" + onSubmitEditing={() => unitRef.current?.focus()} /> - - { - 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} - /> - setReps((Number(reps) + 1).toString())} - /> - setReps((Number(reps) - 1).toString())} - /> - - - - { - const fixed = fixNumeric(newWeight); - setWeight(fixed); - if (fixed.length !== newWeight.length) - toast("Weight must be a number"); - }} - onSubmitEditing={handleSubmit} - innerRef={weightRef} - /> - setWeight((Number(weight) + 2.5).toString())} - /> - setWeight((Number(weight) - 2.5).toString())} - /> - - {settings.showUnit && ( )} - - {settings.images && newImage && ( - setShowRemove(true)} - > - - - )} - - {settings.images && !newImage && ( - - )} - - - Are you sure you want to remove the image? - ); } diff --git a/Routes.tsx b/Routes.tsx index 7379c91..bd79b83 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -36,7 +36,9 @@ export default function Routes() { }} + options={{ + drawerIcon: () => , + }} /> { + const view = useCallback(() => { setShowMenu(false); navigateHome("Sets", { search: item.name }); - }; + }, [item.name, navigateHome]); const left = useCallback( () => ( @@ -106,7 +106,7 @@ export default function StartPlanItem(props: Props) { ), - [anchor, showMenu, edit, undo] + [anchor, showMenu, edit, undo, view] ); return ( diff --git a/ViewWeightGraph.tsx b/ViewWeightGraph.tsx new file mode 100644 index 0000000..64086c0 --- /dev/null +++ b/ViewWeightGraph.tsx @@ -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(); + 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 ; + } + + return ( + set.value) || []} + yFormat={(value) => `${value}${weights?.[0].unit}`} + xData={weights || []} + xFormat={(_value, index) => + format(new Date(weights?.[index].created), "d/M") + } + /> + ); + }, [weights]); + + return ( + <> + + + 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" + /> + + +