Compare commits

..

1 Commits

Author SHA1 Message Date
ebe9a392ca Replace raw SQL in StartPlan with query builder code
Running this code results in the following error:

TypeError: subQueryBuilder.getParameters is not a function (it is undefined)
    at createFromAlias (http://localhost:8081/index.bundle//&platform=android&dev=t
y=false&app=com.massive&modulesOnly=false&runModule=true:205802:59)
    at from (http://localhost:8081/index.bundle//&platform=android&dev=true&minify=
=com.massive&modulesOnly=false&runModule=true:201576:45)
    at ?anon_0_ (http://localhost:8081/index.bundle//&platform=android&dev=true&min
&app=com.massive&modulesOnly=false&runModule=true:316375:209)
    at next (native)
    at asyncGeneratorStep (http://localhost:8081/index.bundle//&platform=android&de
nify=false&app=com.massive&modulesOnly=false&runModule=true:28380:26)
    at _next (http://localhost:8081/index.bundle//&platform=android&dev=true&minify
p=com.massive&modulesOnly=false&runModule=true:28399:29)
    at anonymous (http://localhost:8081/index.bundle//&platform=android&dev=true&mi
e&app=com.massive&modulesOnly=false&runModule=true:28404:14)
    at tryCallTwo (/root/react-native/packages/react-native/ReactAndroid/hermes-eng
Release/4i495j47/arm64-v8a/lib/InternalBytecode/InternalBytecode.js:61:9)
    at doResolve (/root/react-native/packages/react-native/ReactAndroid/hermes-engi
elease/4i495j47/arm64-v8a/lib/InternalBytecode/InternalBytecode.js:216:25)
    at Promise (/root/react-native/packages/react-native/ReactAndroid/hermes-engine
ease/4i495j47/arm64-v8a/lib/InternalBytecode/InternalBytecode.js:82:14)
    at anonymous (http://localhost:8081/index.bundle//&platform=android&dev=true&mi
e&app=com.massive&modulesOnly=false&runModule=true:28396:25)
    at anonymous (http://localhost:8081/index.bundle//&platform=android&dev=true&mi
e&app=com.massive&modulesOnly=false&runModule=true:316420:14)
    at callback (http://localhost:8081/index.bundle//&platform=android&dev=true&min
&app=com.massive&modulesOnly=false&runModule=true:145400:29)
    at anonymous (http://localhost:8081/index.bundle//&platform=android&dev=true&mi
e&app=com.massive&modulesOnly=false&runModule=true:145419:27)
    at commitHookEffectListMount (http://localhost:8081/index.bundle//&platform=and
true&minify=false&app=com.massive&modulesOnly=false&runModule=true:94929:38)
    at commitPassiveMountOnFiber (http://localhost:8081/index.bundle//&platform=and
true&minify=false&app=com.massive&modulesOnly=false&runModule=true:96130:44)
    at commitPassiveMountEffects_complete (http://localhost:8081/index.bundle//&pla
roid&dev=true&minify=false&app=com.massive&modulesOnly=false&runModule=true:96102:4
    at commitPassiveMountEffects_begin (http://localhost:8081/index.bundle//&platfo
d&dev=true&minify=false&app=com.massive&modulesOnly=false&runModule=true:96092:47)
    at commitPassiveMountEffects (http://localhost:8081/index.bundle//&platform=and
true&minify=false&app=com.massive&modulesOnly=false&runModule=true:96082:40)
    at flushPassiveEffectsImpl (http://localhost:8081/index.bundle//&platform=andro
ue&minify=false&app=com.massive&modulesOnly=false&runModule=true:97758:34)
    at flushPassiveEffects (http://localhost:8081/index.bundle//&platform=android&d
inify=false&app=com.massive&modulesOnly=false&runModule=true:97713:43)
    at performSyncWorkOnRoot (http://localhost:8081/index.bundle//&platform=android
&minify=false&app=com.massive&modulesOnly=false&runModule=true:97003:28)
    at flushSyncCallbacks (http://localhost:8081/index.bundle//&platform=android&de
nify=false&app=com.massive&modulesOnly=false&runModule=true:86202:36)
    at flushSyncCallbacksOnlyInLegacyMode (http://localhost:8081/index.bundle//&pla
2023-08-13 20:59:59 +12:00
17 changed files with 162 additions and 312 deletions

View File

@ -14,12 +14,10 @@ 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(",") : []
); );
@ -47,13 +45,8 @@ 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({ await planRepo.save({ days: newDays, workouts: newWorkouts, id: plan.id });
title: title, }, [days, workouts, plan]);
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) => {
@ -103,11 +96,6 @@ 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

View File

@ -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, IconButton, TouchableRipple } from "react-native-paper"; import { Button, Card, 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,65 +155,34 @@ export default function EditSet() {
onSubmitEditing={() => repsRef.current?.focus()} onSubmitEditing={() => repsRef.current?.focus()}
/> />
<View style={{ flexDirection: "row" }}> <AppInput
<AppInput label="Reps"
style={{ keyboardType="numeric"
flex: 1, value={reps}
marginBottom: MARGIN, onChangeText={(newReps) => {
}} const fixed = fixNumeric(newReps);
label="Reps" setReps(fixed);
keyboardType="numeric" if (fixed.length !== newReps.length) toast("Reps must be a number");
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,
}} }}
> onSubmitEditing={() => weightRef.current?.focus()}
<AppInput selection={selection}
style={{ flex: 1 }} onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
label="Weight" innerRef={repsRef}
keyboardType="numeric" />
value={weight}
onChangeText={(newWeight) => { <AppInput
const fixed = fixNumeric(newWeight); label="Weight"
setWeight(fixed); keyboardType="numeric"
if (fixed.length !== newWeight.length) value={weight}
toast("Weight must be a number"); onChangeText={(newWeight) => {
}} const fixed = fixNumeric(newWeight);
onSubmitEditing={handleSubmit} setWeight(fixed);
innerRef={weightRef} if (fixed.length !== newWeight.length)
/> toast("Weight must be a number");
<IconButton }}
icon="add" onSubmitEditing={handleSubmit}
onPress={() => setWeight((Number(weight) + 2.5).toString())} innerRef={weightRef}
/> />
<IconButton
icon="remove"
onPress={() => setWeight((Number(weight) - 2.5).toString())}
/>
</View>
{settings.showUnit && ( {settings.showUnit && (
<AppInput <AppInput

View File

@ -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, IconButton, TouchableRipple } from "react-native-paper"; import { Button, Card, 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,57 +89,23 @@ export default function EditSets() {
autoFocus={!name} autoFocus={!name}
/> />
<View <AppInput
style={{ label={`Reps: ${oldReps}`}
flexDirection: "row", keyboardType="numeric"
marginBottom: MARGIN, value={reps}
}} onChangeText={setReps}
> selection={selection}
<AppInput onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
style={{ autoFocus={!!name}
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>
<View <AppInput
style={{ label={`Weights: ${weights}`}
flexDirection: "row", keyboardType="numeric"
marginBottom: MARGIN, value={weight}
}} 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

View File

@ -6,19 +6,15 @@ 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 { getBestSets } from "./best.service";
import { LIMIT } from "./constants";
import { settingsRepo } from "./db";
import DrawerHeader from "./DrawerHeader";
import { GraphsPageParams } from "./GraphsPage"; import { GraphsPageParams } from "./GraphsPage";
import { setRepo, settingsRepo } from "./db";
import DrawerHeader from "./DrawerHeader";
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>();
@ -30,9 +26,22 @@ export default function GraphsList() {
); );
const refresh = useCallback(async (value: string) => { const refresh = useCallback(async (value: string) => {
const result = await getBestSets({ term: value, offset: 0 }); const result = await 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: `%${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(
@ -41,18 +50,6 @@ 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);
@ -89,12 +86,7 @@ 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 <FlatList style={{ flex: 1 }} renderItem={renderItem} data={bests} />
style={{ flex: 1 }}
renderItem={renderItem}
data={bests}
onEndReached={next}
/>
)} )}
</Page> </Page>
</> </>

View File

@ -56,37 +56,28 @@ 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(
() => () =>
item.title ? ( days.map((day, index) => (
<Text style={{ fontWeight: "bold" }}>{item.title}</Text> <Text key={day}>
) : ( {day === today ? (
currentDays <Text
), 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.title ? currentDays : item.workouts.replace(/,/g, ", ")), () => item.workouts.replace(/,/g, ", "),
[item.title, currentDays, item.workouts] [item.workouts]
); );
const backgroundColor = useMemo(() => { const backgroundColor = useMemo(() => {

View File

@ -25,7 +25,6 @@ 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()}%`) },
], ],
@ -55,9 +54,7 @@ export default function PlanList() {
); );
const onAdd = () => const onAdd = () =>
navigation.navigate("EditPlan", { navigation.navigate("EditPlan", { plan: { days: "", workouts: "" } });
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() } });

View File

@ -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, useTheme } from "react-native-paper"; import { Button, Menu, Subheading } from "react-native-paper";
import { ITEM_PADDING } from "./constants"; import { ITEM_PADDING } from "./constants";
export interface Item { export interface Item {
@ -21,7 +21,6 @@ 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],
@ -61,7 +60,7 @@ function Select({
> >
{items.map((item) => ( {items.map((item) => (
<Menu.Item <Menu.Item
titleStyle={{ color: item.color || colors.onSurface }} titleStyle={{ color: item.color }}
key={item.value} key={item.value}
title={item.label} title={item.label}
onPress={() => handlePress(item.value)} onPress={() => handlePress(item.value)}

View File

@ -7,7 +7,6 @@ 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";
@ -17,6 +16,8 @@ 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);
@ -29,11 +30,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 }); console.log(`${SetList.name}.refresh:`, { value, limit });
setSets(newSets); setSets(newSets);
setOffset(0); setOffset(0);
setEnd(false); setEnd(false);
@ -62,18 +63,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]);

View File

@ -244,13 +244,13 @@ export default function SettingsPage() {
}, [settings, darkColor, formatOptions, theme, lightColor]); }, [settings, darkColor, formatOptions, theme, lightColor]);
const renderSelect = useCallback( const renderSelect = useCallback(
(input: Input<string>) => ( (item: Input<string>) => (
<Select <Select
key={input.name} key={item.name}
value={input.value} value={item.value}
onChange={(value) => changeString(input.key, value)} onChange={(value) => changeString(item.key, value)}
label={input.name} label={item.name}
items={input.items} items={item.items}
/> />
), ),
[changeString] [changeString]

View File

@ -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 { MARGIN, PADDING } from "./constants"; import { 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,18 +45,30 @@ export default function StartPlan() {
const questions = workouts const questions = workouts
.map((workout, index) => `('${workout}',${index})`) .map((workout, index) => `('${workout}',${index})`)
.join(","); .join(",");
const select = ` const newCounts = await AppDataSource.manager
SELECT workouts.name, COUNT(sets.id) as total, sets.sets .createQueryBuilder()
FROM (select 0 as name, 0 as sequence union values ${questions}) as workouts .select("workouts.name")
LEFT JOIN sets ON sets.name = workouts.name .addSelect("COUNT(sets.id)", "total")
AND sets.created LIKE STRFTIME('%Y-%m-%d%%', 'now', 'localtime') .addSelect("sets.sets")
AND NOT sets.hidden .from((qb) => {
GROUP BY workouts.name const subQuery = qb
ORDER BY workouts.sequence .subQuery()
LIMIT -1 .select("0", "name")
OFFSET 1 .addSelect("0", "sequence")
`; .from("workouts", "workouts")
const newCounts = await AppDataSource.manager.query(select); .getQuery();
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]);
@ -126,69 +138,35 @@ 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 }}>
<View <AppInput
style={{ label="Reps"
flexDirection: "row", keyboardType="numeric"
marginBottom: MARGIN, 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()}
<AppInput selection={selection}
label="Reps" onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
style={{ flex: 1 }} innerRef={repsRef}
keyboardType="numeric" />
value={reps} <AppInput
onChangeText={(newReps) => { label="Weight"
const fixed = fixNumeric(newReps); keyboardType="numeric"
setReps(fixed); value={weight}
if (fixed.length !== newReps.length) onChangeText={(newWeight) => {
toast("Reps must be a number"); const fixed = fixNumeric(newWeight);
}} setWeight(fixed);
onSubmitEditing={() => weightRef.current?.focus()} if (fixed.length !== newWeight.length)
selection={selection} toast("Weight must be a number");
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}
<AppInput innerRef={weightRef}
label="Weight" blurOnSubmit
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"

View File

@ -6,7 +6,6 @@ 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";
@ -16,6 +15,8 @@ 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);
@ -31,7 +32,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);
@ -60,10 +61,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,
}); });
@ -73,13 +74,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]);

View File

@ -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 36174 versionCode 36173
versionName "1.148" versionName "1.147"
} }
signingConfigs { signingConfigs {
release { release {

View File

@ -1,4 +1,3 @@
import { LIMIT } from "./constants";
import { setRepo } from "./db"; import { setRepo } from "./db";
import GymSet from "./gym-set"; import GymSet from "./gym-set";
@ -14,29 +13,3 @@ 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();
};

View File

@ -3,4 +3,3 @@ 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;

View File

@ -5,11 +5,10 @@ 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> {

View File

@ -1,6 +1,6 @@
{ {
"name": "massive", "name": "massive",
"version": "1.148", "version": "1.147",
"private": true, "private": true,
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {

View File

@ -5,9 +5,6 @@ export class Plan {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id?: number; id?: number;
@Column("text")
title?: string;
@Column("text") @Column("text")
days: string; days: string;