diff --git a/EditSet.tsx b/EditSet.tsx index b32d49a..649cd03 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -8,12 +8,17 @@ import { } from "@react-navigation/native"; import { format } from "date-fns"; import { useCallback, useRef, useState } from "react"; -import { NativeModules, TextInput, View } from "react-native"; +import { + DeviceEventEmitter, + 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 { GYM_SET_CREATED, GYM_SET_UPDATED, MARGIN, PADDING } from "./constants"; import { getNow, setRepo, settingsRepo } from "./db"; import GymSet from "./gym-set"; import { HomePageParams } from "./home-page-params"; @@ -65,6 +70,7 @@ export default function EditSet() { ); const added = async (value: GymSet) => { + DeviceEventEmitter.emit(GYM_SET_CREATED); startTimer(value.name); console.log(`${EditSet.name}.add`, { set: value }); if (!settings.notify) return; @@ -104,6 +110,7 @@ export default function EditSet() { const saved = await setRepo.save(newSet); if (typeof set.id !== "number") return added(saved); + DeviceEventEmitter.emit(GYM_SET_UPDATED); if (createdDirty) navigate("Sets", { reset: saved.id }); else navigate("Sets", { refresh: saved.id }); }; diff --git a/EditSets.tsx b/EditSets.tsx index bb75375..7f8236d 100644 --- a/EditSets.tsx +++ b/EditSets.tsx @@ -5,13 +5,13 @@ import { useRoute, } from "@react-navigation/native"; import { useCallback, useState } from "react"; -import { View } from "react-native"; +import { DeviceEventEmitter, View } from "react-native"; import DocumentPicker from "react-native-document-picker"; import { Button, Card, IconButton, TouchableRipple } from "react-native-paper"; import { In } from "typeorm"; import AppInput from "./AppInput"; import ConfirmDialog from "./ConfirmDialog"; -import { MARGIN, PADDING } from "./constants"; +import { GYM_SET_UPDATED, MARGIN, PADDING } from "./constants"; import { setRepo, settingsRepo } from "./db"; import GymSet from "./gym-set"; import { HomePageParams } from "./home-page-params"; @@ -60,6 +60,7 @@ export default function EditSets() { if (unit) update.unit = unit; if (newImage) update.image = newImage; if (Object.keys(update).length > 0) await setRepo.update(ids, update); + DeviceEventEmitter.emit(GYM_SET_UPDATED); navigation.goBack(); }; diff --git a/EditWorkout.tsx b/EditWorkout.tsx index e4e5987..ce5e7e2 100644 --- a/EditWorkout.tsx +++ b/EditWorkout.tsx @@ -5,12 +5,12 @@ import { useRoute, } from "@react-navigation/native"; import { useCallback, useRef, useState } from "react"; -import { ScrollView, TextInput, View } from "react-native"; +import { DeviceEventEmitter, ScrollView, TextInput, View } from "react-native"; import DocumentPicker from "react-native-document-picker"; import { Button, Card, TouchableRipple } from "react-native-paper"; import AppInput from "./AppInput"; import ConfirmDialog from "./ConfirmDialog"; -import { MARGIN, PADDING } from "./constants"; +import { GYM_SET_UPDATED, MARGIN, PADDING } from "./constants"; import { getNow, planRepo, setRepo, settingsRepo } from "./db"; import { fixNumeric } from "./fix-numeric"; import { defaultSet } from "./gym-set"; @@ -58,6 +58,7 @@ export default function EditWorkout() { image: removeImage ? "" : uri, } ); + DeviceEventEmitter.emit(GYM_SET_UPDATED); await planRepo.query( `UPDATE plans SET workouts = REPLACE(workouts, $1, $2) diff --git a/EditWorkouts.tsx b/EditWorkouts.tsx index 1431f73..fc8317b 100644 --- a/EditWorkouts.tsx +++ b/EditWorkouts.tsx @@ -6,13 +6,13 @@ import { useRoute, } from "@react-navigation/native"; import { useCallback, useRef, useState } from "react"; -import { ScrollView, TextInput, View } from "react-native"; +import { DeviceEventEmitter, ScrollView, TextInput, View } from "react-native"; import DocumentPicker from "react-native-document-picker"; import { Button, Card, TouchableRipple } from "react-native-paper"; import { In } from "typeorm"; import AppInput from "./AppInput"; import ConfirmDialog from "./ConfirmDialog"; -import { MARGIN, PADDING } from "./constants"; +import { GYM_SETS_EDITED, MARGIN, PADDING } from "./constants"; import { planRepo, setRepo, settingsRepo } from "./db"; import { fixNumeric } from "./fix-numeric"; import Settings from "./settings"; @@ -74,6 +74,7 @@ export default function EditWorkouts() { image: removeImage ? "" : uri, } ); + DeviceEventEmitter.emit(GYM_SETS_EDITED); for (const oldName of params.names) { await planRepo .createQueryBuilder() diff --git a/GraphsList.tsx b/GraphsList.tsx index 52f9c9f..aa60f4a 100644 --- a/GraphsList.tsx +++ b/GraphsList.tsx @@ -1,47 +1,64 @@ +import { NavigationProp, useNavigation } from "@react-navigation/native"; +import { useCallback, useEffect, useState } from "react"; import { - NavigationProp, - useFocusEffect, - useNavigation, -} from "@react-navigation/native"; -import { useCallback, useState } from "react"; -import { FlatList, Image } from "react-native"; + DeviceEventEmitter, + EmitterSubscription, + FlatList, + Image, +} from "react-native"; import { List } from "react-native-paper"; import { getBestSets } from "./best.service"; -import { LIMIT } from "./constants"; +import { + GYM_SET_CREATED, + GYM_SET_DELETED, + GYM_SET_UPDATED, + LIMIT, +} from "./constants"; import { settingsRepo } from "./db"; import DrawerHeader from "./DrawerHeader"; import { GraphsPageParams } from "./GraphsPage"; import GymSet from "./gym-set"; import Page from "./Page"; -import Settings from "./settings"; +import Settings, { SETTINGS } from "./settings"; export default function GraphsList() { - const [bests, setBests] = useState([]); + const [bests, setBests] = useState(); + const [refreshing, setRefreshing] = useState(false); const [offset, setOffset] = useState(0); const [end, setEnd] = useState(false); const [term, setTerm] = useState(""); const navigation = useNavigation>(); const [settings, setSettings] = useState(); - useFocusEffect( - useCallback(() => { - settingsRepo.findOne({ where: {} }).then(setSettings); - }, []) - ); - const refresh = useCallback(async (value: string) => { - const result = await getBestSets({ term: value, offset: 0 }); + setRefreshing(true); + const result = await getBestSets({ term: value, offset: 0 }).finally(() => + setRefreshing(false) + ); setBests(result); setOffset(0); }, []); - useFocusEffect( - useCallback(() => { - refresh(term); - }, [refresh, term]) - ); + useEffect(() => { + refresh(""); + settingsRepo.findOne({ where: {} }).then(setSettings); + const subs: EmitterSubscription[] = []; + + subs.push( + DeviceEventEmitter.addListener(GYM_SET_CREATED, () => refresh("")), + DeviceEventEmitter.addListener(GYM_SET_UPDATED, () => refresh("")), + DeviceEventEmitter.addListener(GYM_SET_DELETED, () => refresh("")), + DeviceEventEmitter.addListener(SETTINGS, () => + settingsRepo.findOne({ where: {} }).then(setSettings) + ) + ); + + return () => subs.forEach((sub) => sub.remove()); + /* eslint-disable react-hooks/exhaustive-deps */ + }, []); const next = useCallback(async () => { + console.log("next:", { end, offset }); if (end) return; const newOffset = offset + LIMIT; console.log(`${GraphsList.name}.next:`, { offset, newOffset, term }); @@ -68,7 +85,7 @@ export default function GraphsList() { description={`${item.reps} x ${item.weight}${item.unit || "kg"}`} onPress={() => navigation.navigate("ViewGraph", { best: item })} left={() => - (settings.images && item.image && ( + (settings?.images && item.image && ( refresh("")} /> )} diff --git a/SetList.tsx b/SetList.tsx index 3fb3c93..65a8d29 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -4,11 +4,20 @@ import { useNavigation, useRoute, } from "@react-navigation/native"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { DeviceEventEmitter, FlatList } from "react-native"; +import { useCallback, useEffect, useState } from "react"; +import { + DeviceEventEmitter, + EmitterSubscription, + FlatList, +} from "react-native"; import { List } from "react-native-paper"; import { Like } from "typeorm"; -import { LIMIT } from "./constants"; +import { + GYM_SET_CREATED, + GYM_SET_DELETED, + GYM_SET_UPDATED, + LIMIT, +} from "./constants"; import { getNow, setRepo, settingsRepo } from "./db"; import DrawerHeader from "./DrawerHeader"; import GymSet, { defaultSet } from "./gym-set"; @@ -29,21 +38,14 @@ export default function SetList() { const { params } = useRoute>(); const [term, setTerm] = useState(params?.search || ""); - const refresh = async ({ - value, - take, - skip, - }: { - value: string; - take: number; - skip: number; - }) => { + const refresh = async ({ value, take }: { value: string; take: number }) => { setRefreshing(true); + setOffset(0); const newSets = await setRepo .find({ where: { name: Like(`%${value.trim()}%`), hidden: 0 as any }, take, - skip, + skip: 0, order: { created: "DESC" }, }) .finally(() => setRefreshing(false)); @@ -57,20 +59,35 @@ export default function SetList() { refresh({ take: LIMIT, value: "", - skip: 0, }); - const description = DeviceEventEmitter.addListener(SETTINGS, () => { - settingsRepo.findOne({ where: {} }).then(setSettings); - }); - return description.remove; + + const subs: EmitterSubscription[] = []; + + subs.push( + DeviceEventEmitter.addListener(SETTINGS, () => { + settingsRepo.findOne({ where: {} }).then(setSettings); + }), + DeviceEventEmitter.addListener(GYM_SET_UPDATED, () => + refresh({ take: offset, value: term }) + ), + DeviceEventEmitter.addListener(GYM_SET_DELETED, () => { + refresh({ take: LIMIT, value: term }); + }), + DeviceEventEmitter.addListener(GYM_SET_CREATED, () => { + refresh({ take: LIMIT, value: term }); + }), + DeviceEventEmitter.addListener(SETTINGS, () => + settingsRepo.findOne({ where: {} }).then(setSettings) + ) + ); + + return () => subs.forEach((sub) => sub.remove()); /* eslint-disable react-hooks/exhaustive-deps */ }, []); const search = (value: string) => { setTerm(value); - setOffset(0); refresh({ - skip: 0, take: LIMIT, value, }); @@ -82,13 +99,11 @@ export default function SetList() { if (params.search) search(params.search); else if (params.refresh) refresh({ - skip: 0, take: offset, value: term, }); else if (params.reset) refresh({ - skip: 0, take: LIMIT, value: term, }); @@ -164,8 +179,8 @@ export default function SetList() { const remove = async () => { setIds([]); await setRepo.delete(ids.length > 0 ? ids : {}); + DeviceEventEmitter.emit(GYM_SET_DELETED); return refresh({ - skip: 0, take: LIMIT, value: term, }); @@ -194,9 +209,7 @@ export default function SetList() { onEndReached={next} refreshing={refreshing} onRefresh={() => { - setOffset(0); refresh({ - skip: 0, take: LIMIT, value: term, }); diff --git a/StartPlan.tsx b/StartPlan.tsx index 0b451d0..59bd013 100644 --- a/StartPlan.tsx +++ b/StartPlan.tsx @@ -6,11 +6,17 @@ import { useRoute, } from "@react-navigation/native"; import { useCallback, useMemo, useRef, useState } from "react"; -import { FlatList, NativeModules, TextInput, View } from "react-native"; +import { + DeviceEventEmitter, + FlatList, + NativeModules, + TextInput, + View, +} from "react-native"; import { Button, IconButton, ProgressBar } from "react-native-paper"; import AppInput from "./AppInput"; import { getBestSet } from "./best.service"; -import { MARGIN, PADDING } from "./constants"; +import { GYM_SETS_EDITED, GYM_SET_CREATED, MARGIN, PADDING } from "./constants"; import CountMany from "./count-many"; import { AppDataSource } from "./data-source"; import { getNow, setRepo, settingsRepo } from "./db"; @@ -103,6 +109,7 @@ export default function StartPlan() { hidden: false, }; await setRepo.save(newSet); + DeviceEventEmitter.emit(GYM_SET_CREATED); await refresh(); if ( settings.notify && diff --git a/StartPlanItem.tsx b/StartPlanItem.tsx index 6d2a1a5..8060068 100644 --- a/StartPlanItem.tsx +++ b/StartPlanItem.tsx @@ -1,8 +1,14 @@ import { NavigationProp, useNavigation } from "@react-navigation/native"; import React, { useCallback, useState } from "react"; -import { GestureResponderEvent, ListRenderItemInfo, View } from "react-native"; +import { + DeviceEventEmitter, + GestureResponderEvent, + ListRenderItemInfo, + View, +} from "react-native"; import { List, Menu, RadioButton, useTheme } from "react-native-paper"; import { Like } from "typeorm"; +import { GYM_SET_DELETED } from "./constants"; import CountMany from "./count-many"; import { getNow, setRepo } from "./db"; import { HomePageParams } from "./home-page-params"; @@ -38,6 +44,7 @@ export default function StartPlanItem(props: Props) { setShowMenu(false); if (!first) return toast("Nothing to undo."); await setRepo.delete(first.id); + DeviceEventEmitter.emit(GYM_SET_DELETED); onUndo(); }, [setShowMenu, onUndo, item.name]); diff --git a/constants.ts b/constants.ts index a1c44b1..f541d11 100644 --- a/constants.ts +++ b/constants.ts @@ -4,3 +4,6 @@ export const ITEM_PADDING = 8; export const DARK_RIPPLE = "#444444"; export const LIGHT_RIPPLE = "#c2c2c2"; export const LIMIT = 15; +export const GYM_SET_UPDATED = "gym-set-updated"; +export const GYM_SET_CREATED = "gym-set-updated"; +export const GYM_SET_DELETED = "gym-set-updated"; diff --git a/data-source.ts b/data-source.ts index 4a87de6..6526143 100644 --- a/data-source.ts +++ b/data-source.ts @@ -1,5 +1,6 @@ import { DataSource } from "typeorm"; import GymSet from "./gym-set"; +import { GymSetSubscriber } from "./gym-set-subscriber"; import { Sets1667185586014 as sets1667185586014 } from "./migrations/1667185586014-sets"; import { plans1667186124792 } from "./migrations/1667186124792-plans"; import { settings1667186130041 } from "./migrations/1667186130041-settings"; diff --git a/home-page-params.ts b/home-page-params.ts index ca197ff..fb521d9 100644 --- a/home-page-params.ts +++ b/home-page-params.ts @@ -3,16 +3,6 @@ import GymSet from "./gym-set"; export type HomePageParams = { Sets: { search?: string; - - /** - * Reload the current list with limit = offset - */ - refresh?: number; - - /** - * Reload the list with limit = 0 - */ - reset?: number; }; EditSet: { set: GymSet;