Massive/ViewGraph.tsx

244 lines
8.1 KiB
TypeScript
Raw Permalink Normal View History

2024-02-09 02:20:17 +00:00
import { RouteProp, useFocusEffect, useRoute } from "@react-navigation/native";
2023-11-12 04:05:37 +00:00
import { format } from "date-fns";
2024-02-09 02:20:17 +00:00
import { useCallback, useEffect, useMemo, useState } from "react";
import { Keyboard, ScrollView, 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";
2023-11-14 22:21:49 +00:00
import { StackParams } from "./AppStack";
import AppLineChart from "./AppLineChart";
import Select from "./Select";
import StackHeader from "./StackHeader";
2024-02-09 02:20:17 +00:00
import { MARGIN, PADDING } from "./constants";
import { setRepo, settingsRepo } from "./db";
import GymSet from "./gym-set";
import { Metrics } from "./metrics";
import { Periods } from "./periods";
import Volume from "./volume";
2024-02-09 02:20:17 +00:00
import AppInput from "./AppInput";
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
import Settings from "./settings";
2022-09-02 00:24:28 +00:00
export default function ViewGraph() {
const { params } = useRoute<RouteProp<StackParams, "ViewGraph">>();
const [weights, setWeights] = useState<GymSet[]>();
const [volumes, setVolumes] = useState<Volume[]>();
const [metric, setMetric] = useState(Metrics.OneRepMax);
const [period, setPeriod] = useState(Periods.Monthly);
const [unit, setUnit] = useState('kg');
2024-02-09 02:20:17 +00:00
const [start, setStart] = useState<Date | null>(null)
const [end, setEnd] = useState<Date | null>(null)
const [settings, setSettings] = useState<Settings>({} as Settings);
useFocusEffect(useCallback(() => {
settingsRepo.findOne({ where: {} }).then(setSettings)
}, []))
const convertWeight = (weight: number, unitFrom: string, unitTo: string) => {
if (unitFrom === unitTo) return weight
if (unitFrom === 'lb' && unitTo === 'kg') return weight * 0.453592;
if (unitFrom === 'kg' && unitTo === 'lb') return weight * 2.20462;
};
2022-07-08 12:11:10 +00:00
2022-09-02 00:24:28 +00:00
useEffect(() => {
let difference = "-7 days";
if (period === Periods.Monthly) difference = "-1 months";
else if (period === Periods.Yearly) difference = "-1 years";
else if (period === Periods.TwoMonths) difference = "-2 months";
else if (period === Periods.ThreeMonths) difference = "-3 months";
else if (period === Periods.SixMonths) difference = "-6 months";
else if (period === Periods.AllTime) difference = null;
2023-11-09 00:28:47 +00:00
let group = "%Y-%m-%d";
if (period === Periods.Yearly) group = "%Y-%m";
2023-11-09 00:28:47 +00:00
const builder = setRepo
.createQueryBuilder()
.select("STRFTIME('%Y-%m-%d', created)", "created")
.addSelect("unit")
.where("name = :name", { name: params.name })
2024-02-09 02:20:17 +00:00
.andWhere("NOT hidden")
2024-02-09 02:20:17 +00:00
if (start)
builder.andWhere("DATE(created) >= :start", { start });
if (end)
builder.andWhere("DATE(created) <= :end", { end });
if (difference)
builder.andWhere("DATE(created) >= DATE('now', 'weekday 0', :difference)", {
difference,
});
2024-02-09 02:20:17 +00:00
builder
.groupBy("name")
.addGroupBy(`STRFTIME('${group}', created)`);
switch (metric) {
case Metrics.Best:
builder
.addSelect("ROUND(MAX(weight), 2)", "weight")
.getRawMany()
.then(newWeights => newWeights.map(set => ({ ...set, weight: convertWeight(set.weight, set.unit, unit) })))
.then(setWeights);
break;
case Metrics.Volume:
builder
.addSelect("ROUND(SUM(weight * reps), 2)", "value")
.getRawMany()
.then(newWeights => newWeights.map(set => ({ ...set, value: convertWeight(set.value, set.unit, unit) })))
.then(setVolumes);
break;
default:
2022-10-31 04:05:31 +00:00
// Brzycki formula https://en.wikipedia.org/wiki/One-repetition_maximum#Brzycki
builder
.addSelect(
"ROUND(MAX(weight / (1.0278 - 0.0278 * reps)), 2)",
"weight"
)
2022-10-31 04:05:31 +00:00
.getRawMany()
.then(newWeights => newWeights.map(set => ({ ...set, weight: convertWeight(set.weight, set.unit, unit) })))
2023-06-27 03:16:59 +00:00
.then((newWeights) => {
console.log(`${ViewGraph.name}.oneRepMax:`, { weights: newWeights });
setWeights(newWeights);
});
}
2024-02-09 02:20:17 +00:00
}, [params.name, metric, period, unit, start, end]);
2022-07-12 03:54:04 +00:00
2023-11-14 02:03:21 +00:00
const weightChart = useMemo(() => {
if (weights === undefined) return null;
2023-11-14 02:03:21 +00:00
if (weights.length === 0) return <List.Item title="No data yet." />;
return (
<AppLineChart
2023-11-14 02:03:21 +00:00
data={weights.map((set) => set.weight)}
labels={weights.map((set) =>
2023-11-23 02:04:18 +00:00
format(new Date(set.created), "yyyy-MM-d")
2023-11-14 02:03:21 +00:00
)}
/>
);
2023-11-23 02:04:18 +00:00
}, [weights]);
2023-11-14 02:03:21 +00:00
const volumeChart = useMemo(() => {
if (volumes === undefined) return null;
2023-11-14 02:03:21 +00:00
if (volumes.length === 0) return <List.Item title="No data yet." />;
2023-11-23 02:04:18 +00:00
2023-11-14 02:03:21 +00:00
return (
<AppLineChart
2023-11-14 02:03:21 +00:00
data={volumes.map((volume) => volume.value)}
labels={volumes.map((volume) =>
2023-11-23 02:04:18 +00:00
format(new Date(volume.created), "yyyy-MM-d")
2023-11-14 02:03:21 +00:00
)}
/>
);
2023-11-23 02:04:18 +00:00
}, [volumes]);
2022-11-23 08:50:11 +00:00
2024-02-09 02:20:17 +00:00
const pickStart = useCallback(() => {
DateTimePickerAndroid.open({
value: start || new Date(),
onChange: (event, date) => {
if (event.type === 'dismissed') return;
if (date === start) return;
setStart(date);
setPeriod(Periods.AllTime);
Keyboard.dismiss();
},
mode: "date",
});
}, [start]);
const pickEnd = useCallback(() => {
DateTimePickerAndroid.open({
value: end || new Date(),
onChange: (event, date) => {
if (event.type === 'dismissed') return;
if (date === end) return;
setEnd(date);
setPeriod(Periods.AllTime);
Keyboard.dismiss();
},
mode: "date",
});
}, [end]);
2022-07-08 12:11:10 +00:00
return (
<>
<StackHeader title={params.name}>
2023-03-27 23:04:54 +00:00
<IconButton
onPress={() =>
2023-06-27 03:16:59 +00:00
captureScreen().then(async (uri) => {
const base64 = await FileSystem.readFile(uri, "base64");
const url = `data:image/jpeg;base64,${base64}`;
2023-03-27 23:04:54 +00:00
Share.open({
type: "image/jpeg",
2023-03-27 23:04:54 +00:00
url,
});
})
}
icon="share"
2023-03-27 23:04:54 +00:00
/>
</StackHeader>
2023-11-26 22:51:15 +00:00
<ScrollView style={{ padding: PADDING }}>
<Select
label="Metric"
items={[
2023-06-27 03:16:59 +00:00
{ value: Metrics.OneRepMax, label: Metrics.OneRepMax },
{ label: Metrics.Best, value: Metrics.Best },
{ value: Metrics.Volume, label: Metrics.Volume },
]}
2023-06-27 03:16:59 +00:00
onChange={(value) => setMetric(value as Metrics)}
value={metric}
/>
2024-02-09 02:20:17 +00:00
<Select
label="Period"
items={[
2023-06-27 03:16:59 +00:00
{ value: Periods.Weekly, label: Periods.Weekly },
{ value: Periods.Monthly, label: Periods.Monthly },
{ value: Periods.TwoMonths, label: Periods.TwoMonths },
{ value: Periods.ThreeMonths, label: Periods.ThreeMonths },
{ value: Periods.SixMonths, label: Periods.SixMonths },
2023-06-27 03:16:59 +00:00
{ value: Periods.Yearly, label: Periods.Yearly },
{ value: Periods.AllTime, label: Periods.AllTime },
]}
2024-02-09 02:20:17 +00:00
onChange={(value) => {
setPeriod(value as Periods);
setStart(null);
setEnd(null);
}}
value={period}
/>
2024-02-09 02:20:17 +00:00
<View style={{ flexDirection: 'row', marginBottom: MARGIN }}>
<AppInput
label="Start date"
value={start ? format(start, settings.date || "Pp") : null}
onPressOut={pickStart}
style={{ flex: 1, marginRight: MARGIN }}
/>
<AppInput
label="End date"
value={end ? format(end, settings.date || "Pp") : null}
onPressOut={pickEnd}
style={{ flex: 1 }}
/>
</View>
<Select
label="Unit"
value={unit}
onChange={setUnit}
items={[
{ label: 'Pounds (lb)', value: 'lb' },
{ label: 'Kilograms (kg)', value: 'kg' },
{ label: 'Stone', value: 'stone' },
]}
/>
2023-10-25 23:20:15 +00:00
<View style={{ paddingTop: PADDING }}>
2023-11-14 02:03:21 +00:00
{metric === Metrics.Volume ? volumeChart : weightChart}
2023-10-25 23:20:15 +00:00
</View>
2023-11-26 22:51:15 +00:00
</ScrollView>
</>
);
2022-07-08 12:11:10 +00:00
}