Only reset SetList in certain situations

This reduces the jitter in the homepage
when you have scrolled down a significant
amount.

Related to #165. Still need to do other
list pages.
This commit is contained in:
Brandon Presley 2023-08-22 09:49:09 +12:00
parent 8e42e9c3e4
commit 672931746b
4 changed files with 154 additions and 72 deletions

View File

@ -1,5 +1,6 @@
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker"; import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
import { import {
NavigationProp,
RouteProp, RouteProp,
useFocusEffect, useFocusEffect,
useNavigation, useNavigation,
@ -24,7 +25,7 @@ import { fixNumeric } from "./fix-numeric";
export default function EditSet() { export default function EditSet() {
const { params } = useRoute<RouteProp<HomePageParams, "EditSet">>(); const { params } = useRoute<RouteProp<HomePageParams, "EditSet">>();
const { set } = params; const { set } = params;
const navigation = useNavigation(); const { navigate } = useNavigation<NavigationProp<HomePageParams>>();
const [settings, setSettings] = useState<Settings>({} as Settings); const [settings, setSettings] = useState<Settings>({} as Settings);
const [name, setName] = useState(set.name); const [name, setName] = useState(set.name);
const [reps, setReps] = useState(set.reps?.toString()); const [reps, setReps] = useState(set.reps?.toString());
@ -63,20 +64,18 @@ export default function EditSet() {
[settings] [settings]
); );
const added = useCallback( const added = async (value: GymSet) => {
async (value: GymSet) => { startTimer(value.name);
startTimer(value.name); console.log(`${EditSet.name}.add`, { set: value });
console.log(`${EditSet.name}.add`, { set: value }); if (!settings.notify) return;
if (!settings.notify) return; if (
if ( value.weight > set.weight ||
value.weight > set.weight || (value.reps > set.reps && value.weight === set.weight)
(value.reps > set.reps && value.weight === set.weight) ) {
) { toast("Great work King! That's a new record.");
toast("Great work King! That's a new record."); }
} navigate("Sets", { added: value.id });
}, };
[startTimer, set, settings]
);
const handleSubmit = async () => { const handleSubmit = async () => {
if (!name) return; if (!name) return;
@ -104,8 +103,9 @@ export default function EditSet() {
if (typeof set.id !== "number") newSet.created = await getNow(); if (typeof set.id !== "number") newSet.created = await getNow();
const saved = await setRepo.save(newSet); const saved = await setRepo.save(newSet);
if (typeof set.id !== "number") added(saved); if (typeof set.id !== "number") return added(saved);
navigation.goBack(); if (createdDirty) navigate("Sets", { reset: saved.id });
else navigate("Sets", { refresh: saved.id });
}; };
const changeImage = useCallback(async () => { const changeImage = useCallback(async () => {

View File

@ -1,9 +1,10 @@
import { import {
NavigationProp, NavigationProp,
useFocusEffect, RouteProp,
useNavigation, useNavigation,
useRoute,
} from "@react-navigation/native"; } from "@react-navigation/native";
import { useCallback, useState } from "react"; import { useCallback, useEffect, useMemo, 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";
@ -18,33 +19,77 @@ import SetItem from "./SetItem";
import Settings from "./settings"; import Settings from "./settings";
export default function SetList() { export default function SetList() {
const [refreshing, setRefreshing] = useState(false);
const [sets, setSets] = useState<GymSet[]>([]); const [sets, setSets] = useState<GymSet[]>([]);
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
const [term, setTerm] = useState("");
const [end, setEnd] = useState(false); const [end, setEnd] = useState(false);
const [settings, setSettings] = useState<Settings>(); const [settings, setSettings] = useState<Settings>();
const [ids, setIds] = useState<number[]>([]); const [ids, setIds] = useState<number[]>([]);
const navigation = useNavigation<NavigationProp<HomePageParams>>(); const navigation = useNavigation<NavigationProp<HomePageParams>>();
const { params } = useRoute<RouteProp<HomePageParams, "Sets">>();
const [term, setTerm] = useState(params?.search || "");
const refresh = useCallback(async (value: string) => { const refresh = async ({
const newSets = await setRepo.find({ value,
where: { name: Like(`%${value.trim()}%`), hidden: 0 as any }, take,
take: LIMIT, skip,
skip: 0, }: {
order: { created: "DESC" }, value: string;
}); take: number;
console.log(`${SetList.name}.refresh:`, { value }); skip: number;
}) => {
setRefreshing(true);
const newSets = await setRepo
.find({
where: { name: Like(`%${value.trim()}%`), hidden: 0 as any },
take,
skip,
order: { created: "DESC" },
})
.finally(() => setRefreshing(false));
console.log(`${SetList.name}.refresh:`, { value, take, offset });
setSets(newSets); setSets(newSets);
setOffset(0);
setEnd(false); setEnd(false);
};
useEffect(() => {
settingsRepo.findOne({ where: {} }).then(setSettings);
refresh({
take: LIMIT,
value: "",
skip: 0,
});
/* eslint-disable react-hooks/exhaustive-deps */
}, []); }, []);
useFocusEffect( const search = (value: string) => {
useCallback(() => { setTerm(value);
refresh(term); setOffset(0);
settingsRepo.findOne({ where: {} }).then(setSettings); refresh({
}, [refresh, term]) skip: 0,
); take: LIMIT,
value,
});
};
useEffect(() => {
if (!params) return;
console.log({ params });
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,
});
/* eslint-disable react-hooks/exhaustive-deps */
}, [params]);
const renderItem = useCallback( const renderItem = useCallback(
({ item }: { item: GymSet }) => ( ({ item }: { item: GymSet }) => (
@ -59,22 +104,29 @@ export default function SetList() {
[settings, ids] [settings, ids]
); );
const next = useCallback(async () => { const next = async () => {
if (end) return; if (end || refreshing) 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({ setRefreshing(true);
where: { name: Like(`%${term}%`), hidden: 0 as any }, const newSets = await setRepo
take: LIMIT, .find({
skip: newOffset, where: { name: Like(`%${term}%`), hidden: 0 as any },
order: { created: "DESC" }, take: LIMIT,
}); skip: newOffset,
order: { created: "DESC" },
})
.finally(() => setRefreshing(false));
if (newSets.length === 0) return setEnd(true); if (newSets.length === 0) return setEnd(true);
if (!sets) return; if (!sets) return;
setSets([...sets, ...newSets]); 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); if (newSets.length < LIMIT) return setEnd(true);
setOffset(newOffset); setOffset(newOffset);
}, [term, end, offset, sets]); };
const onAdd = useCallback(async () => { const onAdd = useCallback(async () => {
const now = await getNow(); const now = await getNow();
@ -85,14 +137,6 @@ export default function SetList() {
navigation.navigate("EditSet", { set }); navigation.navigate("EditSet", { set });
}, [navigation, sets]); }, [navigation, sets]);
const search = useCallback(
(value: string) => {
setTerm(value);
refresh(value);
},
[refresh]
);
const edit = useCallback(() => { const edit = useCallback(() => {
navigation.navigate("EditSets", { ids }); navigation.navigate("EditSets", { ids });
setIds([]); setIds([]);
@ -112,16 +156,47 @@ export default function SetList() {
setIds([]); setIds([]);
}, []); }, []);
const remove = useCallback(async () => { const remove = async () => {
setIds([]); setIds([]);
await setRepo.delete(ids.length > 0 ? ids : {}); await setRepo.delete(ids.length > 0 ? ids : {});
await refresh(term); return refresh({
}, [ids, refresh, term]); skip: 0,
take: LIMIT,
value: term,
});
};
const select = useCallback(() => { const select = useCallback(() => {
setIds(sets.map((set) => set.id)); setIds(sets.map((set) => set.id));
}, [sets]); }, [sets]);
const content = useMemo(() => {
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={false}
onRefresh={() =>
refresh({
skip: 0,
take: LIMIT,
value: term,
})
}
/>
);
}, [sets, settings, term]);
return ( return (
<> <>
<DrawerHeader name={ids.length > 0 ? `${ids.length} selected` : "Home"}> <DrawerHeader name={ids.length > 0 ? `${ids.length} selected` : "Home"}>
@ -136,21 +211,7 @@ export default function SetList() {
</DrawerHeader> </DrawerHeader>
<Page onAdd={onAdd} term={term} search={search}> <Page onAdd={onAdd} term={term} search={search}>
{sets?.length === 0 ? ( {content}
<List.Item
title="No sets yet"
description="A set is a group of repetitions. E.g. 8 reps of Squats."
/>
) : (
settings && (
<FlatList
data={sets}
style={{ flex: 1 }}
renderItem={renderItem}
onEndReached={next}
/>
)
)}
</Page> </Page>
</> </>
); );

View File

@ -5,6 +5,7 @@ import { List, Menu, RadioButton, useTheme } from "react-native-paper";
import { Like } from "typeorm"; import { Like } from "typeorm";
import CountMany from "./count-many"; import CountMany from "./count-many";
import { getNow, setRepo } from "./db"; import { getNow, setRepo } from "./db";
import { HomePageParams } from "./home-page-params";
import { PlanPageParams } from "./plan-page-params"; import { PlanPageParams } from "./plan-page-params";
import { toast } from "./toast"; import { toast } from "./toast";
@ -20,6 +21,8 @@ export default function StartPlanItem(props: Props) {
const [anchor, setAnchor] = useState({ x: 0, y: 0 }); const [anchor, setAnchor] = useState({ x: 0, y: 0 });
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const { navigate } = useNavigation<NavigationProp<PlanPageParams>>(); const { navigate } = useNavigation<NavigationProp<PlanPageParams>>();
const { navigate: navigateHome } =
useNavigation<NavigationProp<HomePageParams>>();
const undo = useCallback(async () => { const undo = useCallback(async () => {
const now = await getNow(); const now = await getNow();
@ -62,6 +65,11 @@ export default function StartPlanItem(props: Props) {
navigate("EditSet", { set: first }); navigate("EditSet", { set: first });
}, [item.name, navigate]); }, [item.name, navigate]);
const view = () => {
setShowMenu(false);
navigateHome("Sets", { search: item.name });
};
const left = useCallback( const left = useCallback(
() => ( () => (
<View style={{ alignItems: "center", justifyContent: "center" }}> <View style={{ alignItems: "center", justifyContent: "center" }}>
@ -89,6 +97,7 @@ 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="edit" onPress={edit} title="Edit" /> <Menu.Item leadingIcon="edit" onPress={edit} title="Edit" />
<Menu.Item leadingIcon="undo" onPress={undo} title="Undo" /> <Menu.Item leadingIcon="undo" onPress={undo} title="Undo" />
</Menu> </Menu>

View File

@ -1,7 +1,19 @@
import GymSet from "./gym-set"; import GymSet from "./gym-set";
export type HomePageParams = { export type HomePageParams = {
Sets: {}; Sets: {
search?: string;
/**
* Reload the current list with limit = offset
*/
refresh?: number;
/**
* Reload the list with limit = 0
*/
reset?: number;
};
EditSet: { EditSet: {
set: GymSet; set: GymSet;
}; };