Add settings for default fields on Exercise - 2.1 🚀

Closes #188
This commit is contained in:
Brandon Presley 2023-11-15 14:03:43 +13:00
parent f6a75d89cd
commit 608bb3e97a
10 changed files with 151 additions and 32 deletions

View File

@ -8,7 +8,7 @@ import {
import { useCallback, useRef, useState } from "react"; import { useCallback, useRef, useState } from "react";
import { ScrollView, TextInput, View } from "react-native"; import { ScrollView, 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 { StackParams } from "./AppStack"; import { StackParams } from "./AppStack";
import ConfirmDialog from "./ConfirmDialog"; import ConfirmDialog from "./ConfirmDialog";
@ -25,7 +25,8 @@ import { toast } from "./toast";
export default function EditExercise() { export default function EditExercise() {
const { params } = useRoute<RouteProp<StackParams, "EditExercise">>(); const { params } = useRoute<RouteProp<StackParams, "EditExercise">>();
const [removeImage, setRemoveImage] = useState(false); const [removeImage, setRemoveImage] = useState(false);
const [showRemove, setShowRemove] = useState(false); const [showRemoveImage, setShowRemoveImage] = useState(false);
const [showDelete, setShowDelete] = useState(false);
const [name, setName] = useState(params.gymSet.name); const [name, setName] = useState(params.gymSet.name);
const [steps, setSteps] = useState(params.gymSet.steps); const [steps, setSteps] = useState(params.gymSet.steps);
const [uri, setUri] = useState(params.gymSet.image); const [uri, setUri] = useState(params.gymSet.image);
@ -45,16 +46,22 @@ export default function EditExercise() {
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
settingsRepo.findOne({ where: {} }).then(setSettings); settingsRepo.findOne({ where: {} }).then((gotSettings) => {
}, []) setSettings(gotSettings);
if (params.gymSet.id) return;
setSets(gotSettings.defaultSets?.toString() ?? "3");
setMinutes(gotSettings.defaultMinutes?.toString() ?? "3");
setSeconds(gotSettings.defaultSeconds?.toString() ?? "30");
});
}, [params.gymSet.id])
); );
const update = async () => { const update = async () => {
const newExercise = { const newExercise = {
name: name || params.gymSet.name, name: name || params.gymSet.name,
sets: Number(sets), sets: Number(sets),
minutes: +minutes, minutes: Number(minutes),
seconds: +seconds, seconds: Number(seconds),
steps, steps,
image: removeImage ? "" : uri, image: removeImage ? "" : uri,
} as GymSet; } as GymSet;
@ -75,15 +82,20 @@ export default function EditExercise() {
name, name,
hidden: true, hidden: true,
image: uri, image: uri,
minutes: minutes ? +minutes : 3, minutes: minutes ? Number(minutes) : 3,
seconds: seconds ? +seconds : 30, seconds: seconds ? Number(seconds) : 30,
sets: sets ? +sets : 3, sets: sets ? Number(sets) : 3,
steps, steps,
created: now, created: now,
}); });
navigate("Exercises"); navigate("Exercises");
}; };
const remove = async () => {
await setRepo.delete({ name: params.gymSet.name });
navigate("Exercises");
};
const save = async () => { const save = async () => {
if (params.gymSet.name) return update(); if (params.gymSet.name) return update();
return add(); return add();
@ -100,7 +112,7 @@ export default function EditExercise() {
const handleRemove = useCallback(async () => { const handleRemove = useCallback(async () => {
setUri(""); setUri("");
setRemoveImage(true); setRemoveImage(true);
setShowRemove(false); setShowRemoveImage(false);
}, []); }, []);
const submitName = () => { const submitName = () => {
@ -112,7 +124,11 @@ export default function EditExercise() {
<> <>
<StackHeader <StackHeader
title={params.gymSet.name ? "Edit exercise" : "Add exercise"} title={params.gymSet.name ? "Edit exercise" : "Add exercise"}
/> >
{typeof params.gymSet.id === "number" ? (
<IconButton onPress={() => setShowDelete(true)} icon="delete" />
) : null}
</StackHeader>
<View style={{ padding: PADDING, flex: 1 }}> <View style={{ padding: PADDING, flex: 1 }}>
<ScrollView style={{ flex: 1 }}> <ScrollView style={{ flex: 1 }}>
<AppInput <AppInput
@ -175,7 +191,7 @@ export default function EditExercise() {
<TouchableRipple <TouchableRipple
style={{ marginBottom: MARGIN }} style={{ marginBottom: MARGIN }}
onPress={changeImage} onPress={changeImage}
onLongPress={() => setShowRemove(true)} onLongPress={() => setShowRemoveImage(true)}
> >
<Card.Cover source={{ uri }} /> <Card.Cover source={{ uri }} />
</TouchableRipple> </TouchableRipple>
@ -193,14 +209,24 @@ export default function EditExercise() {
<PrimaryButton disabled={!name} icon="content-save" onPress={save}> <PrimaryButton disabled={!name} icon="content-save" onPress={save}>
Save Save
</PrimaryButton> </PrimaryButton>
<ConfirmDialog <ConfirmDialog
title="Remove image" title="Remove image"
onOk={handleRemove} onOk={handleRemove}
show={showRemove} show={showRemoveImage}
setShow={setShowRemove} setShow={setShowRemoveImage}
> >
Are you sure you want to remove the image? Are you sure you want to remove the image?
</ConfirmDialog> </ConfirmDialog>
<ConfirmDialog
title="Delete set"
show={showDelete}
onOk={remove}
setShow={setShowDelete}
>
<>Are you sure you want to delete {name}</>
</ConfirmDialog>
</View> </View>
</> </>
); );

View File

@ -49,7 +49,7 @@ export default function EditSet() {
set.created ? new Date(set.created) : new Date() set.created ? new Date(set.created) : new Date()
); );
const [createdDirty, setCreatedDirty] = useState(false); const [createdDirty, setCreatedDirty] = useState(false);
const [showRemove, setShowRemove] = useState(false); const [showRemoveImage, setShowRemoveImage] = useState(false);
const [removeImage, setRemoveImage] = useState(false); const [removeImage, setRemoveImage] = useState(false);
const [setOptions, setSets] = useState<GymSet[]>([]); const [setOptions, setSets] = useState<GymSet[]>([]);
const weightRef = useRef<TextInput>(null); const weightRef = useRef<TextInput>(null);
@ -145,7 +145,7 @@ export default function EditSet() {
const handleRemove = useCallback(async () => { const handleRemove = useCallback(async () => {
setNewImage(""); setNewImage("");
setRemoveImage(true); setRemoveImage(true);
setShowRemove(false); setShowRemoveImage(false);
}, []); }, []);
const pickDate = useCallback(() => { const pickDate = useCallback(() => {
@ -202,14 +202,6 @@ export default function EditSet() {
<IconButton onPress={() => setShowDelete(true)} icon="delete" /> <IconButton onPress={() => setShowDelete(true)} icon="delete" />
) : null} ) : null}
</StackHeader> </StackHeader>
<ConfirmDialog
title="Delete set"
show={showDelete}
onOk={remove}
setShow={setShowDelete}
>
<>Are you sure you want to delete {name}</>
</ConfirmDialog>
<View style={{ padding: PADDING, flex: 1 }}> <View style={{ padding: PADDING, flex: 1 }}>
<View> <View>
@ -324,7 +316,7 @@ export default function EditSet() {
<TouchableRipple <TouchableRipple
style={{ marginBottom: MARGIN }} style={{ marginBottom: MARGIN }}
onPress={changeImage} onPress={changeImage}
onLongPress={() => setShowRemove(true)} onLongPress={() => setShowRemoveImage(true)}
> >
<Card.Cover source={{ uri: newImage }} /> <Card.Cover source={{ uri: newImage }} />
</TouchableRipple> </TouchableRipple>
@ -353,11 +345,20 @@ export default function EditSet() {
<ConfirmDialog <ConfirmDialog
title="Remove image" title="Remove image"
onOk={handleRemove} onOk={handleRemove}
show={showRemove} show={showRemoveImage}
setShow={setShowRemove} setShow={setShowRemoveImage}
> >
Are you sure you want to remove the image? Are you sure you want to remove the image?
</ConfirmDialog> </ConfirmDialog>
<ConfirmDialog
title="Delete set"
show={showDelete}
onOk={remove}
setShow={setShowDelete}
>
<>Are you sure you want to delete {name}</>
</ConfirmDialog>
</> </>
); );
} }

View File

@ -269,6 +269,60 @@ export default function SettingsPage() {
/> />
), ),
}, },
{
name: "Default sets",
renderItem: (name: string) => (
<AppInput
value={settings.defaultSets?.toString() ?? "3"}
label={name}
onChangeText={(value) => setValue("defaultSets", Number(value))}
onSubmitEditing={async (e) => {
const value = e.nativeEvent.text;
setValue("defaultSets", Number(value));
await update("defaultSets", value);
toast(`New exercises now have ${value} sets by default.`);
}}
keyboardType="numeric"
blurOnSubmit
/>
),
},
{
name: "Default minutes",
renderItem: (name: string) => (
<AppInput
value={settings.defaultMinutes?.toString() ?? "3"}
label={name}
onChangeText={(value) => setValue("defaultMinutes", Number(value))}
onSubmitEditing={async (e) => {
const value = e.nativeEvent.text;
setValue("defaultMinutes", Number(value));
await update("defaultMinutes", value);
toast(`New exercises now wait ${value} minutes by default.`);
}}
keyboardType="numeric"
blurOnSubmit
/>
),
},
{
name: "Default seconds",
renderItem: (name: string) => (
<AppInput
value={settings.defaultSeconds?.toString() ?? "30"}
label={name}
onChangeText={(value) => setValue("defaultSeconds", Number(value))}
onSubmitEditing={async (e) => {
const value = e.nativeEvent.text;
setValue("defaultSeconds", Number(value));
await update("defaultSeconds", value);
toast(`New exercises now wait ${value} seconds by default.`);
}}
keyboardType="numeric"
blurOnSubmit
/>
),
},
{ {
name: "Rest timers", name: "Rest timers",
renderItem: (name: string) => ( renderItem: (name: string) => (

View File

@ -125,8 +125,8 @@ export default function StartPlan() {
await refresh(); await refresh();
if ( if (
settings.notify && settings.notify &&
(+weight > best.weight || (Number(weight) > best.weight ||
(Number(reps) > best.reps && +weight === best.weight)) (Number(reps) > best.reps && Number(weight) === best.weight))
) { ) {
toast("Great work King! That's a new record."); toast("Great work King! That's a new record.");
} }

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 36214 versionCode 36216
versionName "1.188" versionName "2.1"
} }
signingConfigs { signingConfigs {
release { release {

View File

@ -37,6 +37,8 @@ import { autoConvert1699948105001 } from "./migrations/1699948105001-auto-conver
import { Plan } from "./plan"; import { Plan } from "./plan";
import Settings from "./settings"; import Settings from "./settings";
import Weight from "./weight"; import Weight from "./weight";
import { settingsDefaultSets1700009253976 } from "./migrations/1700009253976-settings-default-sets";
import { settingsDefaults1700009729468 } from "./migrations/1700009729468-settings-defaults";
export const AppDataSource = new DataSource({ export const AppDataSource = new DataSource({
type: "react-native", type: "react-native",
@ -80,5 +82,7 @@ export const AppDataSource = new DataSource({
settingsBackupDir1699839054226, settingsBackupDir1699839054226,
homeHistoryStartup1699853245534, homeHistoryStartup1699853245534,
autoConvert1699948105001, autoConvert1699948105001,
settingsDefaultSets1700009253976,
settingsDefaults1700009729468,
], ],
}); });

View File

@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class settingsDefaultSets1700009253976 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE settings ADD COLUMN defaultSets INTEGER"
);
}
public async down(queryRunner: QueryRunner): Promise<void> {}
}

View File

@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class settingsDefaults1700009729468 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE settings ADD COLUMN defaultMinutes INTEGER"
);
await queryRunner.query(
"ALTER TABLE settings ADD COLUMN defaultSeconds INTEGER"
);
}
public async down(queryRunner: QueryRunner): Promise<void> {}
}

View File

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

View File

@ -55,4 +55,13 @@ export default class Settings {
@Column("text") @Column("text")
autoConvert: string | null; autoConvert: string | null;
@Column("int")
defaultSets: number | null;
@Column("int")
defaultMinutes: number | null;
@Column("int")
defaultSeconds: number | null;
} }