Compare commits
5 Commits
feature/st
...
master
Author | SHA1 | Date | |
---|---|---|---|
314b09017b | |||
331597e3ee | |||
79cde3a219 | |||
63e1db7349 | |||
da17f8899c |
16
EditPlan.tsx
16
EditPlan.tsx
|
@ -14,10 +14,12 @@ import { PlanPageParams } from "./plan-page-params";
|
||||||
import StackHeader from "./StackHeader";
|
import StackHeader from "./StackHeader";
|
||||||
import Switch from "./Switch";
|
import Switch from "./Switch";
|
||||||
import { DAYS } from "./time";
|
import { DAYS } from "./time";
|
||||||
|
import AppInput from "./AppInput";
|
||||||
|
|
||||||
export default function EditPlan() {
|
export default function EditPlan() {
|
||||||
const { params } = useRoute<RouteProp<PlanPageParams, "EditPlan">>();
|
const { params } = useRoute<RouteProp<PlanPageParams, "EditPlan">>();
|
||||||
const { plan } = params;
|
const { plan } = params;
|
||||||
|
const [title, setTitle] = useState<string>(plan?.title);
|
||||||
const [days, setDays] = useState<string[]>(
|
const [days, setDays] = useState<string[]>(
|
||||||
plan.days ? plan.days.split(",") : []
|
plan.days ? plan.days.split(",") : []
|
||||||
);
|
);
|
||||||
|
@ -45,8 +47,13 @@ export default function EditPlan() {
|
||||||
if (!days || !workouts) return;
|
if (!days || !workouts) return;
|
||||||
const newWorkouts = workouts.filter((workout) => workout).join(",");
|
const newWorkouts = workouts.filter((workout) => workout).join(",");
|
||||||
const newDays = days.filter((day) => day).join(",");
|
const newDays = days.filter((day) => day).join(",");
|
||||||
await planRepo.save({ days: newDays, workouts: newWorkouts, id: plan.id });
|
await planRepo.save({
|
||||||
}, [days, workouts, plan]);
|
title: title,
|
||||||
|
days: newDays,
|
||||||
|
workouts: newWorkouts,
|
||||||
|
id: plan.id,
|
||||||
|
});
|
||||||
|
}, [title, days, workouts, plan]);
|
||||||
|
|
||||||
const toggleWorkout = useCallback(
|
const toggleWorkout = useCallback(
|
||||||
(on: boolean, name: string) => {
|
(on: boolean, name: string) => {
|
||||||
|
@ -96,6 +103,11 @@ export default function EditPlan() {
|
||||||
</StackHeader>
|
</StackHeader>
|
||||||
<View style={{ padding: PADDING, flex: 1 }}>
|
<View style={{ padding: PADDING, flex: 1 }}>
|
||||||
<ScrollView style={{ flex: 1 }}>
|
<ScrollView style={{ flex: 1 }}>
|
||||||
|
<AppInput
|
||||||
|
label="Title"
|
||||||
|
value={title}
|
||||||
|
onChangeText={(value) => setTitle(value)}
|
||||||
|
/>
|
||||||
<Text style={styles.title}>Days</Text>
|
<Text style={styles.title}>Days</Text>
|
||||||
{DAYS.map((day) => (
|
{DAYS.map((day) => (
|
||||||
<Switch
|
<Switch
|
||||||
|
|
85
EditSet.tsx
85
EditSet.tsx
|
@ -9,7 +9,7 @@ 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 { NativeModules, TextInput, View } from "react-native";
|
||||||
import DocumentPicker from "react-native-document-picker";
|
import DocumentPicker from "react-native-document-picker";
|
||||||
import { Button, Card, TouchableRipple } 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";
|
||||||
|
@ -155,34 +155,65 @@ export default function EditSet() {
|
||||||
onSubmitEditing={() => repsRef.current?.focus()}
|
onSubmitEditing={() => repsRef.current?.focus()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppInput
|
<View style={{ flexDirection: "row" }}>
|
||||||
label="Reps"
|
<AppInput
|
||||||
keyboardType="numeric"
|
style={{
|
||||||
value={reps}
|
flex: 1,
|
||||||
onChangeText={(newReps) => {
|
marginBottom: MARGIN,
|
||||||
const fixed = fixNumeric(newReps);
|
}}
|
||||||
setReps(fixed);
|
label="Reps"
|
||||||
if (fixed.length !== newReps.length) toast("Reps must be a number");
|
keyboardType="numeric"
|
||||||
}}
|
value={reps}
|
||||||
onSubmitEditing={() => weightRef.current?.focus()}
|
onChangeText={(newReps) => {
|
||||||
selection={selection}
|
const fixed = fixNumeric(newReps);
|
||||||
onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
|
setReps(fixed);
|
||||||
innerRef={repsRef}
|
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>
|
||||||
|
|
||||||
<AppInput
|
<View
|
||||||
label="Weight"
|
style={{
|
||||||
keyboardType="numeric"
|
flexDirection: "row",
|
||||||
value={weight}
|
marginBottom: MARGIN,
|
||||||
onChangeText={(newWeight) => {
|
|
||||||
const fixed = fixNumeric(newWeight);
|
|
||||||
setWeight(fixed);
|
|
||||||
if (fixed.length !== newWeight.length)
|
|
||||||
toast("Weight must be a number");
|
|
||||||
}}
|
}}
|
||||||
onSubmitEditing={handleSubmit}
|
>
|
||||||
innerRef={weightRef}
|
<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
|
||||||
|
|
68
EditSets.tsx
68
EditSets.tsx
|
@ -7,7 +7,7 @@ import {
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import DocumentPicker from "react-native-document-picker";
|
import DocumentPicker from "react-native-document-picker";
|
||||||
import { Button, Card, TouchableRipple } from "react-native-paper";
|
import { Button, Card, IconButton, TouchableRipple } from "react-native-paper";
|
||||||
import { In } from "typeorm";
|
import { In } from "typeorm";
|
||||||
import AppInput from "./AppInput";
|
import AppInput from "./AppInput";
|
||||||
import ConfirmDialog from "./ConfirmDialog";
|
import ConfirmDialog from "./ConfirmDialog";
|
||||||
|
@ -89,23 +89,57 @@ export default function EditSets() {
|
||||||
autoFocus={!name}
|
autoFocus={!name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppInput
|
<View
|
||||||
label={`Reps: ${oldReps}`}
|
style={{
|
||||||
keyboardType="numeric"
|
flexDirection: "row",
|
||||||
value={reps}
|
marginBottom: MARGIN,
|
||||||
onChangeText={setReps}
|
}}
|
||||||
selection={selection}
|
>
|
||||||
onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
|
<AppInput
|
||||||
autoFocus={!!name}
|
style={{
|
||||||
/>
|
flex: 1,
|
||||||
|
}}
|
||||||
|
label={`Reps: ${oldReps}`}
|
||||||
|
keyboardType="numeric"
|
||||||
|
value={reps}
|
||||||
|
onChangeText={setReps}
|
||||||
|
selection={selection}
|
||||||
|
onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
|
||||||
|
autoFocus={!!name}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon="add"
|
||||||
|
onPress={() => setReps((Number(reps) + 1).toString())}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon="remove"
|
||||||
|
onPress={() => setReps((Number(reps) - 1).toString())}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<AppInput
|
<View
|
||||||
label={`Weights: ${weights}`}
|
style={{
|
||||||
keyboardType="numeric"
|
flexDirection: "row",
|
||||||
value={weight}
|
marginBottom: MARGIN,
|
||||||
onChangeText={setWeight}
|
}}
|
||||||
onSubmitEditing={handleSubmit}
|
>
|
||||||
/>
|
<AppInput
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
label={`Weights: ${weights}`}
|
||||||
|
keyboardType="numeric"
|
||||||
|
value={weight}
|
||||||
|
onChangeText={setWeight}
|
||||||
|
onSubmitEditing={handleSubmit}
|
||||||
|
/>
|
||||||
|
<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
|
||||||
|
|
|
@ -6,15 +6,19 @@ import {
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { FlatList, Image } from "react-native";
|
import { FlatList, Image } from "react-native";
|
||||||
import { List } from "react-native-paper";
|
import { List } from "react-native-paper";
|
||||||
import { GraphsPageParams } from "./GraphsPage";
|
import { getBestSets } from "./best.service";
|
||||||
import { setRepo, settingsRepo } from "./db";
|
import { LIMIT } from "./constants";
|
||||||
|
import { settingsRepo } from "./db";
|
||||||
import DrawerHeader from "./DrawerHeader";
|
import DrawerHeader from "./DrawerHeader";
|
||||||
|
import { GraphsPageParams } from "./GraphsPage";
|
||||||
import GymSet from "./gym-set";
|
import GymSet from "./gym-set";
|
||||||
import Page from "./Page";
|
import Page from "./Page";
|
||||||
import Settings from "./settings";
|
import Settings from "./settings";
|
||||||
|
|
||||||
export default function GraphsList() {
|
export default function GraphsList() {
|
||||||
const [bests, setBests] = useState<GymSet[]>();
|
const [bests, setBests] = useState<GymSet[]>([]);
|
||||||
|
const [offset, setOffset] = useState(0);
|
||||||
|
const [end, setEnd] = useState(false);
|
||||||
const [term, setTerm] = useState("");
|
const [term, setTerm] = useState("");
|
||||||
const navigation = useNavigation<NavigationProp<GraphsPageParams>>();
|
const navigation = useNavigation<NavigationProp<GraphsPageParams>>();
|
||||||
const [settings, setSettings] = useState<Settings>();
|
const [settings, setSettings] = useState<Settings>();
|
||||||
|
@ -26,22 +30,9 @@ export default function GraphsList() {
|
||||||
);
|
);
|
||||||
|
|
||||||
const refresh = useCallback(async (value: string) => {
|
const refresh = useCallback(async (value: string) => {
|
||||||
const result = await setRepo
|
const result = await getBestSets({ term: value, offset: 0 });
|
||||||
.createQueryBuilder("gym_set")
|
|
||||||
.select(["gym_set.name", "gym_set.reps", "gym_set.weight"])
|
|
||||||
.groupBy("gym_set.name")
|
|
||||||
.innerJoin(
|
|
||||||
(qb) =>
|
|
||||||
qb
|
|
||||||
.select(["gym_set2.name", "MAX(gym_set2.weight) AS max_weight"])
|
|
||||||
.from(GymSet, "gym_set2")
|
|
||||||
.where("gym_set2.name LIKE (:name)", { name: `%${value.trim()}%` })
|
|
||||||
.groupBy("gym_set2.name"),
|
|
||||||
"subquery",
|
|
||||||
"gym_set.name = subquery.gym_set2_name AND gym_set.weight = subquery.max_weight"
|
|
||||||
)
|
|
||||||
.getMany();
|
|
||||||
setBests(result);
|
setBests(result);
|
||||||
|
setOffset(0);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
|
@ -50,6 +41,18 @@ export default function GraphsList() {
|
||||||
}, [refresh, term])
|
}, [refresh, term])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const next = useCallback(async () => {
|
||||||
|
if (end) return;
|
||||||
|
const newOffset = offset + LIMIT;
|
||||||
|
console.log(`${GraphsList.name}.next:`, { offset, newOffset, term });
|
||||||
|
const newBests = await getBestSets({ term, offset });
|
||||||
|
if (newBests.length === 0) return setEnd(true);
|
||||||
|
if (!bests) return;
|
||||||
|
setBests([...bests, ...newBests]);
|
||||||
|
if (newBests.length < LIMIT) return setEnd(true);
|
||||||
|
setOffset(newOffset);
|
||||||
|
}, [term, end, offset, bests]);
|
||||||
|
|
||||||
const search = useCallback(
|
const search = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
setTerm(value);
|
setTerm(value);
|
||||||
|
@ -86,7 +89,12 @@ export default function GraphsList() {
|
||||||
description="Once sets have been added, this will highlight your personal bests."
|
description="Once sets have been added, this will highlight your personal bests."
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FlatList style={{ flex: 1 }} renderItem={renderItem} data={bests} />
|
<FlatList
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
renderItem={renderItem}
|
||||||
|
data={bests}
|
||||||
|
onEndReached={next}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
</>
|
</>
|
||||||
|
|
43
PlanItem.tsx
43
PlanItem.tsx
|
@ -56,28 +56,37 @@ export default function PlanItem({
|
||||||
setIds([item.id]);
|
setIds([item.id]);
|
||||||
}, [ids.length, item.id, setIds]);
|
}, [ids.length, item.id, setIds]);
|
||||||
|
|
||||||
|
const currentDays = days.map((day, index) => (
|
||||||
|
<Text key={day}>
|
||||||
|
{day === today ? (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
day
|
||||||
|
)}
|
||||||
|
{index === days.length - 1 ? "" : ", "}
|
||||||
|
</Text>
|
||||||
|
));
|
||||||
|
|
||||||
const title = useMemo(
|
const title = useMemo(
|
||||||
() =>
|
() =>
|
||||||
days.map((day, index) => (
|
item.title ? (
|
||||||
<Text key={day}>
|
<Text style={{ fontWeight: "bold" }}>{item.title}</Text>
|
||||||
{day === today ? (
|
) : (
|
||||||
<Text
|
currentDays
|
||||||
style={{ fontWeight: "bold", textDecorationLine: "underline" }}
|
),
|
||||||
>
|
[item.title, currentDays]
|
||||||
{day}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
day
|
|
||||||
)}
|
|
||||||
{index === days.length - 1 ? "" : ", "}
|
|
||||||
</Text>
|
|
||||||
)),
|
|
||||||
[days, today]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const description = useMemo(
|
const description = useMemo(
|
||||||
() => item.workouts.replace(/,/g, ", "),
|
() => (item.title ? currentDays : item.workouts.replace(/,/g, ", ")),
|
||||||
[item.workouts]
|
[item.title, currentDays, item.workouts]
|
||||||
);
|
);
|
||||||
|
|
||||||
const backgroundColor = useMemo(() => {
|
const backgroundColor = useMemo(() => {
|
||||||
|
|
|
@ -25,6 +25,7 @@ export default function PlanList() {
|
||||||
planRepo
|
planRepo
|
||||||
.find({
|
.find({
|
||||||
where: [
|
where: [
|
||||||
|
{ title: Like(`%${value.trim()}%`) },
|
||||||
{ days: Like(`%${value.trim()}%`) },
|
{ days: Like(`%${value.trim()}%`) },
|
||||||
{ workouts: Like(`%${value.trim()}%`) },
|
{ workouts: Like(`%${value.trim()}%`) },
|
||||||
],
|
],
|
||||||
|
@ -54,7 +55,9 @@ export default function PlanList() {
|
||||||
);
|
);
|
||||||
|
|
||||||
const onAdd = () =>
|
const onAdd = () =>
|
||||||
navigation.navigate("EditPlan", { plan: { days: "", workouts: "" } });
|
navigation.navigate("EditPlan", {
|
||||||
|
plan: { title: "", days: "", workouts: "" },
|
||||||
|
});
|
||||||
|
|
||||||
const edit = useCallback(async () => {
|
const edit = useCallback(async () => {
|
||||||
const plan = await planRepo.findOne({ where: { id: ids.pop() } });
|
const plan = await planRepo.findOne({ where: { id: ids.pop() } });
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback, useMemo, useState } from "react";
|
import React, { useCallback, useMemo, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { Button, Menu, Subheading } from "react-native-paper";
|
import { Button, Menu, Subheading, useTheme } from "react-native-paper";
|
||||||
import { ITEM_PADDING } from "./constants";
|
import { ITEM_PADDING } from "./constants";
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
|
@ -21,6 +21,7 @@ function Select({
|
||||||
label?: string;
|
label?: string;
|
||||||
}) {
|
}) {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
const selected = useMemo(
|
const selected = useMemo(
|
||||||
() => items.find((item) => item.value === value) || items[0],
|
() => items.find((item) => item.value === value) || items[0],
|
||||||
|
@ -60,7 +61,7 @@ function Select({
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
titleStyle={{ color: item.color }}
|
titleStyle={{ color: item.color || colors.onSurface }}
|
||||||
key={item.value}
|
key={item.value}
|
||||||
title={item.label}
|
title={item.label}
|
||||||
onPress={() => handlePress(item.value)}
|
onPress={() => handlePress(item.value)}
|
||||||
|
|
13
SetList.tsx
13
SetList.tsx
|
@ -7,6 +7,7 @@ import { useCallback, useState } from "react";
|
||||||
import { FlatList } from "react-native";
|
import { FlatList } from "react-native";
|
||||||
import { List } from "react-native-paper";
|
import { List } from "react-native-paper";
|
||||||
import { Like } from "typeorm";
|
import { Like } from "typeorm";
|
||||||
|
import { LIMIT } from "./constants";
|
||||||
import { getNow, setRepo, settingsRepo } from "./db";
|
import { getNow, setRepo, settingsRepo } from "./db";
|
||||||
import DrawerHeader from "./DrawerHeader";
|
import DrawerHeader from "./DrawerHeader";
|
||||||
import GymSet, { defaultSet } from "./gym-set";
|
import GymSet, { defaultSet } from "./gym-set";
|
||||||
|
@ -16,8 +17,6 @@ import Page from "./Page";
|
||||||
import SetItem from "./SetItem";
|
import SetItem from "./SetItem";
|
||||||
import Settings from "./settings";
|
import Settings from "./settings";
|
||||||
|
|
||||||
const limit = 15;
|
|
||||||
|
|
||||||
export default function SetList() {
|
export default function SetList() {
|
||||||
const [sets, setSets] = useState<GymSet[]>([]);
|
const [sets, setSets] = useState<GymSet[]>([]);
|
||||||
const [offset, setOffset] = useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
|
@ -30,11 +29,11 @@ export default function SetList() {
|
||||||
const refresh = useCallback(async (value: string) => {
|
const refresh = useCallback(async (value: string) => {
|
||||||
const newSets = await setRepo.find({
|
const newSets = await setRepo.find({
|
||||||
where: { name: Like(`%${value.trim()}%`), hidden: 0 as any },
|
where: { name: Like(`%${value.trim()}%`), hidden: 0 as any },
|
||||||
take: limit,
|
take: LIMIT,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
order: { created: "DESC" },
|
order: { created: "DESC" },
|
||||||
});
|
});
|
||||||
console.log(`${SetList.name}.refresh:`, { value, limit });
|
console.log(`${SetList.name}.refresh:`, { value });
|
||||||
setSets(newSets);
|
setSets(newSets);
|
||||||
setOffset(0);
|
setOffset(0);
|
||||||
setEnd(false);
|
setEnd(false);
|
||||||
|
@ -63,18 +62,18 @@ export default function SetList() {
|
||||||
|
|
||||||
const next = useCallback(async () => {
|
const next = useCallback(async () => {
|
||||||
if (end) return;
|
if (end) return;
|
||||||
const newOffset = offset + limit;
|
const newOffset = offset + LIMIT;
|
||||||
console.log(`${SetList.name}.next:`, { offset, newOffset, term });
|
console.log(`${SetList.name}.next:`, { offset, newOffset, term });
|
||||||
const newSets = await setRepo.find({
|
const newSets = await setRepo.find({
|
||||||
where: { name: Like(`%${term}%`), hidden: 0 as any },
|
where: { name: Like(`%${term}%`), hidden: 0 as any },
|
||||||
take: limit,
|
take: LIMIT,
|
||||||
skip: newOffset,
|
skip: newOffset,
|
||||||
order: { created: "DESC" },
|
order: { created: "DESC" },
|
||||||
});
|
});
|
||||||
if (newSets.length === 0) return setEnd(true);
|
if (newSets.length === 0) return setEnd(true);
|
||||||
if (!sets) return;
|
if (!sets) return;
|
||||||
setSets([...sets, ...newSets]);
|
setSets([...sets, ...newSets]);
|
||||||
if (newSets.length < limit) return setEnd(true);
|
if (newSets.length < LIMIT) return setEnd(true);
|
||||||
setOffset(newOffset);
|
setOffset(newOffset);
|
||||||
}, [term, end, offset, sets]);
|
}, [term, end, offset, sets]);
|
||||||
|
|
||||||
|
|
|
@ -244,13 +244,13 @@ export default function SettingsPage() {
|
||||||
}, [settings, darkColor, formatOptions, theme, lightColor]);
|
}, [settings, darkColor, formatOptions, theme, lightColor]);
|
||||||
|
|
||||||
const renderSelect = useCallback(
|
const renderSelect = useCallback(
|
||||||
(item: Input<string>) => (
|
(input: Input<string>) => (
|
||||||
<Select
|
<Select
|
||||||
key={item.name}
|
key={input.name}
|
||||||
value={item.value}
|
value={input.value}
|
||||||
onChange={(value) => changeString(item.key, value)}
|
onChange={(value) => changeString(input.key, value)}
|
||||||
label={item.name}
|
label={input.name}
|
||||||
items={item.items}
|
items={input.items}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[changeString]
|
[changeString]
|
||||||
|
|
126
StartPlan.tsx
126
StartPlan.tsx
|
@ -10,7 +10,7 @@ import { FlatList, NativeModules, TextInput, View } from "react-native";
|
||||||
import { Button, IconButton, ProgressBar } from "react-native-paper";
|
import { Button, IconButton, ProgressBar } from "react-native-paper";
|
||||||
import AppInput from "./AppInput";
|
import AppInput from "./AppInput";
|
||||||
import { getBestSet } from "./best.service";
|
import { getBestSet } from "./best.service";
|
||||||
import { PADDING } from "./constants";
|
import { MARGIN, PADDING } from "./constants";
|
||||||
import CountMany from "./count-many";
|
import CountMany from "./count-many";
|
||||||
import { AppDataSource } from "./data-source";
|
import { AppDataSource } from "./data-source";
|
||||||
import { getNow, setRepo, settingsRepo } from "./db";
|
import { getNow, setRepo, settingsRepo } from "./db";
|
||||||
|
@ -45,30 +45,18 @@ export default function StartPlan() {
|
||||||
const questions = workouts
|
const questions = workouts
|
||||||
.map((workout, index) => `('${workout}',${index})`)
|
.map((workout, index) => `('${workout}',${index})`)
|
||||||
.join(",");
|
.join(",");
|
||||||
const newCounts = await AppDataSource.manager
|
const select = `
|
||||||
.createQueryBuilder()
|
SELECT workouts.name, COUNT(sets.id) as total, sets.sets
|
||||||
.select("workouts.name")
|
FROM (select 0 as name, 0 as sequence union values ${questions}) as workouts
|
||||||
.addSelect("COUNT(sets.id)", "total")
|
LEFT JOIN sets ON sets.name = workouts.name
|
||||||
.addSelect("sets.sets")
|
AND sets.created LIKE STRFTIME('%Y-%m-%d%%', 'now', 'localtime')
|
||||||
.from((qb) => {
|
AND NOT sets.hidden
|
||||||
const subQuery = qb
|
GROUP BY workouts.name
|
||||||
.subQuery()
|
ORDER BY workouts.sequence
|
||||||
.select("0", "name")
|
LIMIT -1
|
||||||
.addSelect("0", "sequence")
|
OFFSET 1
|
||||||
.from("workouts", "workouts")
|
`;
|
||||||
.getQuery();
|
const newCounts = await AppDataSource.manager.query(select);
|
||||||
return `(${subQuery} UNION ALL values ${questions})`;
|
|
||||||
}, "workouts")
|
|
||||||
.leftJoin(
|
|
||||||
"sets",
|
|
||||||
"sets",
|
|
||||||
"sets.name = workouts.name AND sets.created LIKE STRFTIME('%Y-%m-%d%%', 'now', 'localtime') AND NOT sets.hidden"
|
|
||||||
)
|
|
||||||
.groupBy("workouts.name")
|
|
||||||
.orderBy("workouts.sequence")
|
|
||||||
.limit(-1)
|
|
||||||
.offset(1)
|
|
||||||
.getRawMany();
|
|
||||||
console.log(`${StartPlan.name}.focus:`, { newCounts });
|
console.log(`${StartPlan.name}.focus:`, { newCounts });
|
||||||
setCounts(newCounts);
|
setCounts(newCounts);
|
||||||
}, [workouts]);
|
}, [workouts]);
|
||||||
|
@ -138,35 +126,69 @@ export default function StartPlan() {
|
||||||
</StackHeader>
|
</StackHeader>
|
||||||
<View style={{ padding: PADDING, flex: 1, flexDirection: "column" }}>
|
<View style={{ padding: PADDING, flex: 1, flexDirection: "column" }}>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<AppInput
|
<View
|
||||||
label="Reps"
|
style={{
|
||||||
keyboardType="numeric"
|
flexDirection: "row",
|
||||||
value={reps}
|
marginBottom: MARGIN,
|
||||||
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}
|
<AppInput
|
||||||
onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
|
label="Reps"
|
||||||
innerRef={repsRef}
|
style={{ flex: 1 }}
|
||||||
/>
|
keyboardType="numeric"
|
||||||
<AppInput
|
value={reps}
|
||||||
label="Weight"
|
onChangeText={(newReps) => {
|
||||||
keyboardType="numeric"
|
const fixed = fixNumeric(newReps);
|
||||||
value={weight}
|
setReps(fixed);
|
||||||
onChangeText={(newWeight) => {
|
if (fixed.length !== newReps.length)
|
||||||
const fixed = fixNumeric(newWeight);
|
toast("Reps must be a number");
|
||||||
setWeight(fixed);
|
}}
|
||||||
if (fixed.length !== newWeight.length)
|
onSubmitEditing={() => weightRef.current?.focus()}
|
||||||
toast("Weight must be a number");
|
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,
|
||||||
}}
|
}}
|
||||||
onSubmitEditing={handleSubmit}
|
>
|
||||||
innerRef={weightRef}
|
<AppInput
|
||||||
blurOnSubmit
|
label="Weight"
|
||||||
/>
|
style={{ flex: 1 }}
|
||||||
|
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}
|
||||||
|
blurOnSubmit
|
||||||
|
/>
|
||||||
|
<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"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
import { useCallback, 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 { List } from "react-native-paper";
|
||||||
|
import { LIMIT } from "./constants";
|
||||||
import { setRepo, settingsRepo } from "./db";
|
import { setRepo, settingsRepo } from "./db";
|
||||||
import DrawerHeader from "./DrawerHeader";
|
import DrawerHeader from "./DrawerHeader";
|
||||||
import GymSet from "./gym-set";
|
import GymSet from "./gym-set";
|
||||||
|
@ -15,8 +16,6 @@ import Settings from "./settings";
|
||||||
import WorkoutItem from "./WorkoutItem";
|
import WorkoutItem from "./WorkoutItem";
|
||||||
import { WorkoutsPageParams } from "./WorkoutsPage";
|
import { WorkoutsPageParams } from "./WorkoutsPage";
|
||||||
|
|
||||||
const limit = 15;
|
|
||||||
|
|
||||||
export default function WorkoutList() {
|
export default function WorkoutList() {
|
||||||
const [workouts, setWorkouts] = useState<GymSet[]>();
|
const [workouts, setWorkouts] = useState<GymSet[]>();
|
||||||
const [offset, setOffset] = useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
|
@ -32,7 +31,7 @@ export default function WorkoutList() {
|
||||||
.where("name LIKE :name", { name: `%${value.trim()}%` })
|
.where("name LIKE :name", { name: `%${value.trim()}%` })
|
||||||
.groupBy("name")
|
.groupBy("name")
|
||||||
.orderBy("name")
|
.orderBy("name")
|
||||||
.limit(limit)
|
.limit(LIMIT)
|
||||||
.getMany();
|
.getMany();
|
||||||
console.log(`${WorkoutList.name}`, { newWorkout: newWorkouts[0] });
|
console.log(`${WorkoutList.name}`, { newWorkout: newWorkouts[0] });
|
||||||
setWorkouts(newWorkouts);
|
setWorkouts(newWorkouts);
|
||||||
|
@ -61,10 +60,10 @@ export default function WorkoutList() {
|
||||||
|
|
||||||
const next = useCallback(async () => {
|
const next = useCallback(async () => {
|
||||||
if (end) return;
|
if (end) return;
|
||||||
const newOffset = offset + limit;
|
const newOffset = offset + LIMIT;
|
||||||
console.log(`${SetList.name}.next:`, {
|
console.log(`${SetList.name}.next:`, {
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit: LIMIT,
|
||||||
newOffset,
|
newOffset,
|
||||||
term,
|
term,
|
||||||
});
|
});
|
||||||
|
@ -74,13 +73,13 @@ export default function WorkoutList() {
|
||||||
.where("name LIKE :name", { name: `%${term.trim()}%` })
|
.where("name LIKE :name", { name: `%${term.trim()}%` })
|
||||||
.groupBy("name")
|
.groupBy("name")
|
||||||
.orderBy("name")
|
.orderBy("name")
|
||||||
.limit(limit)
|
.limit(LIMIT)
|
||||||
.offset(newOffset)
|
.offset(newOffset)
|
||||||
.getMany();
|
.getMany();
|
||||||
if (newWorkouts.length === 0) return setEnd(true);
|
if (newWorkouts.length === 0) return setEnd(true);
|
||||||
if (!workouts) return;
|
if (!workouts) return;
|
||||||
setWorkouts([...workouts, ...newWorkouts]);
|
setWorkouts([...workouts, ...newWorkouts]);
|
||||||
if (newWorkouts.length < limit) return setEnd(true);
|
if (newWorkouts.length < LIMIT) return setEnd(true);
|
||||||
setOffset(newOffset);
|
setOffset(newOffset);
|
||||||
}, [term, end, offset, workouts]);
|
}, [term, end, offset, workouts]);
|
||||||
|
|
||||||
|
|
|
@ -85,8 +85,8 @@ android {
|
||||||
applicationId "com.massive"
|
applicationId "com.massive"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 36173
|
versionCode 36174
|
||||||
versionName "1.147"
|
versionName "1.148"
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { LIMIT } from "./constants";
|
||||||
import { setRepo } from "./db";
|
import { setRepo } from "./db";
|
||||||
import GymSet from "./gym-set";
|
import GymSet from "./gym-set";
|
||||||
|
|
||||||
|
@ -13,3 +14,29 @@ export const getBestSet = async (name: string): Promise<GymSet> => {
|
||||||
.addOrderBy("reps", "DESC")
|
.addOrderBy("reps", "DESC")
|
||||||
.getOne();
|
.getOne();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getBestSets = ({
|
||||||
|
term: term,
|
||||||
|
offset,
|
||||||
|
}: {
|
||||||
|
term: string;
|
||||||
|
offset?: number;
|
||||||
|
}) => {
|
||||||
|
return setRepo
|
||||||
|
.createQueryBuilder("gym_set")
|
||||||
|
.select(["gym_set.name", "gym_set.reps", "gym_set.weight"])
|
||||||
|
.groupBy("gym_set.name")
|
||||||
|
.innerJoin(
|
||||||
|
(qb) =>
|
||||||
|
qb
|
||||||
|
.select(["gym_set2.name", "MAX(gym_set2.weight) AS max_weight"])
|
||||||
|
.from(GymSet, "gym_set2")
|
||||||
|
.where("gym_set2.name LIKE (:name)", { name: `%${term.trim()}%` })
|
||||||
|
.groupBy("gym_set2.name"),
|
||||||
|
"subquery",
|
||||||
|
"gym_set.name = subquery.gym_set2_name AND gym_set.weight = subquery.max_weight"
|
||||||
|
)
|
||||||
|
.limit(LIMIT)
|
||||||
|
.offset(offset || 0)
|
||||||
|
.getMany();
|
||||||
|
};
|
||||||
|
|
|
@ -3,3 +3,4 @@ export const PADDING = 10;
|
||||||
export const ITEM_PADDING = 8;
|
export const ITEM_PADDING = 8;
|
||||||
export const DARK_RIPPLE = "#444444";
|
export const DARK_RIPPLE = "#444444";
|
||||||
export const LIGHT_RIPPLE = "#c2c2c2";
|
export const LIGHT_RIPPLE = "#c2c2c2";
|
||||||
|
export const LIMIT = 15;
|
||||||
|
|
|
@ -5,10 +5,11 @@ export class plans1667186124792 implements MigrationInterface {
|
||||||
await queryRunner.query(`
|
await queryRunner.query(`
|
||||||
CREATE TABLE IF NOT EXISTS plans (
|
CREATE TABLE IF NOT EXISTS plans (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
days TEXT NOT NULL,
|
days TEXT NOT NULL,
|
||||||
workouts TEXT NOT NULL
|
workouts TEXT NOT NULL
|
||||||
)
|
)
|
||||||
`)
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "massive",
|
"name": "massive",
|
||||||
"version": "1.147",
|
"version": "1.148",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user