Compare commits

...

11 Commits

Author SHA1 Message Date
Brandon Presley 5b066bccaf Add daily page - 2.26 🚀
The daily page is used to flip through your exercises
by day. This is in contrast to the History page, which is
an infinitely scrolling list of all sets.

Closes #216, #207
2024-02-18 01:52:30 +13:00
Brandon Presley d5fab6d6d2 Add toast after successfully importing database 2024-02-18 00:54:51 +13:00
Brandon Presley c6ac2cae86 Prevent multiple alarm timers running at once 2024-02-18 00:50:08 +13:00
Brandon Presley 8162724328 Dont run timers once a plan is finished 2024-02-18 00:49:56 +13:00
Brandon Presley d89e307950 Fix tooltip on settings page vibration 2024-02-18 00:46:21 +13:00
Brandon Presley a9367cd53b Select first item in plan 2024-02-18 00:36:00 +13:00
Brandon Presley f5f96035a0 Use outlined inputs
They look WAY cooler
2024-02-18 00:34:27 +13:00
Brandon Presley 974d2207db Fix broken undo on plans 2024-02-18 00:21:27 +13:00
Brandon Presley cc5089d4b4 Rename GraphsList -> GraphList 2024-02-18 00:08:57 +13:00
Brandon Presley 2d9c69a3dd Rename yellow-green to Green 2024-02-17 20:57:44 +13:00
Brandon Presley 495d6b35b7 Disable sound -> Sound & Remove show steps setting
1. Negating is more complicated than just saying Sound
2. The exercise edit screen is already pretty small,
so this feature of hiding the steps is probably
not useful.
2024-02-17 20:57:29 +13:00
15 changed files with 154 additions and 74 deletions

View File

