Swap to using MaterialCommunityIcons
This commit is contained in:
parent
12dfa923e5
commit
7928cab4c1
2
App.tsx
2
App.tsx
|
@ -11,7 +11,7 @@ import {
|
||||||
Provider as PaperProvider,
|
Provider as PaperProvider,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
} from "react-native-paper";
|
} from "react-native-paper";
|
||||||
import MaterialIcon from "react-native-vector-icons/MaterialIcons";
|
import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { AppDataSource } from "./data-source";
|
import { AppDataSource } from "./data-source";
|
||||||
import { settingsRepo } from "./db";
|
import { settingsRepo } from "./db";
|
||||||
import { emitter } from "./emitter";
|
import { emitter } from "./emitter";
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default function AppFab(props: Partial<ComponentProps<typeof FAB>>) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FAB
|
<FAB
|
||||||
icon="add"
|
icon="plus"
|
||||||
testID="add"
|
testID="add"
|
||||||
color={fabColor}
|
color={fabColor}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -97,7 +97,7 @@ export default function EditPlan() {
|
||||||
delete first.id;
|
delete first.id;
|
||||||
navigation.navigate("StartPlan", { plan: newPlan, first });
|
navigation.navigate("StartPlan", { plan: newPlan, first });
|
||||||
}}
|
}}
|
||||||
icon="play-arrow"
|
icon="play"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StackHeader>
|
</StackHeader>
|
||||||
|
@ -138,7 +138,7 @@ export default function EditPlan() {
|
||||||
disabled={workouts.length === 0 && days.length === 0}
|
disabled={workouts.length === 0 && days.length === 0}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
icon="save"
|
icon="content-save"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
await save();
|
await save();
|
||||||
navigation.navigate("PlanList");
|
navigation.navigate("PlanList");
|
||||||
|
|
12
EditSet.tsx
12
EditSet.tsx
|
@ -202,11 +202,11 @@ export default function EditSet() {
|
||||||
innerRef={repsRef}
|
innerRef={repsRef}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="add"
|
icon="plus"
|
||||||
onPress={() => setReps((Number(reps) + 1).toString())}
|
onPress={() => setReps((Number(reps) + 1).toString())}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="remove"
|
icon="minus"
|
||||||
onPress={() => setReps((Number(reps) - 1).toString())}
|
onPress={() => setReps((Number(reps) - 1).toString())}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -232,11 +232,11 @@ export default function EditSet() {
|
||||||
innerRef={weightRef}
|
innerRef={weightRef}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="add"
|
icon="plus"
|
||||||
onPress={() => setWeight((Number(weight) + 2.5).toString())}
|
onPress={() => setWeight((Number(weight) + 2.5).toString())}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="remove"
|
icon="minus"
|
||||||
onPress={() => setWeight((Number(weight) - 2.5).toString())}
|
onPress={() => setWeight((Number(weight) - 2.5).toString())}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -273,7 +273,7 @@ export default function EditSet() {
|
||||||
<Button
|
<Button
|
||||||
style={{ marginBottom: MARGIN }}
|
style={{ marginBottom: MARGIN }}
|
||||||
onPress={changeImage}
|
onPress={changeImage}
|
||||||
icon="add-photo-alternate"
|
icon="image-plus"
|
||||||
>
|
>
|
||||||
Image
|
Image
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -283,7 +283,7 @@ export default function EditSet() {
|
||||||
<Button
|
<Button
|
||||||
disabled={!name}
|
disabled={!name}
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
icon="save"
|
icon="content-save"
|
||||||
style={{ margin: MARGIN }}
|
style={{ margin: MARGIN }}
|
||||||
onPress={handleSubmit}
|
onPress={handleSubmit}
|
||||||
>
|
>
|
||||||
|
|
12
EditSets.tsx
12
EditSets.tsx
|
@ -111,11 +111,11 @@ export default function EditSets() {
|
||||||
autoFocus={!!name}
|
autoFocus={!!name}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="add"
|
icon="plus"
|
||||||
onPress={() => setReps((Number(reps) + 1).toString())}
|
onPress={() => setReps((Number(reps) + 1).toString())}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="remove"
|
icon="minus"
|
||||||
onPress={() => setReps((Number(reps) - 1).toString())}
|
onPress={() => setReps((Number(reps) - 1).toString())}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -135,11 +135,11 @@ export default function EditSets() {
|
||||||
onSubmitEditing={save}
|
onSubmitEditing={save}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="add"
|
icon="plus"
|
||||||
onPress={() => setWeight((Number(weight) + 2.5).toString())}
|
onPress={() => setWeight((Number(weight) + 2.5).toString())}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="remove"
|
icon="minus"
|
||||||
onPress={() => setWeight((Number(weight) - 2.5).toString())}
|
onPress={() => setWeight((Number(weight) - 2.5).toString())}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -175,7 +175,7 @@ export default function EditSets() {
|
||||||
<Button
|
<Button
|
||||||
style={{ marginBottom: MARGIN }}
|
style={{ marginBottom: MARGIN }}
|
||||||
onPress={changeImage}
|
onPress={changeImage}
|
||||||
icon="add-photo-alternate"
|
icon="image-plus"
|
||||||
>
|
>
|
||||||
Image
|
Image
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -184,7 +184,7 @@ export default function EditSets() {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
icon="save"
|
icon="content-save"
|
||||||
style={{ margin: MARGIN }}
|
style={{ margin: MARGIN }}
|
||||||
onPress={save}
|
onPress={save}
|
||||||
>
|
>
|
||||||
|
|
303
EditWeight.tsx
Normal file
303
EditWeight.tsx
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
|
||||||
|
import {
|
||||||
|
NavigationProp,
|
||||||
|
RouteProp,
|
||||||
|
useFocusEffect,
|
||||||
|
useNavigation,
|
||||||
|
useRoute,
|
||||||
|
} from "@react-navigation/native";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { useCallback, useRef, useState } from "react";
|
||||||
|
import { NativeModules, TextInput, View } from "react-native";
|
||||||
|
import DocumentPicker from "react-native-document-picker";
|
||||||
|
import { Button, Card, IconButton, TouchableRipple } from "react-native-paper";
|
||||||
|
import AppInput from "./AppInput";
|
||||||
|
import ConfirmDialog from "./ConfirmDialog";
|
||||||
|
import { MARGIN, PADDING } from "./constants";
|
||||||
|
import { getNow, setRepo, settingsRepo } from "./db";
|
||||||
|
import GymSet, {
|
||||||
|
GYM_SET_CREATED,
|
||||||
|
GYM_SET_DELETED,
|
||||||
|
GYM_SET_UPDATED,
|
||||||
|
} from "./gym-set";
|
||||||
|
import { HomePageParams } from "./home-page-params";
|
||||||
|
import Settings from "./settings";
|
||||||
|
import StackHeader from "./StackHeader";
|
||||||
|
import { toast } from "./toast";
|
||||||
|
import { fixNumeric } from "./fix-numeric";
|
||||||
|
import { emitter } from "./emitter";
|
||||||
|
|
||||||
|
export default function EditWeight() {
|
||||||
|
const { params } = useRoute<RouteProp<HomePageParams, "EditSet">>();
|
||||||
|
const { set } = params;
|
||||||
|
const { navigate } = useNavigation<NavigationProp<HomePageParams>>();
|
||||||
|
const [settings, setSettings] = useState<Settings>({} as Settings);
|
||||||
|
const [name, setName] = useState(set.name);
|
||||||
|
const [reps, setReps] = useState(set.reps?.toString());
|
||||||
|
const [weight, setWeight] = useState(set.weight?.toString());
|
||||||
|
const [newImage, setNewImage] = useState(set.image);
|
||||||
|
const [unit, setUnit] = useState(set.unit);
|
||||||
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
|
const [created, setCreated] = useState<Date>(
|
||||||
|
set.created ? new Date(set.created) : new Date()
|
||||||
|
);
|
||||||
|
const [createdDirty, setCreatedDirty] = useState(false);
|
||||||
|
const [showRemove, setShowRemove] = useState(false);
|
||||||
|
const [removeImage, setRemoveImage] = useState(false);
|
||||||
|
const weightRef = useRef<TextInput>(null);
|
||||||
|
const repsRef = useRef<TextInput>(null);
|
||||||
|
const unitRef = useRef<TextInput>(null);
|
||||||
|
|
||||||
|
const [selection, setSelection] = useState({
|
||||||
|
start: 0,
|
||||||
|
end: set.reps?.toString().length,
|
||||||
|
});
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
settingsRepo.findOne({ where: {} }).then(setSettings);
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
|
||||||
|
const startTimer = useCallback(
|
||||||
|
async (value: string) => {
|
||||||
|
if (!settings.alarm) return;
|
||||||
|
const first = await setRepo.findOne({ where: { name: value } });
|
||||||
|
const milliseconds =
|
||||||
|
(first?.minutes ?? 3) * 60 * 1000 + (first?.seconds ?? 0) * 1000;
|
||||||
|
if (milliseconds) NativeModules.AlarmModule.timer(milliseconds);
|
||||||
|
},
|
||||||
|
[settings]
|
||||||
|
);
|
||||||
|
|
||||||
|
const notify = (value: Partial<GymSet>) => {
|
||||||
|
if (!settings.notify) return navigate("Sets");
|
||||||
|
if (
|
||||||
|
value.weight > set.weight ||
|
||||||
|
(value.reps > set.reps && value.weight === set.weight)
|
||||||
|
) {
|
||||||
|
toast("Great work King! That's a new record.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const added = async (value: GymSet) => {
|
||||||
|
console.log(`${EditSet.name}.added:`, value);
|
||||||
|
emitter.emit(GYM_SET_CREATED);
|
||||||
|
startTimer(value.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!name) return;
|
||||||
|
|
||||||
|
const newSet: Partial<GymSet> = {
|
||||||
|
id: set.id,
|
||||||
|
name,
|
||||||
|
reps: Number(reps),
|
||||||
|
weight: Number(weight),
|
||||||
|
unit,
|
||||||
|
minutes: Number(set.minutes ?? 3),
|
||||||
|
seconds: Number(set.seconds ?? 30),
|
||||||
|
sets: set.sets ?? 3,
|
||||||
|
hidden: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
newSet.image = newImage;
|
||||||
|
if (!newImage && !removeImage) {
|
||||||
|
newSet.image = await setRepo
|
||||||
|
.findOne({ where: { name } })
|
||||||
|
.then((s) => s?.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createdDirty) newSet.created = created.toISOString();
|
||||||
|
if (typeof set.id !== "number") newSet.created = await getNow();
|
||||||
|
|
||||||
|
const saved = await setRepo.save(newSet);
|
||||||
|
notify(newSet);
|
||||||
|
if (typeof set.id !== "number") return added(saved);
|
||||||
|
else emitter.emit(GYM_SET_UPDATED, saved);
|
||||||
|
navigate("Sets");
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeImage = useCallback(async () => {
|
||||||
|
const { fileCopyUri } = await DocumentPicker.pickSingle({
|
||||||
|
type: DocumentPicker.types.images,
|
||||||
|
copyTo: "documentDirectory",
|
||||||
|
});
|
||||||
|
if (fileCopyUri) setNewImage(fileCopyUri);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRemove = useCallback(async () => {
|
||||||
|
setNewImage("");
|
||||||
|
setRemoveImage(true);
|
||||||
|
setShowRemove(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const pickDate = useCallback(() => {
|
||||||
|
DateTimePickerAndroid.open({
|
||||||
|
value: created,
|
||||||
|
onChange: (_, date) => {
|
||||||
|
if (date === created) return;
|
||||||
|
setCreated(date);
|
||||||
|
setCreatedDirty(true);
|
||||||
|
DateTimePickerAndroid.open({
|
||||||
|
value: date,
|
||||||
|
onChange: (__, time) => setCreated(time),
|
||||||
|
mode: "time",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
mode: "date",
|
||||||
|
});
|
||||||
|
}, [created]);
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
await setRepo.delete(set.id);
|
||||||
|
emitter.emit(GYM_SET_DELETED);
|
||||||
|
navigate("Sets");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StackHeader title={typeof set.id === "number" ? "Edit set" : "Add set"}>
|
||||||
|
{typeof set.id === "number" ? (
|
||||||
|
<IconButton onPress={() => setShowDelete(true)} icon="delete" />
|
||||||
|
) : null}
|
||||||
|
</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 }}>
|
||||||
|
<AppInput
|
||||||
|
label="Name"
|
||||||
|
value={name}
|
||||||
|
onChangeText={setName}
|
||||||
|
autoCorrect={false}
|
||||||
|
autoFocus={!name}
|
||||||
|
onSubmitEditing={() => repsRef.current?.focus()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={{ flexDirection: "row" }}>
|
||||||
|
<AppInput
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
marginBottom: MARGIN,
|
||||||
|
}}
|
||||||
|
label="Reps"
|
||||||
|
keyboardType="numeric"
|
||||||
|
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,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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 && (
|
||||||
|
<AppInput
|
||||||
|
autoCapitalize="none"
|
||||||
|
label="Unit"
|
||||||
|
value={unit}
|
||||||
|
onChangeText={setUnit}
|
||||||
|
innerRef={unitRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{settings.showDate && (
|
||||||
|
<AppInput
|
||||||
|
label="Created"
|
||||||
|
value={format(created, settings.date || "P")}
|
||||||
|
onPressOut={pickDate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{settings.images && newImage && (
|
||||||
|
<TouchableRipple
|
||||||
|
style={{ marginBottom: MARGIN }}
|
||||||
|
onPress={changeImage}
|
||||||
|
onLongPress={() => setShowRemove(true)}
|
||||||
|
>
|
||||||
|
<Card.Cover source={{ uri: newImage }} />
|
||||||
|
</TouchableRipple>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{settings.images && !newImage && (
|
||||||
|
<Button
|
||||||
|
style={{ marginBottom: MARGIN }}
|
||||||
|
onPress={changeImage}
|
||||||
|
icon="add-photo-alternate"
|
||||||
|
>
|
||||||
|
Image
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disabled={!name}
|
||||||
|
mode="outlined"
|
||||||
|
icon="save"
|
||||||
|
style={{ margin: MARGIN }}
|
||||||
|
onPress={handleSubmit}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
title="Remove image"
|
||||||
|
onOk={handleRemove}
|
||||||
|
show={showRemove}
|
||||||
|
setShow={setShowRemove}
|
||||||
|
>
|
||||||
|
Are you sure you want to remove the image?
|
||||||
|
</ConfirmDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
10
ListMenu.tsx
10
ListMenu.tsx
|
@ -49,17 +49,19 @@ export default function ListMenu({
|
||||||
<Menu
|
<Menu
|
||||||
visible={showMenu}
|
visible={showMenu}
|
||||||
onDismiss={() => setShowMenu(false)}
|
onDismiss={() => setShowMenu(false)}
|
||||||
anchor={<IconButton onPress={() => setShowMenu(true)} icon="more-vert" />}
|
anchor={
|
||||||
|
<IconButton onPress={() => setShowMenu(true)} icon="dots-vertical" />
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Menu.Item leadingIcon="done-all" title="Select all" onPress={select} />
|
<Menu.Item leadingIcon="check-all" title="Select all" onPress={select} />
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leadingIcon="clear"
|
leadingIcon="close"
|
||||||
title="Clear"
|
title="Clear"
|
||||||
onPress={clear}
|
onPress={clear}
|
||||||
disabled={ids?.length === 0}
|
disabled={ids?.length === 0}
|
||||||
/>
|
/>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leadingIcon="edit"
|
leadingIcon="pencil"
|
||||||
title="Edit"
|
title="Edit"
|
||||||
onPress={edit}
|
onPress={edit}
|
||||||
disabled={ids?.length === 0}
|
disabled={ids?.length === 0}
|
||||||
|
|
4
Page.tsx
4
Page.tsx
|
@ -22,8 +22,8 @@ export default function Page({
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={term}
|
value={term}
|
||||||
onChangeText={search}
|
onChangeText={search}
|
||||||
icon="search"
|
icon="magnify"
|
||||||
clearIcon="clear"
|
clearIcon="close"
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
{onAdd && <AppFab onPress={onAdd} />}
|
{onAdd && <AppFab onPress={onAdd} />}
|
||||||
|
|
16
Routes.tsx
16
Routes.tsx
|
@ -8,6 +8,7 @@ import SettingsPage from "./SettingsPage";
|
||||||
import TimerPage from "./TimerPage";
|
import TimerPage from "./TimerPage";
|
||||||
import useDark from "./use-dark";
|
import useDark from "./use-dark";
|
||||||
import WorkoutsPage from "./WorkoutsPage";
|
import WorkoutsPage from "./WorkoutsPage";
|
||||||
|
import WeightPage from "./WeightPage";
|
||||||
|
|
||||||
const Drawer = createDrawerNavigator<DrawerParamList>();
|
const Drawer = createDrawerNavigator<DrawerParamList>();
|
||||||
|
|
||||||
|
@ -30,27 +31,32 @@ export default function Routes() {
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="Plans"
|
name="Plans"
|
||||||
component={PlanPage}
|
component={PlanPage}
|
||||||
options={{ drawerIcon: () => <IconButton icon="event" /> }}
|
options={{ drawerIcon: () => <IconButton icon="calendar" /> }}
|
||||||
/>
|
/>
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="Graphs"
|
name="Graphs"
|
||||||
component={GraphsPage}
|
component={GraphsPage}
|
||||||
options={{ drawerIcon: () => <IconButton icon="insights" /> }}
|
options={{ drawerIcon: () => <IconButton icon="chart-line" /> }}
|
||||||
/>
|
/>
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="Workouts"
|
name="Workouts"
|
||||||
component={WorkoutsPage}
|
component={WorkoutsPage}
|
||||||
options={{ drawerIcon: () => <IconButton icon="fitness-center" /> }}
|
options={{ drawerIcon: () => <IconButton icon="dumbbell" /> }}
|
||||||
/>
|
/>
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="Timer"
|
name="Timer"
|
||||||
component={TimerPage}
|
component={TimerPage}
|
||||||
options={{ drawerIcon: () => <IconButton icon="access-time" /> }}
|
options={{ drawerIcon: () => <IconButton icon="timer-outline" /> }}
|
||||||
|
/>
|
||||||
|
<Drawer.Screen
|
||||||
|
name="Weight"
|
||||||
|
component={WeightPage}
|
||||||
|
options={{ drawerIcon: () => <IconButton icon="scale" /> }}
|
||||||
/>
|
/>
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="Settings"
|
name="Settings"
|
||||||
component={SettingsPage}
|
component={SettingsPage}
|
||||||
options={{ drawerIcon: () => <IconButton icon="settings" /> }}
|
options={{ drawerIcon: () => <IconButton icon="cog" /> }}
|
||||||
/>
|
/>
|
||||||
</Drawer.Navigator>
|
</Drawer.Navigator>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default function StackHeader({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Appbar.Header>
|
<Appbar.Header>
|
||||||
<IconButton icon="arrow-back" onPress={navigation.goBack} />
|
<IconButton icon="arrow-left" onPress={navigation.goBack} />
|
||||||
<Appbar.Content title={title} />
|
<Appbar.Content title={title} />
|
||||||
{children}
|
{children}
|
||||||
</Appbar.Header>
|
</Appbar.Header>
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { AppDataSource } from "./data-source";
|
||||||
import { getNow, setRepo, settingsRepo } from "./db";
|
import { getNow, setRepo, settingsRepo } from "./db";
|
||||||
import { emitter } from "./emitter";
|
import { emitter } from "./emitter";
|
||||||
import { fixNumeric } from "./fix-numeric";
|
import { fixNumeric } from "./fix-numeric";
|
||||||
import GymSet, { GYM_SET_CREATED, GYM_SET_UPDATED } from "./gym-set";
|
import GymSet, { GYM_SET_CREATED } from "./gym-set";
|
||||||
import { PlanPageParams } from "./plan-page-params";
|
import { PlanPageParams } from "./plan-page-params";
|
||||||
import Settings from "./settings";
|
import Settings from "./settings";
|
||||||
import StackHeader from "./StackHeader";
|
import StackHeader from "./StackHeader";
|
||||||
|
@ -125,7 +125,7 @@ export default function StartPlan() {
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={() => navigation.navigate("EditPlan", { plan: params.plan })}
|
onPress={() => navigation.navigate("EditPlan", { plan: params.plan })}
|
||||||
icon="edit"
|
icon="pencil"
|
||||||
/>
|
/>
|
||||||
</StackHeader>
|
</StackHeader>
|
||||||
<View style={{ padding: PADDING, flex: 1, flexDirection: "column" }}>
|
<View style={{ padding: PADDING, flex: 1, flexDirection: "column" }}>
|
||||||
|
@ -153,11 +153,11 @@ export default function StartPlan() {
|
||||||
innerRef={repsRef}
|
innerRef={repsRef}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="add"
|
icon="plus"
|
||||||
onPress={() => setReps((Number(reps) + 1).toString())}
|
onPress={() => setReps((Number(reps) + 1).toString())}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="remove"
|
icon="minus"
|
||||||
onPress={() => setReps((Number(reps) - 1).toString())}
|
onPress={() => setReps((Number(reps) - 1).toString())}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -184,11 +184,11 @@ export default function StartPlan() {
|
||||||
blurOnSubmit
|
blurOnSubmit
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="add"
|
icon="plus"
|
||||||
onPress={() => setWeight((Number(weight) + 2.5).toString())}
|
onPress={() => setWeight((Number(weight) + 2.5).toString())}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="remove"
|
icon="minus"
|
||||||
onPress={() => setWeight((Number(weight) - 2.5).toString())}
|
onPress={() => setWeight((Number(weight) - 2.5).toString())}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -222,7 +222,7 @@ export default function StartPlan() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Button mode="outlined" icon="save" onPress={handleSubmit}>
|
<Button mode="outlined" icon="content-save" onPress={handleSubmit}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -100,8 +100,8 @@ export default function StartPlanItem(props: Props) {
|
||||||
visible={showMenu}
|
visible={showMenu}
|
||||||
onDismiss={() => setShowMenu(false)}
|
onDismiss={() => setShowMenu(false)}
|
||||||
>
|
>
|
||||||
<Menu.Item leadingIcon="visibility" onPress={view} title="View" />
|
<Menu.Item leadingIcon="eye-outline" onPress={view} title="View" />
|
||||||
<Menu.Item leadingIcon="edit" onPress={edit} title="Edit" />
|
<Menu.Item leadingIcon="pencil" onPress={edit} title="Edit" />
|
||||||
<Menu.Item leadingIcon="undo" onPress={undo} title="Undo" />
|
<Menu.Item leadingIcon="undo" onPress={undo} title="Undo" />
|
||||||
</Menu>
|
</Menu>
|
||||||
</View>
|
</View>
|
||||||
|
|
213
WeightList.tsx
Normal file
213
WeightList.tsx
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
import {
|
||||||
|
NavigationProp,
|
||||||
|
RouteProp,
|
||||||
|
useNavigation,
|
||||||
|
useRoute,
|
||||||
|
} from "@react-navigation/native";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { FlatList } from "react-native";
|
||||||
|
import { List } from "react-native-paper";
|
||||||
|
import { Like } from "typeorm";
|
||||||
|
import { LIMIT } from "./constants";
|
||||||
|
import { getNow, setRepo, settingsRepo } from "./db";
|
||||||
|
import DrawerHeader from "./DrawerHeader";
|
||||||
|
import { emitter } from "./emitter";
|
||||||
|
import GymSet, {
|
||||||
|
defaultSet,
|
||||||
|
GYM_SET_CREATED,
|
||||||
|
GYM_SET_DELETED,
|
||||||
|
GYM_SET_UPDATED,
|
||||||
|
} from "./gym-set";
|
||||||
|
import { HomePageParams } from "./home-page-params";
|
||||||
|
import ListMenu from "./ListMenu";
|
||||||
|
import Page from "./Page";
|
||||||
|
import SetItem from "./SetItem";
|
||||||
|
import Settings, { SETTINGS } from "./settings";
|
||||||
|
|
||||||
|
export default function WeightList() {
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [sets, setSets] = useState<GymSet[]>();
|
||||||
|
const [offset, setOffset] = useState(0);
|
||||||
|
const [end, setEnd] = useState(false);
|
||||||
|
const [settings, setSettings] = useState<Settings>();
|
||||||
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
|
const navigation = useNavigation<NavigationProp<HomePageParams>>();
|
||||||
|
const { params } = useRoute<RouteProp<HomePageParams, "Sets">>();
|
||||||
|
const [term, setTerm] = useState(params?.search || "");
|
||||||
|
|
||||||
|
const reset = useCallback(
|
||||||
|
async (value: string) => {
|
||||||
|
const newSets = await setRepo.find({
|
||||||
|
where: { name: Like(`%${value.trim()}%`), hidden: 0 as any },
|
||||||
|
take: LIMIT,
|
||||||
|
skip: 0,
|
||||||
|
order: { created: "DESC" },
|
||||||
|
});
|
||||||
|
console.log(`${SetList.name}.reset:`, { value, offset });
|
||||||
|
setSets(newSets);
|
||||||
|
setEnd(false);
|
||||||
|
},
|
||||||
|
[offset]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
settingsRepo.findOne({ where: {} }).then(setSettings);
|
||||||
|
reset("");
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updated = (gymSet: GymSet) => {
|
||||||
|
if (!sets) console.log({ sets });
|
||||||
|
console.log(`${SetList.name}.updated:`, { gymSet, length: sets.length });
|
||||||
|
const newSets = sets.map((set) => {
|
||||||
|
if (set.id !== gymSet.id) return set;
|
||||||
|
if (gymSet.created === undefined) gymSet.created = set.created;
|
||||||
|
return gymSet;
|
||||||
|
});
|
||||||
|
setSets(newSets);
|
||||||
|
};
|
||||||
|
|
||||||
|
const descriptions = [
|
||||||
|
emitter.addListener(SETTINGS, () => {
|
||||||
|
settingsRepo.findOne({ where: {} }).then(setSettings);
|
||||||
|
}),
|
||||||
|
emitter.addListener(GYM_SET_UPDATED, updated),
|
||||||
|
emitter.addListener(GYM_SET_CREATED, () => reset("")),
|
||||||
|
emitter.addListener(GYM_SET_DELETED, () => reset("")),
|
||||||
|
];
|
||||||
|
return () => descriptions.forEach((description) => description.remove());
|
||||||
|
}, [sets]);
|
||||||
|
|
||||||
|
const search = (value: string) => {
|
||||||
|
console.log(`${SetList.name}.search:`, value);
|
||||||
|
setTerm(value);
|
||||||
|
setOffset(0);
|
||||||
|
reset(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(`${SetList.name}.useEffect:`, params);
|
||||||
|
if (params?.search) search(params.search);
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item }: { item: GymSet }) => (
|
||||||
|
<SetItem
|
||||||
|
settings={settings}
|
||||||
|
item={item}
|
||||||
|
key={item.id}
|
||||||
|
ids={ids}
|
||||||
|
setIds={setIds}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[settings, ids]
|
||||||
|
);
|
||||||
|
|
||||||
|
const next = async () => {
|
||||||
|
console.log({ end, refreshing });
|
||||||
|
if (end || refreshing) return;
|
||||||
|
const newOffset = offset + LIMIT;
|
||||||
|
console.log(`${SetList.name}.next:`, { offset, newOffset, term });
|
||||||
|
const newSets = await setRepo.find({
|
||||||
|
where: { name: Like(`%${term}%`), hidden: 0 as any },
|
||||||
|
take: LIMIT,
|
||||||
|
skip: newOffset,
|
||||||
|
order: { created: "DESC" },
|
||||||
|
});
|
||||||
|
if (newSets.length === 0) return setEnd(true);
|
||||||
|
if (!sets) return;
|
||||||
|
const map = new Map<number, GymSet>();
|
||||||
|
for (const set of sets) map.set(set.id, set);
|
||||||
|
for (const set of newSets) map.set(set.id, set);
|
||||||
|
const unique = Array.from(map.values());
|
||||||
|
setSets(unique);
|
||||||
|
if (newSets.length < LIMIT) return setEnd(true);
|
||||||
|
setOffset(newOffset);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAdd = useCallback(async () => {
|
||||||
|
const now = await getNow();
|
||||||
|
let set = sets?.[0];
|
||||||
|
if (!set) set = { ...defaultSet };
|
||||||
|
set.created = now;
|
||||||
|
delete set.id;
|
||||||
|
navigation.navigate("EditSet", { set });
|
||||||
|
}, [navigation, sets]);
|
||||||
|
|
||||||
|
const edit = useCallback(() => {
|
||||||
|
navigation.navigate("EditSets", { ids });
|
||||||
|
setIds([]);
|
||||||
|
}, [ids, navigation]);
|
||||||
|
|
||||||
|
const copy = useCallback(async () => {
|
||||||
|
const set = await setRepo.findOne({
|
||||||
|
where: { id: ids.pop() },
|
||||||
|
});
|
||||||
|
delete set.id;
|
||||||
|
delete set.created;
|
||||||
|
navigation.navigate("EditSet", { set });
|
||||||
|
setIds([]);
|
||||||
|
}, [ids, navigation]);
|
||||||
|
|
||||||
|
const clear = useCallback(() => {
|
||||||
|
setIds([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
setIds([]);
|
||||||
|
await setRepo.delete(ids.length > 0 ? ids : {});
|
||||||
|
return reset(term);
|
||||||
|
};
|
||||||
|
|
||||||
|
const select = useCallback(() => {
|
||||||
|
if (!sets) return;
|
||||||
|
if (ids.length === sets.length) return setIds([]);
|
||||||
|
setIds(sets.map((set) => set.id));
|
||||||
|
}, [sets, ids]);
|
||||||
|
|
||||||
|
const getContent = () => {
|
||||||
|
if (!settings) return null;
|
||||||
|
if (sets?.length === 0)
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
title="No sets yet"
|
||||||
|
description="A set is a group of repetitions. E.g. 8 reps of Squats."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
data={sets ?? []}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
renderItem={renderItem}
|
||||||
|
onEndReached={next}
|
||||||
|
refreshing={refreshing}
|
||||||
|
keyExtractor={(set) => set.id?.toString()}
|
||||||
|
onRefresh={() => {
|
||||||
|
setOffset(0);
|
||||||
|
setRefreshing(true);
|
||||||
|
reset(term).finally(() => setRefreshing(false));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DrawerHeader name={ids.length > 0 ? `${ids.length} selected` : "Home"}>
|
||||||
|
<ListMenu
|
||||||
|
onClear={clear}
|
||||||
|
onCopy={copy}
|
||||||
|
onDelete={remove}
|
||||||
|
onEdit={edit}
|
||||||
|
ids={ids}
|
||||||
|
onSelect={select}
|
||||||
|
/>
|
||||||
|
</DrawerHeader>
|
||||||
|
|
||||||
|
<Page onAdd={onAdd} term={term} search={search}>
|
||||||
|
{getContent()}
|
||||||
|
</Page>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
23
WeightPage.tsx
Normal file
23
WeightPage.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { createStackNavigator } from "@react-navigation/stack";
|
||||||
|
import EditSet from "./EditSet";
|
||||||
|
import SetList from "./SetList";
|
||||||
|
|
||||||
|
export type WeightPageParams = {
|
||||||
|
Weights: {};
|
||||||
|
EditWeight: {
|
||||||
|
weight: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Stack = createStackNavigator<WeightPageParams>();
|
||||||
|
|
||||||
|
export default function WeightPage() {
|
||||||
|
return (
|
||||||
|
<Stack.Navigator
|
||||||
|
screenOptions={{ headerShown: false, animationEnabled: false }}
|
||||||
|
>
|
||||||
|
<Stack.Screen name="Weights" component={SetList} />
|
||||||
|
<Stack.Screen name="EditWeight" component={EditSet} />
|
||||||
|
</Stack.Navigator>
|
||||||
|
);
|
||||||
|
}
|
|
@ -143,6 +143,6 @@ dependencies {
|
||||||
|
|
||||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||||
project.ext.vectoricons = [
|
project.ext.vectoricons = [
|
||||||
iconFontNames: ['MaterialIcons.ttf']
|
iconFontNames: ['MaterialCommunityIcons.ttf']
|
||||||
]
|
]
|
||||||
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
||||||
|
|
|
@ -5,4 +5,5 @@ export type DrawerParamList = {
|
||||||
Plans: {};
|
Plans: {};
|
||||||
Workouts: {};
|
Workouts: {};
|
||||||
Timer: {};
|
Timer: {};
|
||||||
|
Weight: {};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user