@ -3,12 +3,13 @@ import { StackScreenProps } from "@react-navigation/stack";
import { IconButton, useTheme, Banner } from "react-native-paper";
import { DrawerParams } from "./drawer-params";
import ExerciseList from "./ExerciseList";
import GraphsList from "./GraphsList";
import GraphsList from "./GraphList";
import InsightsPage from "./InsightsPage";
import PlanList from "./PlanList";
import SetList from "./SetList";
import SettingsPage from "./SettingsPage";
import WeightList from "./WeightList";
import Daily from "./Daily";
const Drawer = createDrawerNavigator<DrawerParams>();
@ -42,10 +43,15 @@ export default function AppDrawer({
component={ExerciseList}
options={{ drawerIcon: () => <IconButton icon="dumbbell" /> }}
/>
<Drawer.Screen
name="Daily"
component={Daily}
options={{ drawerIcon: () => <IconButton icon="calendar-outline" /> }}
/>
<Drawer.Screen
name="Plans"
component={PlanList}
options={{ drawerIcon: () => <IconButton icon="calendar-outline" /> }}
options={{ drawerIcon: () => <IconButton icon="checkbox-multiple-marked-outline" /> }}
/>
<Drawer.Screen
name="Graphs"

View File

@ -17,6 +17,7 @@ function AppInput(
selectTextOnFocus
ref={props.innerRef}
blurOnSubmit={false}
mode="outlined"
{...props}
/>
);

104
Daily.tsx Normal file
View File

@ -0,0 +1,104 @@
import { useCallback, useEffect, useState } from "react";
import { FlatList, Pressable, View } from "react-native";
import { Button, IconButton, List, Text } from "react-native-paper";
import AppFab from "./AppFab";
import DrawerHeader from "./DrawerHeader";
import { LIMIT, PADDING } from "./constants";
import GymSet, { defaultSet } from "./gym-set";
import { getNow, setRepo, settingsRepo } from "./db";
import { NavigationProp, useFocusEffect, useNavigation } from "@react-navigation/native";
import { Like } from "typeorm";
import Settings from "./settings";
import { format } from "date-fns";
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
import SetItem from "./SetItem";
import { StackParams } from "./AppStack";
export default function Daily() {
const [sets, setSets] = useState<GymSet[]>();
const [day, setDay] = useState<Date>()
const [settings, setSettings] = useState<Settings>();
const navigation = useNavigation<NavigationProp<StackParams>>();
const onFocus = async () => {
const now = await getNow();
let created = now.split('T')[0];
setDay(new Date(created));
}
useEffect(() => {
(async () => {
if (!day) return
const created = day.toISOString().split('T')[0]
const newSets = await setRepo.find({
where: { hidden: 0 as any, created: Like(`${created}%`) },
take: LIMIT,
skip: 0,
order: { created: "DESC" },
});
setSets(newSets);
console.log(`${Daily.name}.useEffect:`, { day });
settingsRepo.findOne({ where: {} }).then(setSettings)
})()
}, [day])
useFocusEffect(useCallback(() => {
onFocus();
}, []))
const onAdd = async () => {
const now = await getNow();
let set: Partial<GymSet> = { ...sets[0] };
if (!set) set = { ...defaultSet };
set.created = now;
delete set.id;
navigation.navigate("EditSet", { set });
}
const onRight = () => {
const newDay = new Date(day)
newDay.setDate(newDay.getDate() + 1)
setDay(newDay)
}
const onLeft = () => {
const newDay = new Date(day)
newDay.setDate(newDay.getDate() - 1)
setDay(newDay)
}
const onDate = () => {
DateTimePickerAndroid.open({
value: new Date(day),
onChange: (event, date) => {
if (event.type === 'dismissed') return;
setDay(date)
},
mode: 'date',
})
}
return (
<>
<DrawerHeader name="Daily" />
<View style={{ padding: PADDING, flexGrow: 1 }}>
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
<IconButton style={{ marginRight: 'auto' }} icon="chevron-double-left" onPress={onLeft} />
<Button onPress={onDate}>{format(day ? new Date(day) : new Date(), "PPPP")}</Button>
<IconButton style={{ marginLeft: 'auto' }} icon="chevron-double-right" onPress={onRight} />
</View>
{settings && (
<FlatList ListEmptyComponent={<List.Item title="No sets yet" />} style={{ flex: 1 }} data={sets} renderItem={({ item }) => <SetItem ids={[]} setIds={() => { }} item={item} settings={settings} />} />
)}
<AppFab onPress={onAdd} />
</View>
</>
)
}

View File

@ -138,17 +138,15 @@ export default function EditExercise() {
onChangeText={setName}
onSubmitEditing={submitName}
/>
{settings?.steps && (
<AppInput
innerRef={stepsRef}
selectTextOnFocus={false}
value={steps}
onChangeText={setSteps}
label="Steps"
multiline
onSubmitEditing={() => setsRef.current?.focus()}
/>
)}
<AppInput
innerRef={stepsRef}
selectTextOnFocus={false}
value={steps}
onChangeText={setSteps}
label="Steps"
multiline
onSubmitEditing={() => setsRef.current?.focus()}
/>
<AppInput
innerRef={setsRef}
value={sets}

View File

@ -120,17 +120,15 @@ export default function EditExercises() {
onChangeText={setName}
onSubmitEditing={submitName}
/>
{settings?.steps && (
<AppInput
innerRef={stepsRef}
selectTextOnFocus={false}
value={steps}
onChangeText={setSteps}
label={`Steps: ${oldSteps}`}
multiline
onSubmitEditing={() => setsRef.current?.focus()}
/>
)}
<AppInput
innerRef={stepsRef}
selectTextOnFocus={false}
value={steps}
onChangeText={setSteps}
label={`Steps: ${oldSteps}`}
multiline
onSubmitEditing={() => setsRef.current?.focus()}
/>
<AppInput
innerRef={setsRef}
value={sets}

View File

@ -62,7 +62,10 @@ export default function EditSet() {
useFocusEffect(
useCallback(() => {
settingsRepo.findOne({ where: {} }).then(setSettings);
settingsRepo.findOne({ where: {} }).then(gotSettings => {
setSettings(gotSettings);
console.log(`${EditSet.name}.focus:`, { gotSettings })
});
}, [])
);

View File

@ -125,6 +125,7 @@ export default function SettingsPage() {
await setRepo.update({}, { image: null });
await settingsRepo.update({}, { sound: null, backup: false });
reset({ index: 0, routes: [{ name: "Settings" }] });
toast("Imported database successfully.")
}, [reset]);
const today = new Date();
@ -138,7 +139,8 @@ export default function SettingsPage() {
items={[
{ label: "History", value: "History", icon: 'history' },
{ label: "Exercises", value: "Exercises", icon: 'dumbbell' },
{ label: "Plans", value: "Plans", icon: 'calendar-outline' },
{ label: "Daily", value: "Daily", icon: 'calendar-outline' },
{ label: "Plans", value: "Plans", icon: 'checkbox-multiple-marked-outline' },
{ label: "Graphs", value: "Graphs", icon: 'chart-bell-curve-cumulative' },
{ label: "Timer", value: "Timer", icon: 'timer-outline' },
{ label: "Weight", value: "Weight", icon: 'scale-bathroom' },
@ -341,38 +343,22 @@ export default function SettingsPage() {
onChange={async (value) => {
setValue("vibrate", value);
await settingsRepo.update({}, { vibrate: value });
if (value) toast("Timers will now run after each set.");
else toast("Stopped timers running after each set.");
if (value) toast("Alarms will vibrate.");
else toast("Stopped alarms from vibrating.");
}}
title={name}
/>
),
},
{
name: "Disable sound",
name: "Sound",
renderItem: (name: string) => (
<Switch
value={settings.noSound}
value={!settings.noSound}
onChange={async (value) => {
setValue("noSound", value);
const silentPath = Dirs.DocumentDir + "/silent.mp3";
if (value) {
await FileSystem.writeFile(silentPath, "");
setValue("sound", silentPath);
await settingsRepo.update(
{},
{
sound: silentPath,
noSound: value,
}
);
} else if (!value && settings.sound === silentPath) {
setValue("sound", null);
await settingsRepo.update({}, { sound: null, noSound: value });
}
if (value) toast("Alarms will no longer make a sound.");
setValue("noSound", !value);
await settingsRepo.update({}, { noSound: !value });
if (!value) toast("Alarms will no longer make a sound.");
else toast("Enabled sound for alarms.");
}}
title={name}
@ -424,21 +410,6 @@ export default function SettingsPage() {
/>
),
},
{
name: "Show steps",
renderItem: (name: string) => (
<Switch
value={settings.steps}
onChange={async (value) => {
setValue("steps", value);
await settingsRepo.update({}, { steps: value });
if (value) toast("Show steps for exercises.");
else toast("Hid steps for exercises.");
}}
title={name}
/>
),
},
{
name: "Show date",
renderItem: (name: string) => (

View File

@ -31,7 +31,7 @@ export default function StartPlan() {
const [reps, setReps] = useState(params.first?.reps.toString() || "0");
const [weight, setWeight] = useState(params.first?.weight.toString() || "0");
const [unit, setUnit] = useState<string>(params.first?.unit || "kg");
const [selected, setSelected] = useState<number | null>(null);
const [selected, setSelected] = useState<number>(0);
const [settings, setSettings] = useState<Settings>();
const [counts, setCounts] = useState<CountMany[]>();
const weightRef = useRef<TextInput>(null);
@ -88,10 +88,6 @@ export default function StartPlan() {
useCallback(() => {
settingsRepo.findOne({ where: {} }).then(setSettings);
refresh();
setRepo.find({ order: { created: "DESC" }, take: 1 }).then(sets => {
const index = exercises.findIndex(exercise => exercise === sets[0].name);
setSelected(index === -1 ? 0 : index);
});
// eslint-disable-next-line
}, [])
);
@ -132,6 +128,8 @@ export default function StartPlan() {
const canNotify = await check(PERMISSIONS.ANDROID.POST_NOTIFICATIONS);
if (canNotify === RESULTS.DENIED)
await request(PERMISSIONS.ANDROID.POST_NOTIFICATIONS);
if (isNaN(exercise.total) ? 0 : exercise.total === best.sets - 1 && selected === exercises.length - 1)
return
NativeModules.AlarmModule.timer(milliseconds, `${exercise.name} (${exercise.total + 1}/${best.sets})`);
};

View File

@ -41,7 +41,6 @@ export default function StartPlanItem(props: Props) {
setShowMenu(false);
if (!first) return toast("Nothing to undo.");
await setRepo.delete(first.id);
NativeModules.AlarmModule.stop();
onUndo();
}, [setShowMenu, onUndo, item.name]);

View File

@ -87,8 +87,8 @@ android {
applicationId "com.massive"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 36240
versionName "2.25"
versionCode 36241
versionName "2.26"
}
signingConfigs {
release {

View File

@ -71,6 +71,7 @@ class TimerService : Service() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
timerRunnable?.let { timerHandler.removeCallbacks(it) }
secondsLeft = (intent?.getIntExtra("milliseconds", 0) ?: 0) / 1000
currentDescription = intent?.getStringExtra("description").toString()
secondsTotal = secondsLeft

View File

@ -6,7 +6,7 @@ export const LIGHT_COLORS = [
{ hex: "#FA8072", name: "Salmon" },
{ hex: "#FFC0CB", name: "Pink" },
{ hex: "#E9DCC9", name: "Linen" },
{ hex: "#9ACD32", name: "Yellow Green" },
{ hex: "#9ACD32", name: "Green" },
{ hex: "#FFD700", name: "Gold" },
{ hex: "#00CED1", name: "Turquoise" },
];

View File

@ -7,4 +7,5 @@ export type DrawerParams = {
Weight: {};
Insights: {};
Settings: {};
Daily: {};
};

View File

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