From e6dcd4a47e4c855d652ca4ddbb616e464cba2888 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Fri, 28 Oct 2022 18:36:47 +1300 Subject: [PATCH 01/78] Use hermes engine https://reactnative.dev/docs/hermes --- App.tsx | 2 +- BestList.tsx | 2 +- BestPage.tsx | 1 - Chart.tsx | 1 - ConfirmDialog.tsx | 1 - DrawerHeader.tsx | 1 - DrawerMenu.tsx | 2 +- EditPlan.tsx | 2 +- EditSet.tsx | 2 +- EditWorkout.tsx | 2 +- HomePage.tsx | 1 - MassiveFab.tsx | 6 ++---- MassiveInput.tsx | 6 +++--- MassiveSnack.tsx | 4 ++-- Page.tsx | 1 - PlanItem.tsx | 2 +- PlanList.tsx | 2 +- PlanPage.tsx | 1 - Routes.tsx | 1 - SetForm.tsx | 2 +- SetItem.tsx | 2 +- SetList.tsx | 2 +- SettingsPage.tsx | 2 +- StackHeader.tsx | 1 - StartPlan.tsx | 2 +- Switch.tsx | 2 +- ViewBest.tsx | 2 +- WorkoutItem.tsx | 2 +- WorkoutList.tsx | 2 +- WorkoutsPage.tsx | 1 - android/app/build.gradle | 4 ++-- android/app/proguard-rules.pro | 3 +++ 32 files changed, 29 insertions(+), 38 deletions(-) diff --git a/App.tsx b/App.tsx index 7e5ce15..b75bc2d 100644 --- a/App.tsx +++ b/App.tsx @@ -3,7 +3,7 @@ import { DefaultTheme as NavigationDefaultTheme, NavigationContainer, } from '@react-navigation/native'; -import React, {useEffect, useMemo, useState} from 'react'; +import {useEffect, useMemo, useState} from 'react'; import {useColorScheme} from 'react-native'; import { DarkTheme as PaperDarkTheme, diff --git a/BestList.tsx b/BestList.tsx index 6e982f3..ae1de94 100644 --- a/BestList.tsx +++ b/BestList.tsx @@ -3,7 +3,7 @@ import { useFocusEffect, useNavigation, } from '@react-navigation/native'; -import React, {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {FlatList, Image} from 'react-native'; import {List} from 'react-native-paper'; import {getBestReps, getBestWeights} from './best.service'; diff --git a/BestPage.tsx b/BestPage.tsx index f3af6b1..520eff0 100644 --- a/BestPage.tsx +++ b/BestPage.tsx @@ -1,5 +1,4 @@ import {createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; import BestList from './BestList'; import Set from './set'; import ViewBest from './ViewBest'; diff --git a/Chart.tsx b/Chart.tsx index 35db05f..a035658 100644 --- a/Chart.tsx +++ b/Chart.tsx @@ -1,5 +1,4 @@ import * as shape from 'd3-shape'; -import React from 'react'; import {View} from 'react-native'; import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts'; import {CombinedDarkTheme, CombinedDefaultTheme} from './App'; diff --git a/ConfirmDialog.tsx b/ConfirmDialog.tsx index adb08da..e316950 100644 --- a/ConfirmDialog.tsx +++ b/ConfirmDialog.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import {Button, Dialog, Portal, Text} from 'react-native-paper'; export default function ConfirmDialog({ diff --git a/DrawerHeader.tsx b/DrawerHeader.tsx index 34c90d3..d7f6351 100644 --- a/DrawerHeader.tsx +++ b/DrawerHeader.tsx @@ -1,6 +1,5 @@ import {DrawerNavigationProp} from '@react-navigation/drawer'; import {useNavigation} from '@react-navigation/native'; -import React from 'react'; import {Appbar, IconButton} from 'react-native-paper'; import {DrawerParamList} from './drawer-param-list'; import DrawerMenu from './DrawerMenu'; diff --git a/DrawerMenu.tsx b/DrawerMenu.tsx index 2c8734c..7cf021c 100644 --- a/DrawerMenu.tsx +++ b/DrawerMenu.tsx @@ -1,5 +1,5 @@ import {NavigationProp, useNavigation} from '@react-navigation/native'; -import React, {useCallback, useState} from 'react'; +import {useCallback, useState} from 'react'; import DocumentPicker from 'react-native-document-picker'; import {FileSystem} from 'react-native-file-access'; import {Divider, IconButton, Menu} from 'react-native-paper'; diff --git a/EditPlan.tsx b/EditPlan.tsx index 0e65214..0536d40 100644 --- a/EditPlan.tsx +++ b/EditPlan.tsx @@ -4,7 +4,7 @@ import { useNavigation, useRoute, } from '@react-navigation/native'; -import React, {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {ScrollView, StyleSheet, View} from 'react-native'; import {Button, Text} from 'react-native-paper'; import {MARGIN, PADDING} from './constants'; diff --git a/EditSet.tsx b/EditSet.tsx index f330962..a346ddb 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -1,5 +1,5 @@ import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; -import React, {useCallback} from 'react'; +import {useCallback} from 'react'; import {NativeModules, View} from 'react-native'; import {PADDING} from './constants'; import {HomePageParams} from './home-page-params'; diff --git a/EditWorkout.tsx b/EditWorkout.tsx index e6a90c6..6a6f09d 100644 --- a/EditWorkout.tsx +++ b/EditWorkout.tsx @@ -1,5 +1,5 @@ import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; -import React, {useCallback, useRef, useState} from 'react'; +import {useCallback, useRef, useState} from 'react'; import {ScrollView, TextInput, View} from 'react-native'; import DocumentPicker from 'react-native-document-picker'; import {Button, Card, TouchableRipple} from 'react-native-paper'; diff --git a/HomePage.tsx b/HomePage.tsx index 61cc472..03d9d66 100644 --- a/HomePage.tsx +++ b/HomePage.tsx @@ -1,5 +1,4 @@ import {createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; import EditSet from './EditSet'; import {HomePageParams} from './home-page-params'; import SetList from './SetList'; diff --git a/MassiveFab.tsx b/MassiveFab.tsx index ba82bfd..bf60392 100644 --- a/MassiveFab.tsx +++ b/MassiveFab.tsx @@ -1,11 +1,9 @@ -import React from 'react'; +import {ComponentProps} from 'react'; import {FAB} from 'react-native-paper'; import {useColor} from './color'; import {lightColors} from './colors'; -export default function MassiveFab( - props: Partial>, -) { +export default function MassiveFab(props: Partial>) { const {color} = useColor(); const fabColor = lightColors.map(lightColor => lightColor.hex).includes(color) ? 'black' diff --git a/MassiveInput.tsx b/MassiveInput.tsx index 0ee3f49..2c81648 100644 --- a/MassiveInput.tsx +++ b/MassiveInput.tsx @@ -1,12 +1,12 @@ -import React from 'react'; +import {ComponentProps, Ref} from 'react'; import {TextInput} from 'react-native-paper'; import {CombinedDefaultTheme} from './App'; import {MARGIN} from './constants'; import useDark from './use-dark'; export default function MassiveInput( - props: Partial> & { - innerRef?: React.Ref; + props: Partial> & { + innerRef?: Ref; }, ) { const dark = useDark(); diff --git a/MassiveSnack.tsx b/MassiveSnack.tsx index 38f75db..2f2313d 100644 --- a/MassiveSnack.tsx +++ b/MassiveSnack.tsx @@ -1,9 +1,9 @@ -import React, {useContext, useState} from 'react'; +import {createContext, useContext, useState} from 'react'; import {Snackbar} from 'react-native-paper'; import {CombinedDarkTheme, CombinedDefaultTheme} from './App'; import useDark from './use-dark'; -export const SnackbarContext = React.createContext<{ +export const SnackbarContext = createContext<{ toast: (value: string, timeout: number) => void; }>({toast: () => null}); diff --git a/Page.tsx b/Page.tsx index c945fc4..13cb08b 100644 --- a/Page.tsx +++ b/Page.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import {StyleSheet, View} from 'react-native'; import {Searchbar} from 'react-native-paper'; import {PADDING} from './constants'; diff --git a/PlanItem.tsx b/PlanItem.tsx index c86025b..1e15568 100644 --- a/PlanItem.tsx +++ b/PlanItem.tsx @@ -3,7 +3,7 @@ import { useFocusEffect, useNavigation, } from '@react-navigation/native'; -import React, {useCallback, useMemo, useState} from 'react'; +import {useCallback, useMemo, useState} from 'react'; import {GestureResponderEvent, Text} from 'react-native'; import {Divider, List, Menu} from 'react-native-paper'; import {getBestSet} from './best.service'; diff --git a/PlanList.tsx b/PlanList.tsx index af2dd85..8522cef 100644 --- a/PlanList.tsx +++ b/PlanList.tsx @@ -3,7 +3,7 @@ import { useFocusEffect, useNavigation, } from '@react-navigation/native'; -import React, {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {FlatList} from 'react-native'; import {List} from 'react-native-paper'; import DrawerHeader from './DrawerHeader'; diff --git a/PlanPage.tsx b/PlanPage.tsx index 86d5e7f..980ca2a 100644 --- a/PlanPage.tsx +++ b/PlanPage.tsx @@ -1,5 +1,4 @@ import {createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; import EditPlan from './EditPlan'; import {PlanPageParams} from './plan-page-params'; import PlanList from './PlanList'; diff --git a/Routes.tsx b/Routes.tsx index 7bad039..028cd97 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -1,5 +1,4 @@ import {createDrawerNavigator} from '@react-navigation/drawer'; -import React from 'react'; import {IconButton} from 'react-native-paper'; import BestPage from './BestPage'; import {DrawerParamList} from './drawer-param-list'; diff --git a/SetForm.tsx b/SetForm.tsx index f95ce1e..285e610 100644 --- a/SetForm.tsx +++ b/SetForm.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useRef, useState} from 'react'; +import {useCallback, useRef, useState} from 'react'; import {TextInput, View} from 'react-native'; import DocumentPicker from 'react-native-document-picker'; import {Button, Card, TouchableRipple} from 'react-native-paper'; diff --git a/SetItem.tsx b/SetItem.tsx index a566fc0..eaa3fc8 100644 --- a/SetItem.tsx +++ b/SetItem.tsx @@ -1,5 +1,5 @@ import {NavigationProp, useNavigation} from '@react-navigation/native'; -import React, {useCallback, useState} from 'react'; +import {useCallback, useState} from 'react'; import {GestureResponderEvent, Image} from 'react-native'; import {Divider, List, Menu, Text} from 'react-native-paper'; import {HomePageParams} from './home-page-params'; diff --git a/SetList.tsx b/SetList.tsx index dfde9a0..47bc02b 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -3,7 +3,7 @@ import { useFocusEffect, useNavigation, } from '@react-navigation/native'; -import React, {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {FlatList} from 'react-native'; import {List} from 'react-native-paper'; import DrawerHeader from './DrawerHeader'; diff --git a/SettingsPage.tsx b/SettingsPage.tsx index 5dea2b9..8aa9e74 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -1,6 +1,6 @@ import {Picker} from '@react-native-picker/picker'; import {useFocusEffect} from '@react-navigation/native'; -import React, {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {NativeModules, ScrollView} from 'react-native'; import DocumentPicker from 'react-native-document-picker'; import {Button} from 'react-native-paper'; diff --git a/StackHeader.tsx b/StackHeader.tsx index aa45a5d..b02ad6f 100644 --- a/StackHeader.tsx +++ b/StackHeader.tsx @@ -1,5 +1,4 @@ import {useNavigation} from '@react-navigation/native'; -import React from 'react'; import Share from 'react-native-share'; import {FileSystem} from 'react-native-file-access'; import {Appbar, IconButton} from 'react-native-paper'; diff --git a/StartPlan.tsx b/StartPlan.tsx index 4be2e9c..d74bb6e 100644 --- a/StartPlan.tsx +++ b/StartPlan.tsx @@ -1,5 +1,5 @@ import {RouteProp, useFocusEffect, useRoute} from '@react-navigation/native'; -import React, {useCallback, useMemo, useRef, useState} from 'react'; +import {useCallback, useMemo, useRef, useState} from 'react'; import {NativeModules, TextInput, View} from 'react-native'; import {FlatList} from 'react-native-gesture-handler'; import {Button, List, RadioButton} from 'react-native-paper'; diff --git a/Switch.tsx b/Switch.tsx index 7f940a4..4d5454d 100644 --- a/Switch.tsx +++ b/Switch.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import {useMemo} from 'react'; import {Pressable} from 'react-native'; import {Switch as PaperSwitch, Text} from 'react-native-paper'; import {CombinedDarkTheme, CombinedDefaultTheme} from './App'; diff --git a/ViewBest.tsx b/ViewBest.tsx index 492e5f7..a9ca740 100644 --- a/ViewBest.tsx +++ b/ViewBest.tsx @@ -1,6 +1,6 @@ import {Picker} from '@react-native-picker/picker'; import {RouteProp, useRoute} from '@react-navigation/native'; -import React, {useEffect, useState} from 'react'; +import {useEffect, useState} from 'react'; import {View} from 'react-native'; import {getOneRepMax, getVolumes, getWeightsBy} from './best.service'; import {BestPageParams} from './BestPage'; diff --git a/WorkoutItem.tsx b/WorkoutItem.tsx index ad001e6..8dbdf84 100644 --- a/WorkoutItem.tsx +++ b/WorkoutItem.tsx @@ -1,5 +1,5 @@ import {NavigationProp, useNavigation} from '@react-navigation/native'; -import React, {useCallback, useMemo, useState} from 'react'; +import {useCallback, useMemo, useState} from 'react'; import {GestureResponderEvent, Image} from 'react-native'; import {List, Menu, Text} from 'react-native-paper'; import ConfirmDialog from './ConfirmDialog'; diff --git a/WorkoutList.tsx b/WorkoutList.tsx index 493439c..4f12fe3 100644 --- a/WorkoutList.tsx +++ b/WorkoutList.tsx @@ -3,7 +3,7 @@ import { useFocusEffect, useNavigation, } from '@react-navigation/native'; -import React, {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {FlatList} from 'react-native'; import {List} from 'react-native-paper'; import DrawerHeader from './DrawerHeader'; diff --git a/WorkoutsPage.tsx b/WorkoutsPage.tsx index 06b132f..8b5268e 100644 --- a/WorkoutsPage.tsx +++ b/WorkoutsPage.tsx @@ -1,5 +1,4 @@ import {createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; import EditWorkout from './EditWorkout'; import Set from './set'; import WorkoutList from './WorkoutList'; diff --git a/android/app/build.gradle b/android/app/build.gradle index 6191837..63798de 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -4,7 +4,7 @@ apply plugin: "kotlin-android" import com.android.build.OutputFile project.ext.react = [ - enableHermes: false, // clean and rebuild if changing + enableHermes: true, // clean and rebuild if changing ] project.ext.vectoricons = [ @@ -17,7 +17,7 @@ apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" def enableSeparateBuildPerCPUArchitecture = true def enableProguardInReleaseBuilds = true def jscFlavor = 'org.webkit:android-jsc:+' -def enableHermes = project.ext.react.get("enableHermes", false); +def enableHermes = project.ext.react.get("enableHermes", true); def reactNativeArchitectures() { def value = project.getProperties().get("reactNativeArchitectures") diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index b691e04..a249924 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -44,3 +44,6 @@ -dontwarn java.nio.file.* -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -dontwarn okio.** + +-keep class com.facebook.hermes.unicode.** { *; } +-keep class com.facebook.jni.** { *; } From 3d591f4618539d5880abaa3c00cc542a0b1e093c Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Fri, 28 Oct 2022 18:41:17 +1300 Subject: [PATCH 02/78] Prevent searching twice on homepage first load --- SetList.tsx | 57 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/SetList.tsx b/SetList.tsx index 47bc02b..f55e0b4 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -3,7 +3,7 @@ import { useFocusEffect, useNavigation, } from '@react-navigation/native'; -import {useCallback, useEffect, useState} from 'react'; +import {useCallback, useState} from 'react'; import {FlatList} from 'react-native'; import {List} from 'react-native-paper'; import DrawerHeader from './DrawerHeader'; @@ -25,37 +25,36 @@ export default function SetList() { const {settings} = useSettings(); const navigation = useNavigation>(); - const refresh = useCallback(async () => { - const todaysSet = await getToday(); - if (todaysSet) setSet({...todaysSet}); - const newSets = await getSets({ - search: `%${search}%`, - limit, - offset: 0, - format: settings.date || '%Y-%m-%d %H:%M', - }); - console.log(`${SetList.name}.refresh:`, {first: newSets[0]}); - if (newSets.length === 0) return setSets([]); - setSets(newSets); - setOffset(0); - setEnd(false); - }, [search, settings.date]); + const refresh = useCallback( + async (term: string) => { + const todaysSet = await getToday(); + if (todaysSet) setSet({...todaysSet}); + const newSets = await getSets({ + search: `%${term}%`, + limit, + offset: 0, + format: settings.date || '%Y-%m-%d %H:%M', + }); + console.log(`${SetList.name}.refresh:`, {first: newSets[0]}); + if (newSets.length === 0) return setSets([]); + setSets(newSets); + setOffset(0); + setEnd(false); + }, + [settings.date], + ); useFocusEffect( useCallback(() => { - refresh(); - }, [refresh]), + refresh(search); + }, [refresh, search]), ); - useEffect(() => { - refresh(); - }, [search, refresh]); - const renderItem = useCallback( ({item}: {item: Set}) => ( - + refresh(search)} /> ), - [refresh], + [refresh, search], ); const next = useCallback(async () => { @@ -82,10 +81,18 @@ export default function SetList() { }); }, [navigation, set]); + const handleSearch = useCallback( + (value: string) => { + setSearch(value); + refresh(value); + }, + [refresh], + ); + return ( <> - + {sets?.length === 0 ? ( Date: Fri, 28 Oct 2022 18:59:54 +1300 Subject: [PATCH 03/78] Prevent double searching everywhere Also change variable names. Search should represent the act of searching, rather than the value being typed by the user. --- BestList.tsx | 26 +++++++++++++++----------- Page.tsx | 10 +++++----- PlanList.tsx | 30 +++++++++++++++++------------- SetForm.tsx | 2 +- SetList.tsx | 27 +++++++++++++-------------- SettingsPage.tsx | 14 +++++++------- WorkoutList.tsx | 42 +++++++++++++++++++++++++----------------- set.service.ts | 12 +++++------- 8 files changed, 88 insertions(+), 75 deletions(-) diff --git a/BestList.tsx b/BestList.tsx index ae1de94..97cf8e1 100644 --- a/BestList.tsx +++ b/BestList.tsx @@ -3,7 +3,7 @@ import { useFocusEffect, useNavigation, } from '@react-navigation/native'; -import {useCallback, useEffect, useState} from 'react'; +import {useCallback, useState} from 'react'; import {FlatList, Image} from 'react-native'; import {List} from 'react-native-paper'; import {getBestReps, getBestWeights} from './best.service'; @@ -15,12 +15,12 @@ import {useSettings} from './use-settings'; export default function BestList() { const [bests, setBests] = useState(); - const [search, setSearch] = useState(''); + const [term, setTerm] = useState(''); const navigation = useNavigation>(); const {settings} = useSettings(); - const refresh = useCallback(async () => { - const weights = await getBestWeights(search); + const refresh = useCallback(async (value: string) => { + const weights = await getBestWeights(value); console.log(`${BestList.name}.refresh:`, {length: weights.length}); let newBest: Set[] = []; for (const set of weights) { @@ -28,17 +28,21 @@ export default function BestList() { newBest.push(...reps); } setBests(newBest); - }, [search]); + }, []); useFocusEffect( useCallback(() => { - refresh(); - }, [refresh]), + refresh(term); + }, [refresh, term]), ); - useEffect(() => { - refresh(); - }, [search, refresh]); + const search = useCallback( + (value: string) => { + setTerm(value); + refresh(value); + }, + [refresh], + ); const renderItem = ({item}: {item: Set}) => ( - + {bests?.length === 0 ? ( void; - search: string; - setSearch: (value: string) => void; + term: string; + search: (value: string) => void; }) { return ( diff --git a/PlanList.tsx b/PlanList.tsx index 8522cef..81d3cc6 100644 --- a/PlanList.tsx +++ b/PlanList.tsx @@ -3,7 +3,7 @@ import { useFocusEffect, useNavigation, } from '@react-navigation/native'; -import {useCallback, useEffect, useState} from 'react'; +import {useCallback, useState} from 'react'; import {FlatList} from 'react-native'; import {List} from 'react-native-paper'; import DrawerHeader from './DrawerHeader'; @@ -14,29 +14,33 @@ import {getPlans} from './plan.service'; import PlanItem from './PlanItem'; export default function PlanList() { - const [search, setSearch] = useState(''); + const [term, setTerm] = useState(''); const [plans, setPlans] = useState(); const navigation = useNavigation>(); - const refresh = useCallback(async () => { - getPlans(search).then(setPlans); - }, [search]); + const refresh = useCallback(async (value: string) => { + getPlans(value).then(setPlans); + }, []); useFocusEffect( useCallback(() => { - refresh(); - }, [refresh]), + refresh(term); + }, [refresh, term]), ); - useEffect(() => { - refresh(); - }, [search, refresh]); + const search = useCallback( + (value: string) => { + setTerm(value); + refresh(value); + }, + [refresh], + ); const renderItem = useCallback( ({item}: {item: Plan}) => ( - + refresh(term)} /> ), - [refresh], + [refresh, term], ); const onAdd = () => @@ -45,7 +49,7 @@ export default function PlanList() { return ( <> - + {plans?.length === 0 ? ( gotSet?.image, ); console.log(`${SetForm.name}.handleSubmit:`, {image}); diff --git a/SetList.tsx b/SetList.tsx index f55e0b4..66a597c 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -20,17 +20,17 @@ export default function SetList() { const [sets, setSets] = useState(); const [set, setSet] = useState(); const [offset, setOffset] = useState(0); - const [search, setSearch] = useState(''); + const [term, setTerm] = useState(''); const [end, setEnd] = useState(false); const {settings} = useSettings(); const navigation = useNavigation>(); const refresh = useCallback( - async (term: string) => { + async (value: string) => { const todaysSet = await getToday(); if (todaysSet) setSet({...todaysSet}); const newSets = await getSets({ - search: `%${term}%`, + term: `%${value}%`, limit, offset: 0, format: settings.date || '%Y-%m-%d %H:%M', @@ -46,33 +46,32 @@ export default function SetList() { useFocusEffect( useCallback(() => { - refresh(search); - }, [refresh, search]), + refresh(term); + }, [refresh, term]), ); const renderItem = useCallback( ({item}: {item: Set}) => ( - refresh(search)} /> + refresh(term)} /> ), - [refresh, search], + [refresh, term], ); const next = useCallback(async () => { if (end) return; const newOffset = offset + limit; - console.log(`${SetList.name}.next:`, {offset, newOffset, search}); + console.log(`${SetList.name}.next:`, {offset, newOffset, term}); const newSets = await getSets({ - search: `%${search}%`, + term: `%${term}%`, limit, offset: newOffset, - format: settings.date || '%Y-%m-%d %H:%M', }); if (newSets.length === 0) return setEnd(true); if (!sets) return; setSets([...sets, ...newSets]); if (newSets.length < limit) return setEnd(true); setOffset(newOffset); - }, [search, end, offset, sets, settings.date]); + }, [term, end, offset, sets]); const onAdd = useCallback(async () => { console.log(`${SetList.name}.onAdd`, {set}); @@ -81,9 +80,9 @@ export default function SetList() { }); }, [navigation, set]); - const handleSearch = useCallback( + const search = useCallback( (value: string) => { - setSearch(value); + setTerm(value); refresh(value); }, [refresh], @@ -92,7 +91,7 @@ export default function SetList() { return ( <> - + {sets?.length === 0 ? ( - + {switches .filter(input => - input.name.toLowerCase().includes(search.toLowerCase()), + input.name.toLowerCase().includes(term.toLowerCase()), ) .map(input => ( ))} - {'theme'.includes(search.toLowerCase()) && ( + {'theme'.includes(term.toLowerCase()) && ( )} - {'color'.includes(search.toLowerCase()) && ( + {'color'.includes(term.toLowerCase()) && ( )} - {'date format'.includes(search.toLowerCase()) && ( + {'date format'.includes(term.toLowerCase()) && ( )} - {'alarm sound'.includes(search.toLowerCase()) && ( + {'alarm sound'.includes(term.toLowerCase()) && ( @@ -127,7 +127,7 @@ export default function EditPlan() { )} - ); + ) } const styles = StyleSheet.create({ @@ -135,4 +135,4 @@ const styles = StyleSheet.create({ fontSize: 20, marginBottom: MARGIN, }, -}); +}) diff --git a/EditSet.tsx b/EditSet.tsx index ecca964..13e7000 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -1,64 +1,64 @@ -import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; -import {useCallback} from 'react'; -import {NativeModules, View} from 'react-native'; -import {PADDING} from './constants'; -import {getNow, setRepo} from './db'; -import GymSet from './gym-set'; -import {HomePageParams} from './home-page-params'; -import {useSnackbar} from './MassiveSnack'; -import SetForm from './SetForm'; -import StackHeader from './StackHeader'; -import {useSettings} from './use-settings'; +import {RouteProp, useNavigation, useRoute} from '@react-navigation/native' +import {useCallback} from 'react' +import {NativeModules, View} from 'react-native' +import {PADDING} from './constants' +import {getNow, setRepo} from './db' +import GymSet from './gym-set' +import {HomePageParams} from './home-page-params' +import {useSnackbar} from './MassiveSnack' +import SetForm from './SetForm' +import StackHeader from './StackHeader' +import {useSettings} from './use-settings' export default function EditSet() { - const {params} = useRoute>(); - const {set} = params; - const navigation = useNavigation(); - const {toast} = useSnackbar(); - const {settings} = useSettings(); + const {params} = useRoute>() + const {set} = params + const navigation = useNavigation() + const {toast} = useSnackbar() + const {settings} = useSettings() const startTimer = useCallback( async (name: string) => { - if (!settings.alarm) return; - const {minutes, seconds} = await setRepo.findOne({where: {name}}); - const milliseconds = (minutes ?? 3) * 60 * 1000 + (seconds ?? 0) * 1000; + if (!settings.alarm) return + const {minutes, seconds} = await setRepo.findOne({where: {name}}) + const milliseconds = (minutes ?? 3) * 60 * 1000 + (seconds ?? 0) * 1000 NativeModules.AlarmModule.timer( milliseconds, !!settings.vibrate, settings.sound, !!settings.noSound, - ); + ) }, [settings], - ); + ) const add = useCallback( async (value: GymSet) => { - startTimer(value.name); - const [{now}] = await getNow(); - value.created = now; - value.hidden = false; - console.log(`${EditSet.name}.add`, {set: value}); - const result = await setRepo.save(value); - console.log({result}); - if (!settings.notify) return; + startTimer(value.name) + const [{now}] = await getNow() + value.created = now + value.hidden = false + console.log(`${EditSet.name}.add`, {set: value}) + const result = await setRepo.save(value) + console.log({result}) + if (!settings.notify) return if ( value.weight > set.weight || (value.reps > set.reps && value.weight === set.weight) ) - toast("Great work King! That's a new record.", 3000); + toast("Great work King! That's a new record.", 3000) }, [startTimer, set, toast, settings], - ); + ) const save = useCallback( async (value: GymSet) => { - if (typeof set.id === 'number') await setRepo.save(value); - else await add(value); - navigation.goBack(); + if (typeof set.id === 'number') await setRepo.save(value) + else await add(value) + navigation.goBack() }, [add, set.id, navigation], - ); + ) return ( <> @@ -67,5 +67,5 @@ export default function EditSet() { - ); + ) } diff --git a/EditWorkout.tsx b/EditWorkout.tsx index 206f8d0..46870b0 100644 --- a/EditWorkout.tsx +++ b/EditWorkout.tsx @@ -1,39 +1,39 @@ -import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; -import {useCallback, useRef, useState} from 'react'; -import {ScrollView, TextInput, View} from 'react-native'; -import DocumentPicker from 'react-native-document-picker'; -import {Button, Card, TouchableRipple} from 'react-native-paper'; -import {Like} from 'typeorm'; -import ConfirmDialog from './ConfirmDialog'; -import {MARGIN, PADDING} from './constants'; -import {getNow, planRepo, setRepo} from './db'; -import MassiveInput from './MassiveInput'; -import {useSnackbar} from './MassiveSnack'; -import StackHeader from './StackHeader'; -import {useSettings} from './use-settings'; -import {WorkoutsPageParams} from './WorkoutsPage'; +import {RouteProp, useNavigation, useRoute} from '@react-navigation/native' +import {useCallback, useRef, useState} from 'react' +import {ScrollView, TextInput, View} from 'react-native' +import DocumentPicker from 'react-native-document-picker' +import {Button, Card, TouchableRipple} from 'react-native-paper' +import {Like} from 'typeorm' +import ConfirmDialog from './ConfirmDialog' +import {MARGIN, PADDING} from './constants' +import {getNow, planRepo, setRepo} from './db' +import MassiveInput from './MassiveInput' +import {useSnackbar} from './MassiveSnack' +import StackHeader from './StackHeader' +import {useSettings} from './use-settings' +import {WorkoutsPageParams} from './WorkoutsPage' export default function EditWorkout() { - const {params} = useRoute>(); - const [removeImage, setRemoveImage] = useState(false); - const [showRemove, setShowRemove] = useState(false); - const [name, setName] = useState(params.value.name); - const [steps, setSteps] = useState(params.value.steps); - const [uri, setUri] = useState(params.value.image); + const {params} = useRoute>() + const [removeImage, setRemoveImage] = useState(false) + const [showRemove, setShowRemove] = useState(false) + const [name, setName] = useState(params.value.name) + const [steps, setSteps] = useState(params.value.steps) + const [uri, setUri] = useState(params.value.image) const [minutes, setMinutes] = useState( params.value.minutes?.toString() ?? '3', - ); + ) const [seconds, setSeconds] = useState( params.value.seconds?.toString() ?? '30', - ); - const [sets, setSets] = useState(params.value.sets?.toString() ?? '3'); - const {toast} = useSnackbar(); - const navigation = useNavigation(); - const setsRef = useRef(null); - const stepsRef = useRef(null); - const minutesRef = useRef(null); - const secondsRef = useRef(null); - const {settings} = useSettings(); + ) + const [sets, setSets] = useState(params.value.sets?.toString() ?? '3') + const {toast} = useSnackbar() + const navigation = useNavigation() + const setsRef = useRef(null) + const stepsRef = useRef(null) + const minutesRef = useRef(null) + const secondsRef = useRef(null) + const {settings} = useSettings() const update = async () => { await setRepo.update( @@ -46,18 +46,18 @@ export default function EditWorkout() { steps, image: removeImage ? '' : uri, }, - ); + ) await planRepo.query( `UPDATE plans SET workouts = REPLACE(workouts, $1, $2) WHERE workouts LIKE $3`, [params.value.name, name, `%${params.value.name}%`], - ); - navigation.goBack(); - }; + ) + navigation.goBack() + } const add = async () => { - const [{now}] = await getNow(); + const [{now}] = await getNow() await setRepo.save({ name, reps: 0, @@ -69,45 +69,45 @@ export default function EditWorkout() { sets: sets ? +sets : 3, steps, created: now, - }); - navigation.goBack(); - }; + }) + navigation.goBack() + } const save = async () => { - if (params.value.name) return update(); - return add(); - }; + if (params.value.name) return update() + return add() + } const changeImage = useCallback(async () => { const {fileCopyUri} = await DocumentPicker.pickSingle({ type: 'image/*', copyTo: 'documentDirectory', - }); - if (fileCopyUri) setUri(fileCopyUri); - }, []); + }) + if (fileCopyUri) setUri(fileCopyUri) + }, []) const handleRemove = useCallback(async () => { - setUri(''); - setRemoveImage(true); - setShowRemove(false); - }, []); + setUri('') + setRemoveImage(true) + setShowRemove(false) + }, []) const handleName = (value: string) => { - setName(value.replace(/,|'/g, '')); + setName(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000); - }; + toast('Commas and single quotes would break CSV exports', 6000) + } const handleSteps = (value: string) => { - setSteps(value.replace(/,|'/g, '')); + setSteps(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000); - }; + toast('Commas and single quotes would break CSV exports', 6000) + } const submitName = () => { - if (settings.steps) stepsRef.current?.focus(); - else setsRef.current?.focus(); - }; + if (settings.steps) stepsRef.current?.focus() + else setsRef.current?.focus() + } return ( <> @@ -191,5 +191,5 @@ export default function EditWorkout() { - ); + ) } diff --git a/HomePage.tsx b/HomePage.tsx index 03d9d66..891eaac 100644 --- a/HomePage.tsx +++ b/HomePage.tsx @@ -1,9 +1,9 @@ -import {createStackNavigator} from '@react-navigation/stack'; -import EditSet from './EditSet'; -import {HomePageParams} from './home-page-params'; -import SetList from './SetList'; +import {createStackNavigator} from '@react-navigation/stack' +import EditSet from './EditSet' +import {HomePageParams} from './home-page-params' +import SetList from './SetList' -const Stack = createStackNavigator(); +const Stack = createStackNavigator() export default function HomePage() { return ( @@ -12,5 +12,5 @@ export default function HomePage() { - ); + ) } diff --git a/MassiveFab.tsx b/MassiveFab.tsx index bf60392..0991091 100644 --- a/MassiveFab.tsx +++ b/MassiveFab.tsx @@ -1,13 +1,13 @@ -import {ComponentProps} from 'react'; -import {FAB} from 'react-native-paper'; -import {useColor} from './color'; -import {lightColors} from './colors'; +import {ComponentProps} from 'react' +import {FAB} from 'react-native-paper' +import {useColor} from './color' +import {lightColors} from './colors' export default function MassiveFab(props: Partial>) { - const {color} = useColor(); + const {color} = useColor() const fabColor = lightColors.map(lightColor => lightColor.hex).includes(color) ? 'black' - : undefined; + : undefined return ( >) { }} {...props} /> - ); + ) } diff --git a/MassiveInput.tsx b/MassiveInput.tsx index 2c81648..8b51717 100644 --- a/MassiveInput.tsx +++ b/MassiveInput.tsx @@ -1,15 +1,15 @@ -import {ComponentProps, Ref} from 'react'; -import {TextInput} from 'react-native-paper'; -import {CombinedDefaultTheme} from './App'; -import {MARGIN} from './constants'; -import useDark from './use-dark'; +import {ComponentProps, Ref} from 'react' +import {TextInput} from 'react-native-paper' +import {CombinedDefaultTheme} from './App' +import {MARGIN} from './constants' +import useDark from './use-dark' export default function MassiveInput( props: Partial> & { - innerRef?: Ref; + innerRef?: Ref }, ) { - const dark = useDark(); + const dark = useDark() return ( - ); + ) } diff --git a/MassiveSnack.tsx b/MassiveSnack.tsx index 2f2313d..3613189 100644 --- a/MassiveSnack.tsx +++ b/MassiveSnack.tsx @@ -1,31 +1,31 @@ -import {createContext, useContext, useState} from 'react'; -import {Snackbar} from 'react-native-paper'; -import {CombinedDarkTheme, CombinedDefaultTheme} from './App'; -import useDark from './use-dark'; +import {createContext, useContext, useState} from 'react' +import {Snackbar} from 'react-native-paper' +import {CombinedDarkTheme, CombinedDefaultTheme} from './App' +import useDark from './use-dark' export const SnackbarContext = createContext<{ - toast: (value: string, timeout: number) => void; -}>({toast: () => null}); + toast: (value: string, timeout: number) => void +}>({toast: () => null}) export const useSnackbar = () => { - return useContext(SnackbarContext); -}; + return useContext(SnackbarContext) +} export default function MassiveSnack({ children, }: { - children?: JSX.Element[] | JSX.Element; + children?: JSX.Element[] | JSX.Element }) { - const [snackbar, setSnackbar] = useState(''); - const [timeoutId, setTimeoutId] = useState(0); - const dark = useDark(); + const [snackbar, setSnackbar] = useState('') + const [timeoutId, setTimeoutId] = useState(0) + const dark = useDark() const toast = (value: string, timeout: number) => { - setSnackbar(value); - clearTimeout(timeoutId); - const id = setTimeout(() => setSnackbar(''), timeout); - setTimeoutId(id); - }; + setSnackbar(value) + clearTimeout(timeoutId) + const id = setTimeout(() => setSnackbar(''), timeout) + setTimeoutId(id) + } return ( <> @@ -45,5 +45,5 @@ export default function MassiveSnack({ {snackbar} - ); + ) } diff --git a/Page.tsx b/Page.tsx index aae1404..eb65413 100644 --- a/Page.tsx +++ b/Page.tsx @@ -1,7 +1,7 @@ -import {StyleSheet, View} from 'react-native'; -import {Searchbar} from 'react-native-paper'; -import {PADDING} from './constants'; -import MassiveFab from './MassiveFab'; +import {StyleSheet, View} from 'react-native' +import {Searchbar} from 'react-native-paper' +import {PADDING} from './constants' +import MassiveFab from './MassiveFab' export default function Page({ onAdd, @@ -9,10 +9,10 @@ export default function Page({ term, search, }: { - children: JSX.Element | JSX.Element[]; - onAdd?: () => void; - term: string; - search: (value: string) => void; + children: JSX.Element | JSX.Element[] + onAdd?: () => void + term: string + search: (value: string) => void }) { return ( @@ -26,7 +26,7 @@ export default function Page({ {children} {onAdd && } - ); + ) } const styles = StyleSheet.create({ @@ -34,4 +34,4 @@ const styles = StyleSheet.create({ flexGrow: 1, padding: PADDING, }, -}); +}) diff --git a/PlanItem.tsx b/PlanItem.tsx index 6496e52..a95b68d 100644 --- a/PlanItem.tsx +++ b/PlanItem.tsx @@ -2,60 +2,60 @@ import { NavigationProp, useFocusEffect, useNavigation, -} from '@react-navigation/native'; -import {useCallback, useMemo, useState} from 'react'; -import {GestureResponderEvent, Text} from 'react-native'; -import {Divider, List, Menu} from 'react-native-paper'; -import {getBestSet} from './best.service'; -import {planRepo} from './db'; -import {Plan} from './plan'; -import {PlanPageParams} from './plan-page-params'; -import {DAYS} from './time'; +} from '@react-navigation/native' +import {useCallback, useMemo, useState} from 'react' +import {GestureResponderEvent, Text} from 'react-native' +import {Divider, List, Menu} from 'react-native-paper' +import {getBestSet} from './best.service' +import {planRepo} from './db' +import {Plan} from './plan' +import {PlanPageParams} from './plan-page-params' +import {DAYS} from './time' export default function PlanItem({ item, onRemove, }: { - item: Plan; - onRemove: () => void; + item: Plan + onRemove: () => void }) { - const [show, setShow] = useState(false); - const [anchor, setAnchor] = useState({x: 0, y: 0}); - const [today, setToday] = useState(); - const days = useMemo(() => item.days.split(','), [item.days]); - const navigation = useNavigation>(); + const [show, setShow] = useState(false) + const [anchor, setAnchor] = useState({x: 0, y: 0}) + const [today, setToday] = useState() + const days = useMemo(() => item.days.split(','), [item.days]) + const navigation = useNavigation>() useFocusEffect( useCallback(() => { - const newToday = DAYS[new Date().getDay()]; - setToday(newToday); + const newToday = DAYS[new Date().getDay()] + setToday(newToday) }, []), - ); + ) const remove = useCallback(async () => { - if (typeof item.id === 'number') await planRepo.delete(item.id); - setShow(false); - onRemove(); - }, [setShow, item.id, onRemove]); + if (typeof item.id === 'number') await planRepo.delete(item.id) + setShow(false) + onRemove() + }, [setShow, item.id, onRemove]) const start = useCallback(async () => { - console.log(`${PlanItem.name}.start:`, {item}); - setShow(false); - navigation.navigate('StartPlan', {plan: item}); - }, [item, navigation]); + console.log(`${PlanItem.name}.start:`, {item}) + setShow(false) + navigation.navigate('StartPlan', {plan: item}) + }, [item, navigation]) const longPress = useCallback( (e: GestureResponderEvent) => { - setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY}); - setShow(true); + setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY}) + setShow(true) }, [setAnchor, setShow], - ); + ) const edit = useCallback(() => { - setShow(false); - navigation.navigate('EditPlan', {plan: item}); - }, [navigation, item]); + setShow(false) + navigation.navigate('EditPlan', {plan: item}) + }, [navigation, item]) const title = useMemo( () => @@ -72,12 +72,12 @@ export default function PlanItem({ )), [days, today], - ); + ) const description = useMemo( () => item.workouts.replace(/,/g, ', '), [item.workouts], - ); + ) return ( <> @@ -95,5 +95,5 @@ export default function PlanItem({ )} /> - ); + ) } diff --git a/PlanList.tsx b/PlanList.tsx index 0153169..b20fd38 100644 --- a/PlanList.tsx +++ b/PlanList.tsx @@ -2,54 +2,54 @@ import { NavigationProp, useFocusEffect, useNavigation, -} from '@react-navigation/native'; -import {useCallback, useState} from 'react'; -import {FlatList} from 'react-native'; -import {List} from 'react-native-paper'; -import {Like} from 'typeorm'; -import {planRepo} from './db'; -import DrawerHeader from './DrawerHeader'; -import Page from './Page'; -import {Plan} from './plan'; -import {PlanPageParams} from './plan-page-params'; -import PlanItem from './PlanItem'; +} from '@react-navigation/native' +import {useCallback, useState} from 'react' +import {FlatList} from 'react-native' +import {List} from 'react-native-paper' +import {Like} from 'typeorm' +import {planRepo} from './db' +import DrawerHeader from './DrawerHeader' +import Page from './Page' +import {Plan} from './plan' +import {PlanPageParams} from './plan-page-params' +import PlanItem from './PlanItem' export default function PlanList() { - const [term, setTerm] = useState(''); - const [plans, setPlans] = useState(); - const navigation = useNavigation>(); + const [term, setTerm] = useState('') + const [plans, setPlans] = useState() + const navigation = useNavigation>() const refresh = useCallback(async (value: string) => { planRepo .find({ where: [{days: Like(`%${value}%`)}, {workouts: Like(`%${value}%`)}], }) - .then(setPlans); - }, []); + .then(setPlans) + }, []) useFocusEffect( useCallback(() => { - refresh(term); + refresh(term) }, [refresh, term]), - ); + ) const search = useCallback( (value: string) => { - setTerm(value); - refresh(value); + setTerm(value) + refresh(value) }, [refresh], - ); + ) const renderItem = useCallback( ({item}: {item: Plan}) => ( refresh(term)} /> ), [refresh, term], - ); + ) const onAdd = () => - navigation.navigate('EditPlan', {plan: {days: '', workouts: ''}}); + navigation.navigate('EditPlan', {plan: {days: '', workouts: ''}}) return ( <> @@ -70,5 +70,5 @@ export default function PlanList() { )} - ); + ) } diff --git a/PlanPage.tsx b/PlanPage.tsx index 980ca2a..0507684 100644 --- a/PlanPage.tsx +++ b/PlanPage.tsx @@ -1,10 +1,10 @@ -import {createStackNavigator} from '@react-navigation/stack'; -import EditPlan from './EditPlan'; -import {PlanPageParams} from './plan-page-params'; -import PlanList from './PlanList'; -import StartPlan from './StartPlan'; +import {createStackNavigator} from '@react-navigation/stack' +import EditPlan from './EditPlan' +import {PlanPageParams} from './plan-page-params' +import PlanList from './PlanList' +import StartPlan from './StartPlan' -const Stack = createStackNavigator(); +const Stack = createStackNavigator() export default function PlanPage() { return ( @@ -14,5 +14,5 @@ export default function PlanPage() { - ); + ) } diff --git a/Routes.tsx b/Routes.tsx index 028cd97..de8bb32 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -1,18 +1,18 @@ -import {createDrawerNavigator} from '@react-navigation/drawer'; -import {IconButton} from 'react-native-paper'; -import BestPage from './BestPage'; -import {DrawerParamList} from './drawer-param-list'; -import HomePage from './HomePage'; -import PlanPage from './PlanPage'; -import Route from './route'; -import SettingsPage from './SettingsPage'; -import useDark from './use-dark'; -import WorkoutsPage from './WorkoutsPage'; +import {createDrawerNavigator} from '@react-navigation/drawer' +import {IconButton} from 'react-native-paper' +import BestPage from './BestPage' +import {DrawerParamList} from './drawer-param-list' +import HomePage from './HomePage' +import PlanPage from './PlanPage' +import Route from './route' +import SettingsPage from './SettingsPage' +import useDark from './use-dark' +import WorkoutsPage from './WorkoutsPage' -const Drawer = createDrawerNavigator(); +const Drawer = createDrawerNavigator() export default function Routes() { - const dark = useDark(); + const dark = useDark() const routes: Route[] = [ {name: 'Home', component: HomePage, icon: 'home'}, @@ -20,7 +20,7 @@ export default function Routes() { {name: 'Best', component: BestPage, icon: 'insights'}, {name: 'Workouts', component: WorkoutsPage, icon: 'fitness-center'}, {name: 'Settings', component: SettingsPage, icon: 'settings'}, - ]; + ] return ( ))} - ); + ) } diff --git a/SetForm.tsx b/SetForm.tsx index 1fa7f10..f07a4a6 100644 --- a/SetForm.tsx +++ b/SetForm.tsx @@ -1,47 +1,47 @@ -import {useCallback, useRef, useState} from 'react'; -import {TextInput, View} from 'react-native'; -import DocumentPicker from 'react-native-document-picker'; -import {Button, Card, TouchableRipple} from 'react-native-paper'; -import ConfirmDialog from './ConfirmDialog'; -import {MARGIN} from './constants'; -import {setRepo} from './db'; -import GymSet from './gym-set'; -import MassiveInput from './MassiveInput'; -import {useSnackbar} from './MassiveSnack'; -import {useSettings} from './use-settings'; +import {useCallback, useRef, useState} from 'react' +import {TextInput, View} from 'react-native' +import DocumentPicker from 'react-native-document-picker' +import {Button, Card, TouchableRipple} from 'react-native-paper' +import ConfirmDialog from './ConfirmDialog' +import {MARGIN} from './constants' +import {setRepo} from './db' +import GymSet from './gym-set' +import MassiveInput from './MassiveInput' +import {useSnackbar} from './MassiveSnack' +import {useSettings} from './use-settings' export default function SetForm({ save, set, }: { - set: GymSet; - save: (set: GymSet) => void; + set: GymSet + save: (set: GymSet) => void }) { - 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 [showRemove, setShowRemove] = useState(false); + 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 [showRemove, setShowRemove] = useState(false) const [selection, setSelection] = useState({ start: 0, end: set.reps.toString().length, - }); - const [removeImage, setRemoveImage] = useState(false); - const {toast} = useSnackbar(); - const {settings} = useSettings(); - const weightRef = useRef(null); - const repsRef = useRef(null); - const unitRef = useRef(null); + }) + const [removeImage, setRemoveImage] = useState(false) + const {toast} = useSnackbar() + const {settings} = useSettings() + const weightRef = useRef(null) + const repsRef = useRef(null) + const unitRef = useRef(null) const handleSubmit = async () => { - console.log(`${SetForm.name}.handleSubmit:`, {set, uri: newImage, name}); - if (!name) return; - let image = newImage; + console.log(`${SetForm.name}.handleSubmit:`, {set, uri: newImage, name}) + if (!name) return + let image = newImage if (!newImage && !removeImage) - image = await setRepo.findOne({where: {name}}).then(s => s?.image); + image = await setRepo.findOne({where: {name}}).then(s => s?.image) - console.log(`${SetForm.name}.handleSubmit:`, {image}); + console.log(`${SetForm.name}.handleSubmit:`, {image}) save({ name, reps: Number(reps), @@ -53,34 +53,34 @@ export default function SetForm({ seconds: Number(set.seconds ?? 30), sets: set.sets ?? 3, hidden: false, - }); - }; + }) + } const handleName = (value: string) => { - setName(value.replace(/,|'/g, '')); + setName(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000); - }; + toast('Commas and single quotes would break CSV exports', 6000) + } const handleUnit = (value: string) => { - setUnit(value.replace(/,|'/g, '')); + setUnit(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000); - }; + toast('Commas and single quotes would break CSV exports', 6000) + } const changeImage = useCallback(async () => { const {fileCopyUri} = await DocumentPicker.pickSingle({ type: 'image/*', copyTo: 'documentDirectory', - }); - if (fileCopyUri) setNewImage(fileCopyUri); - }, []); + }) + if (fileCopyUri) setNewImage(fileCopyUri) + }, []) const handleRemove = useCallback(async () => { - setNewImage(''); - setRemoveImage(true); - setShowRemove(false); - }, []); + setNewImage('') + setRemoveImage(true) + setShowRemove(false) + }, []) return ( <> @@ -156,5 +156,5 @@ export default function SetForm({ Are you sure you want to remove the image? - ); + ) } diff --git a/SetItem.tsx b/SetItem.tsx index da84ed3..91319cc 100644 --- a/SetItem.tsx +++ b/SetItem.tsx @@ -1,47 +1,47 @@ -import {NavigationProp, useNavigation} from '@react-navigation/native'; -import {useCallback, useState} from 'react'; -import {GestureResponderEvent, Image} from 'react-native'; -import {Divider, List, Menu, Text} from 'react-native-paper'; -import {setRepo} from './db'; -import GymSet from './gym-set'; -import {HomePageParams} from './home-page-params'; -import {format} from './time'; -import useDark from './use-dark'; -import {useSettings} from './use-settings'; +import {NavigationProp, useNavigation} from '@react-navigation/native' +import {useCallback, useState} from 'react' +import {GestureResponderEvent, Image} from 'react-native' +import {Divider, List, Menu, Text} from 'react-native-paper' +import {setRepo} from './db' +import GymSet from './gym-set' +import {HomePageParams} from './home-page-params' +import {format} from './time' +import useDark from './use-dark' +import {useSettings} from './use-settings' export default function SetItem({ item, onRemove, }: { - item: GymSet; - onRemove: () => void; + item: GymSet + onRemove: () => void }) { - const [showMenu, setShowMenu] = useState(false); - const [anchor, setAnchor] = useState({x: 0, y: 0}); - const {settings} = useSettings(); - const dark = useDark(); - const navigation = useNavigation>(); + const [showMenu, setShowMenu] = useState(false) + const [anchor, setAnchor] = useState({x: 0, y: 0}) + const {settings} = useSettings() + const dark = useDark() + const navigation = useNavigation>() const remove = useCallback(async () => { - if (typeof item.id === 'number') await setRepo.delete(item.id); - setShowMenu(false); - onRemove(); - }, [setShowMenu, onRemove, item.id]); + if (typeof item.id === 'number') await setRepo.delete(item.id) + setShowMenu(false) + onRemove() + }, [setShowMenu, onRemove, item.id]) const copy = useCallback(() => { - const set: GymSet = {...item}; - delete set.id; - setShowMenu(false); - navigation.navigate('EditSet', {set}); - }, [navigation, item]); + const set: GymSet = {...item} + delete set.id + setShowMenu(false) + navigation.navigate('EditSet', {set}) + }, [navigation, item]) const longPress = useCallback( (e: GestureResponderEvent) => { - setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY}); - setShowMenu(true); + setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY}) + setShowMenu(true) }, [setShowMenu, setAnchor], - ); + ) return ( <> @@ -79,5 +79,5 @@ export default function SetItem({ )} /> - ); + ) } diff --git a/SetList.tsx b/SetList.tsx index 4460a4e..e612fa4 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -2,29 +2,29 @@ import { NavigationProp, useFocusEffect, useNavigation, -} from '@react-navigation/native'; -import React, {useCallback, useEffect, useState} from 'react'; -import {FlatList} from 'react-native'; -import {List} from 'react-native-paper'; -import {Like} from 'typeorm'; -import {getNow, setRepo} from './db'; -import DrawerHeader from './DrawerHeader'; -import GymSet from './gym-set'; -import {HomePageParams} from './home-page-params'; -import Page from './Page'; -import SetItem from './SetItem'; +} from '@react-navigation/native' +import React, {useCallback, useEffect, useState} from 'react' +import {FlatList} from 'react-native' +import {List} from 'react-native-paper' +import {Like} from 'typeorm' +import {getNow, setRepo} from './db' +import DrawerHeader from './DrawerHeader' +import GymSet from './gym-set' +import {HomePageParams} from './home-page-params' +import Page from './Page' +import SetItem from './SetItem' -const limit = 15; +const limit = 15 export default function SetList() { - const [sets, setSets] = useState([]); - const [set, setSet] = useState(); - const [offset, setOffset] = useState(0); - const [term, setTerm] = useState(''); - const [end, setEnd] = useState(false); - const navigation = useNavigation>(); + const [sets, setSets] = useState([]) + const [set, setSet] = useState() + const [offset, setOffset] = useState(0) + const [term, setTerm] = useState('') + const [end, setEnd] = useState(false) + const navigation = useNavigation>() - useEffect(() => console.log({sets}), [sets]); + useEffect(() => console.log({sets}), [sets]) const refresh = useCallback(async (value: string) => { const newSets = await setRepo.find({ @@ -32,47 +32,47 @@ export default function SetList() { take: limit, skip: 0, order: {created: 'DESC'}, - }); - setSet(newSets[0]); - if (newSets.length === 0) return setSets([]); - setSets(newSets); - setOffset(0); - setEnd(false); - }, []); + }) + setSet(newSets[0]) + if (newSets.length === 0) return setSets([]) + setSets(newSets) + setOffset(0) + setEnd(false) + }, []) useFocusEffect( useCallback(() => { - refresh(term); + refresh(term) }, [refresh, term]), - ); + ) const renderItem = useCallback( ({item}: {item: GymSet}) => ( refresh(term)} /> ), [refresh, term], - ); + ) const next = useCallback(async () => { - if (end) return; - const newOffset = offset + limit; - console.log(`${SetList.name}.next:`, {offset, newOffset, term}); + if (end) 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; - setSets([...sets, ...newSets]); - if (newSets.length < limit) return setEnd(true); - setOffset(newOffset); - }, [term, end, offset, sets]); + }) + if (newSets.length === 0) return setEnd(true) + if (!sets) return + setSets([...sets, ...newSets]) + if (newSets.length < limit) return setEnd(true) + setOffset(newOffset) + }, [term, end, offset, sets]) const onAdd = useCallback(async () => { - console.log(`${SetList.name}.onAdd`, {set}); - const [{now}] = await getNow(); + console.log(`${SetList.name}.onAdd`, {set}) + const [{now}] = await getNow() navigation.navigate('EditSet', { set: set || { hidden: false, @@ -84,16 +84,16 @@ export default function SetList() { weight: 0, created: now, }, - }); - }, [navigation, set]); + }) + }, [navigation, set]) const search = useCallback( (value: string) => { - setTerm(value); - refresh(value); + setTerm(value) + refresh(value) }, [refresh], - ); + ) return ( <> @@ -114,5 +114,5 @@ export default function SetList() { )} - ); + ) } diff --git a/SettingsPage.tsx b/SettingsPage.tsx index def0f63..0c25c2b 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -1,27 +1,27 @@ -import {Picker} from '@react-native-picker/picker'; -import {useFocusEffect} from '@react-navigation/native'; -import {useCallback, useEffect, useState} from 'react'; -import {NativeModules, ScrollView} from 'react-native'; -import DocumentPicker from 'react-native-document-picker'; -import {Button} from 'react-native-paper'; -import {useColor} from './color'; -import {darkColors, lightColors} from './colors'; -import ConfirmDialog from './ConfirmDialog'; -import {MARGIN} from './constants'; -import {settingsRepo} from './db'; -import DrawerHeader from './DrawerHeader'; -import Input from './input'; -import {useSnackbar} from './MassiveSnack'; -import Page from './Page'; -import Settings from './settings'; -import Switch from './Switch'; -import {useSettings} from './use-settings'; +import {Picker} from '@react-native-picker/picker' +import {useFocusEffect} from '@react-navigation/native' +import {useCallback, useEffect, useState} from 'react' +import {NativeModules, ScrollView} from 'react-native' +import DocumentPicker from 'react-native-document-picker' +import {Button} from 'react-native-paper' +import {useColor} from './color' +import {darkColors, lightColors} from './colors' +import ConfirmDialog from './ConfirmDialog' +import {MARGIN} from './constants' +import {settingsRepo} from './db' +import DrawerHeader from './DrawerHeader' +import Input from './input' +import {useSnackbar} from './MassiveSnack' +import Page from './Page' +import Settings from './settings' +import Switch from './Switch' +import {useSettings} from './use-settings' export default function SettingsPage() { - const [battery, setBattery] = useState(false); - const [ignoring, setIgnoring] = useState(false); - const [term, setTerm] = useState(''); - const {settings, setSettings} = useSettings(); + const [battery, setBattery] = useState(false) + const [ignoring, setIgnoring] = useState(false) + const [term, setTerm] = useState('') + const {settings, setSettings} = useSettings() const { vibrate, sound, @@ -34,120 +34,120 @@ export default function SettingsPage() { theme, alarm, noSound, - } = settings; - const {color, setColor} = useColor(); - const {toast} = useSnackbar(); + } = settings + const {color, setColor} = useColor() + const {toast} = useSnackbar() useEffect(() => { - console.log(`${SettingsPage.name}.useEffect:`, {settings}); - }, [settings]); + console.log(`${SettingsPage.name}.useEffect:`, {settings}) + }, [settings]) useFocusEffect( useCallback(() => { - NativeModules.AlarmModule.ignoringBattery(setIgnoring); + NativeModules.AlarmModule.ignoringBattery(setIgnoring) }, []), - ); + ) const update = useCallback( (value: boolean, field: keyof Settings) => { - settingsRepo.update({}, {[field]: value}); - setSettings({...settings, [field]: value}); + settingsRepo.update({}, {[field]: value}) + setSettings({...settings, [field]: value}) }, [settings, setSettings], - ); + ) const changeAlarmEnabled = useCallback( (enabled: boolean) => { - if (enabled) toast('Timers will now run after each set.', 4000); - else toast('Stopped timers running after each set.', 4000); - if (enabled && !ignoring) setBattery(true); - update(enabled, 'alarm'); + if (enabled) toast('Timers will now run after each set.', 4000) + else toast('Stopped timers running after each set.', 4000) + if (enabled && !ignoring) setBattery(true) + update(enabled, 'alarm') }, [setBattery, ignoring, toast, update], - ); + ) const changeVibrate = useCallback( (enabled: boolean) => { - if (enabled) toast('When a timer completes, vibrate your phone.', 4000); - else toast('Stop vibrating at the end of timers.', 4000); - update(enabled, 'vibrate'); + if (enabled) toast('When a timer completes, vibrate your phone.', 4000) + else toast('Stop vibrating at the end of timers.', 4000) + update(enabled, 'vibrate') }, [toast, update], - ); + ) const changeSound = useCallback(async () => { const {fileCopyUri} = await DocumentPicker.pickSingle({ type: 'audio/*', copyTo: 'documentDirectory', - }); - if (!fileCopyUri) return; - settingsRepo.update({}, {sound: fileCopyUri}); - setSettings({...settings, sound: fileCopyUri}); - toast('This song will now play after rest timers complete.', 4000); - }, [toast, setSettings, settings]); + }) + if (!fileCopyUri) return + settingsRepo.update({}, {sound: fileCopyUri}) + setSettings({...settings, sound: fileCopyUri}) + toast('This song will now play after rest timers complete.', 4000) + }, [toast, setSettings, settings]) const changeNotify = useCallback( (enabled: boolean) => { - update(enabled, 'notify'); - if (enabled) toast('Show when a set is a new record.', 4000); - else toast('Stopped showing notifications for new records.', 4000); + update(enabled, 'notify') + if (enabled) toast('Show when a set is a new record.', 4000) + else toast('Stopped showing notifications for new records.', 4000) }, [toast, update], - ); + ) const changeImages = useCallback( (enabled: boolean) => { - update(enabled, 'images'); - if (enabled) toast('Show images for sets.', 4000); - else toast('Stopped showing images for sets.', 4000); + update(enabled, 'images') + if (enabled) toast('Show images for sets.', 4000) + else toast('Stopped showing images for sets.', 4000) }, [toast, update], - ); + ) const changeUnit = useCallback( (enabled: boolean) => { - update(enabled, 'showUnit'); - if (enabled) toast('Show option to select unit for sets.', 4000); - else toast('Hid unit option for sets.', 4000); + update(enabled, 'showUnit') + if (enabled) toast('Show option to select unit for sets.', 4000) + else toast('Hid unit option for sets.', 4000) }, [toast, update], - ); + ) const changeSteps = useCallback( (enabled: boolean) => { - update(enabled, 'steps'); - if (enabled) toast('Show steps for a workout.', 4000); - else toast('Stopped showing steps for workouts.', 4000); + update(enabled, 'steps') + if (enabled) toast('Show steps for a workout.', 4000) + else toast('Stopped showing steps for workouts.', 4000) }, [toast, update], - ); + ) const changeShowDate = useCallback( (enabled: boolean) => { - update(enabled, 'showDate'); - if (enabled) toast('Show date for sets by default.', 4000); - else toast('Stopped showing date for sets by default.', 4000); + update(enabled, 'showDate') + if (enabled) toast('Show date for sets by default.', 4000) + else toast('Stopped showing date for sets by default.', 4000) }, [toast, update], - ); + ) const changeShowSets = useCallback( (enabled: boolean) => { - update(enabled, 'showSets'); - if (enabled) toast('Show maximum sets for workouts.', 4000); - else toast('Stopped showing maximum sets for workouts.', 4000); + update(enabled, 'showSets') + if (enabled) toast('Show maximum sets for workouts.', 4000) + else toast('Stopped showing maximum sets for workouts.', 4000) }, [toast, update], - ); + ) const changeNoSound = useCallback( (enabled: boolean) => { - update(enabled, 'noSound'); - if (enabled) toast('Disable sound on rest timer alarms.', 4000); - else toast('Enabled sound for rest timer alarms.', 4000); + update(enabled, 'noSound') + if (enabled) toast('Disable sound on rest timer alarms.', 4000) + else toast('Enabled sound for rest timer alarms.', 4000) }, [toast, update], - ); + ) const switches: Input[] = [ {name: 'Rest timers', value: alarm, onChange: changeAlarmEnabled}, @@ -159,23 +159,23 @@ export default function SettingsPage() { {name: 'Show steps', value: steps, onChange: changeSteps}, {name: 'Show date', value: showDate, onChange: changeShowDate}, {name: 'Show sets', value: showSets, onChange: changeShowSets}, - ]; + ] const changeTheme = useCallback( (value: string) => { - settingsRepo.update({}, {theme: value}); - setSettings({...settings, theme: value}); + settingsRepo.update({}, {theme: value}) + setSettings({...settings, theme: value}) }, [settings, setSettings], - ); + ) const changeDate = useCallback( (value: string) => { - settingsRepo.update({}, {date: value}); - setSettings({...settings, date: value as any}); + settingsRepo.update({}, {date: value}) + setSettings({...settings, date: value as any}) }, [settings, setSettings], - ); + ) return ( <> @@ -256,12 +256,12 @@ export default function SettingsPage() { show={battery} setShow={setBattery} onOk={() => { - NativeModules.AlarmModule.ignoreBattery(); - setBattery(false); + NativeModules.AlarmModule.ignoreBattery() + setBattery(false) }}> Disable battery optimizations for Massive to use rest timers. - ); + ) } diff --git a/StackHeader.tsx b/StackHeader.tsx index 581a682..3b0562a 100644 --- a/StackHeader.tsx +++ b/StackHeader.tsx @@ -1,13 +1,13 @@ -import {useNavigation} from '@react-navigation/native'; -import Share from 'react-native-share'; -import {FileSystem} from 'react-native-file-access'; -import {Appbar, IconButton} from 'react-native-paper'; -import {captureScreen} from 'react-native-view-shot'; -import useDark from './use-dark'; +import {useNavigation} from '@react-navigation/native' +import Share from 'react-native-share' +import {FileSystem} from 'react-native-file-access' +import {Appbar, IconButton} from 'react-native-paper' +import {captureScreen} from 'react-native-view-shot' +import useDark from './use-dark' export default function StackHeader({title}: {title: string}) { - const navigation = useNavigation(); - const dark = useDark(); + const navigation = useNavigation() + const dark = useDark() return ( @@ -21,16 +21,16 @@ export default function StackHeader({title}: {title: string}) { color={dark ? 'white' : 'white'} onPress={() => captureScreen().then(async uri => { - const base64 = await FileSystem.readFile(uri, 'base64'); - const url = `data:image/jpeg;base64,${base64}`; + const base64 = await FileSystem.readFile(uri, 'base64') + const url = `data:image/jpeg;base64,${base64}` Share.open({ type: 'image/jpeg', url, - }); + }) }) } icon="share" /> - ); + ) } diff --git a/StartPlan.tsx b/StartPlan.tsx index 22091a2..53fac07 100644 --- a/StartPlan.tsx +++ b/StartPlan.tsx @@ -1,50 +1,50 @@ -import {RouteProp, useFocusEffect, useRoute} from '@react-navigation/native'; -import {useCallback, useMemo, useRef, useState} from 'react'; -import {NativeModules, TextInput, View} from 'react-native'; -import {FlatList} from 'react-native-gesture-handler'; -import {Button} from 'react-native-paper'; -import {getBestSet} from './best.service'; -import {PADDING} from './constants'; -import CountMany from './count-many'; -import {AppDataSource} from './data-source'; -import {getNow, setRepo} from './db'; -import GymSet from './gym-set'; -import MassiveInput from './MassiveInput'; -import {useSnackbar} from './MassiveSnack'; -import {PlanPageParams} from './plan-page-params'; -import SetForm from './SetForm'; -import StackHeader from './StackHeader'; -import StartPlanItem from './StartPlanItem'; -import {useSettings} from './use-settings'; +import {RouteProp, useFocusEffect, useRoute} from '@react-navigation/native' +import {useCallback, useMemo, useRef, useState} from 'react' +import {NativeModules, TextInput, View} from 'react-native' +import {FlatList} from 'react-native-gesture-handler' +import {Button} from 'react-native-paper' +import {getBestSet} from './best.service' +import {PADDING} from './constants' +import CountMany from './count-many' +import {AppDataSource} from './data-source' +import {getNow, setRepo} from './db' +import GymSet from './gym-set' +import MassiveInput from './MassiveInput' +import {useSnackbar} from './MassiveSnack' +import {PlanPageParams} from './plan-page-params' +import SetForm from './SetForm' +import StackHeader from './StackHeader' +import StartPlanItem from './StartPlanItem' +import {useSettings} from './use-settings' export default function StartPlan() { - const {params} = useRoute>(); - const [name, setName] = useState(''); - const [reps, setReps] = useState(''); - const [weight, setWeight] = useState(''); - const [unit, setUnit] = useState('kg'); - const {toast} = useSnackbar(); - const [minutes, setMinutes] = useState(3); - const [seconds, setSeconds] = useState(30); - const [best, setBest] = useState(); - const [selected, setSelected] = useState(0); - const {settings} = useSettings(); - const [counts, setCounts] = useState(); - const weightRef = useRef(null); - const repsRef = useRef(null); - const unitRef = useRef(null); - const workouts = useMemo(() => params.plan.workouts.split(','), [params]); + const {params} = useRoute>() + const [name, setName] = useState('') + const [reps, setReps] = useState('') + const [weight, setWeight] = useState('') + const [unit, setUnit] = useState('kg') + const {toast} = useSnackbar() + const [minutes, setMinutes] = useState(3) + const [seconds, setSeconds] = useState(30) + const [best, setBest] = useState() + const [selected, setSelected] = useState(0) + const {settings} = useSettings() + const [counts, setCounts] = useState() + const weightRef = useRef(null) + const repsRef = useRef(null) + const unitRef = useRef(null) + const workouts = useMemo(() => params.plan.workouts.split(','), [params]) const [selection, setSelection] = useState({ start: 0, end: 0, - }); + }) const refresh = useCallback(() => { const questions = workouts .map((workout, index) => `('${workout}',${index})`) - .join(','); - console.log({questions, workouts}); + .join(',') + console.log({questions, workouts}) const select = ` SELECT workouts.name, COUNT(sets.id) as total FROM (select 0 as name, 0 as sequence union values ${questions}) as workouts @@ -55,43 +55,43 @@ export default function StartPlan() { ORDER BY workouts.sequence LIMIT -1 OFFSET 1 - `; + ` return AppDataSource.manager.query(select).then(newCounts => { - setCounts(newCounts); - console.log(`${StartPlan.name}.focus:`, {newCounts}); - return newCounts; - }); - }, [workouts]); + setCounts(newCounts) + console.log(`${StartPlan.name}.focus:`, {newCounts}) + return newCounts + }) + }, [workouts]) const select = useCallback( async (index: number, newCounts?: CountMany[]) => { - setSelected(index); - console.log(`${StartPlan.name}.next:`, {name, index}); - if (!counts && !newCounts) return; - const workout = counts ? counts[index] : newCounts[index]; - console.log(`${StartPlan.name}.next:`, {workout}); - const newBest = await getBestSet(workout.name); - console.log(`${StartPlan.name}.next:`, {newBest}); - setMinutes(newBest.minutes); - setSeconds(newBest.seconds); - setName(newBest.name); - setReps(newBest.reps.toString()); - setWeight(newBest.weight.toString()); - setUnit(newBest.unit); - setBest(newBest); + setSelected(index) + console.log(`${StartPlan.name}.next:`, {name, index}) + if (!counts && !newCounts) return + const workout = counts ? counts[index] : newCounts[index] + console.log(`${StartPlan.name}.next:`, {workout}) + const newBest = await getBestSet(workout.name) + console.log(`${StartPlan.name}.next:`, {newBest}) + setMinutes(newBest.minutes) + setSeconds(newBest.seconds) + setName(newBest.name) + setReps(newBest.reps.toString()) + setWeight(newBest.weight.toString()) + setUnit(newBest.unit) + setBest(newBest) }, [name, counts], - ); + ) useFocusEffect( useCallback(() => { - refresh().then(newCounts => select(0, newCounts)); + refresh().then(newCounts => select(0, newCounts)) }, [refresh]), - ); + ) const handleSubmit = async () => { - console.log(`${SetForm.name}.handleSubmit:`, {reps, weight, unit, best}); - const [{now}] = await getNow(); + console.log(`${SetForm.name}.handleSubmit:`, {reps, weight, unit, best}) + const [{now}] = await getNow() await setRepo.save({ name, weight: +weight, @@ -102,30 +102,30 @@ export default function StartPlan() { seconds, sets: best.sets, hidden: false, - }); - await refresh(); + }) + await refresh() if ( settings.notify && (+weight > best.weight || (+reps > best.reps && +weight === best.weight)) ) - toast("Great work King! That's a new record.", 5000); - else if (settings.alarm) toast('Resting...', 3000); - else toast('Added set', 3000); - if (!settings.alarm) return; - const milliseconds = Number(minutes) * 60 * 1000 + Number(seconds) * 1000; - const {vibrate, sound, noSound} = settings; - const args = [milliseconds, !!vibrate, sound, !!noSound]; - NativeModules.AlarmModule.timer(...args); - }; + toast("Great work King! That's a new record.", 5000) + else if (settings.alarm) toast('Resting...', 3000) + else toast('Added set', 3000) + if (!settings.alarm) return + const milliseconds = Number(minutes) * 60 * 1000 + Number(seconds) * 1000 + const {vibrate, sound, noSound} = settings + const args = [milliseconds, !!vibrate, sound, !!noSound] + NativeModules.AlarmModule.timer(...args) + } const handleUnit = useCallback( (value: string) => { - setUnit(value.replace(/,|'/g, '')); + setUnit(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000); + toast('Commas and single quotes would break CSV exports', 6000) }, [toast], - ); + ) return ( <> @@ -179,5 +179,5 @@ export default function StartPlan() { - ); + ) } diff --git a/StartPlanItem.tsx b/StartPlanItem.tsx index d618b84..cce51ce 100644 --- a/StartPlanItem.tsx +++ b/StartPlanItem.tsx @@ -1,40 +1,40 @@ -import React, {useCallback, useState} from 'react'; -import {GestureResponderEvent, ListRenderItemInfo, View} from 'react-native'; -import {List, Menu, RadioButton} from 'react-native-paper'; -import {useColor} from './color'; -import CountMany from './count-many'; -import {setRepo} from './db'; +import React, {useCallback, useState} from 'react' +import {GestureResponderEvent, ListRenderItemInfo, View} from 'react-native' +import {List, Menu, RadioButton} from 'react-native-paper' +import {useColor} from './color' +import CountMany from './count-many' +import {setRepo} from './db' interface Props extends ListRenderItemInfo { - onSelect: (index: number) => void; - selected: number; - onUndo: () => void; + onSelect: (index: number) => void + selected: number + onUndo: () => void } export default function StartPlanItem(props: Props) { - const {index, item, onSelect, selected, onUndo} = props; - const {color} = useColor(); - const [anchor, setAnchor] = useState({x: 0, y: 0}); - const [showMenu, setShowMenu] = useState(false); + const {index, item, onSelect, selected, onUndo} = props + const {color} = useColor() + const [anchor, setAnchor] = useState({x: 0, y: 0}) + const [showMenu, setShowMenu] = useState(false) const undo = useCallback(async () => { const first = await setRepo.findOne({ where: {name: item.name, hidden: 0 as any}, order: {created: 'desc'}, - }); - console.log({first}); - await setRepo.delete(first.id); - setShowMenu(false); - onUndo(); - }, [setShowMenu, onUndo, item.name]); + }) + console.log({first}) + await setRepo.delete(first.id) + setShowMenu(false) + onUndo() + }, [setShowMenu, onUndo, item.name]) const longPress = useCallback( (e: GestureResponderEvent) => { - setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY}); - setShowMenu(true); + setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY}) + setShowMenu(true) }, [setShowMenu, setAnchor], - ); + ) return ( )} /> - ); + ) } diff --git a/Switch.tsx b/Switch.tsx index 4d5454d..11f6f5d 100644 --- a/Switch.tsx +++ b/Switch.tsx @@ -1,11 +1,11 @@ -import {useMemo} from 'react'; -import {Pressable} from 'react-native'; -import {Switch as PaperSwitch, Text} from 'react-native-paper'; -import {CombinedDarkTheme, CombinedDefaultTheme} from './App'; -import {useColor} from './color'; -import {colorShade} from './colors'; -import {MARGIN} from './constants'; -import useDark from './use-dark'; +import {useMemo} from 'react' +import {Pressable} from 'react-native' +import {Switch as PaperSwitch, Text} from 'react-native-paper' +import {CombinedDarkTheme, CombinedDefaultTheme} from './App' +import {useColor} from './color' +import {colorShade} from './colors' +import {MARGIN} from './constants' +import useDark from './use-dark' export default function Switch({ value, @@ -13,25 +13,25 @@ export default function Switch({ onPress, children, }: { - value?: boolean; - onValueChange: (value: boolean) => void; - onPress: () => void; - children: string; + value?: boolean + onValueChange: (value: boolean) => void + onPress: () => void + children: string }) { - const {color} = useColor(); - const dark = useDark(); + const {color} = useColor() + const dark = useDark() const track = useMemo(() => { if (dark) return { false: CombinedDarkTheme.colors.placeholder, true: colorShade(color, -40), - }; + } return { false: CombinedDefaultTheme.colors.placeholder, true: colorShade(color, -40), - }; - }, [dark, color]); + } + }, [dark, color]) return ( {children} - ); + ) } diff --git a/ViewBest.tsx b/ViewBest.tsx index 040fcf4..6dbdb4b 100644 --- a/ViewBest.tsx +++ b/ViewBest.tsx @@ -1,35 +1,35 @@ -import {Picker} from '@react-native-picker/picker'; -import {RouteProp, useRoute} from '@react-navigation/native'; -import {useEffect, useState} from 'react'; -import {View} from 'react-native'; -import {BestPageParams} from './BestPage'; -import Chart from './Chart'; -import {PADDING} from './constants'; -import {setRepo} from './db'; -import GymSet from './gym-set'; -import {Metrics} from './metrics'; -import {Periods} from './periods'; -import StackHeader from './StackHeader'; -import {formatMonth} from './time'; -import useDark from './use-dark'; -import Volume from './volume'; +import {Picker} from '@react-native-picker/picker' +import {RouteProp, useRoute} from '@react-navigation/native' +import {useEffect, useState} from 'react' +import {View} from 'react-native' +import {BestPageParams} from './BestPage' +import Chart from './Chart' +import {PADDING} from './constants' +import {setRepo} from './db' +import GymSet from './gym-set' +import {Metrics} from './metrics' +import {Periods} from './periods' +import StackHeader from './StackHeader' +import {formatMonth} from './time' +import useDark from './use-dark' +import Volume from './volume' export default function ViewBest() { - const {params} = useRoute>(); - const dark = useDark(); - const [weights, setWeights] = useState([]); - const [volumes, setVolumes] = useState([]); - const [metric, setMetric] = useState(Metrics.Weight); - const [period, setPeriod] = useState(Periods.Monthly); + const {params} = useRoute>() + const dark = useDark() + const [weights, setWeights] = useState([]) + const [volumes, setVolumes] = useState([]) + const [metric, setMetric] = useState(Metrics.Weight) + const [period, setPeriod] = useState(Periods.Monthly) useEffect(() => { - console.log(`${ViewBest.name}.useEffect`, {metric}); - console.log(`${ViewBest.name}.useEffect`, {period}); - let difference = '-7 days'; - if (period === Periods.Monthly) difference = '-1 months'; - else if (period === Periods.Yearly) difference = '-1 years'; - let group = '%Y-%m-%d'; - if (period === Periods.Yearly) group = '%Y-%m'; + console.log(`${ViewBest.name}.useEffect`, {metric}) + console.log(`${ViewBest.name}.useEffect`, {period}) + let difference = '-7 days' + if (period === Periods.Monthly) difference = '-1 months' + else if (period === Periods.Yearly) difference = '-1 years' + let group = '%Y-%m-%d' + if (period === Periods.Yearly) group = '%Y-%m' const builder = setRepo .createQueryBuilder() .select("STRFTIME('%Y-%m-%d', created)", 'created') @@ -40,31 +40,28 @@ export default function ViewBest() { difference, }) .groupBy('name') - .addGroupBy(`STRFTIME('${group}', created)`); + .addGroupBy(`STRFTIME('${group}', created)`) switch (metric) { case Metrics.Weight: - builder - .addSelect('MAX(weight)', 'weight') - .getRawMany() - .then(setWeights); - break; + builder.addSelect('MAX(weight)', 'weight').getRawMany().then(setWeights) + break case Metrics.Volume: builder .addSelect('SUM(weight * reps)', 'value') .getRawMany() - .then(setVolumes); - break; + .then(setVolumes) + break default: // Brzycki formula https://en.wikipedia.org/wiki/One-repetition_maximum#Brzycki builder .addSelect('MAX(weight / (1.0278 - 0.0278 * reps))', 'weight') .getRawMany() .then(weights => { - console.log({weights}); - setWeights(weights); - }); + console.log({weights}) + setWeights(weights) + }) } - }, [params.best.name, metric, period]); + }, [params.best.name, metric, period]) return ( <> @@ -109,5 +106,5 @@ export default function ViewBest() { )} - ); + ) } diff --git a/WorkoutItem.tsx b/WorkoutItem.tsx index 6132b0e..4cd5b0d 100644 --- a/WorkoutItem.tsx +++ b/WorkoutItem.tsx @@ -1,48 +1,48 @@ -import {NavigationProp, useNavigation} from '@react-navigation/native'; -import {useCallback, useMemo, useState} from 'react'; -import {GestureResponderEvent, Image} from 'react-native'; -import {List, Menu, Text} from 'react-native-paper'; -import ConfirmDialog from './ConfirmDialog'; -import {setRepo} from './db'; -import GymSet from './gym-set'; -import {useSettings} from './use-settings'; -import {WorkoutsPageParams} from './WorkoutsPage'; +import {NavigationProp, useNavigation} from '@react-navigation/native' +import {useCallback, useMemo, useState} from 'react' +import {GestureResponderEvent, Image} from 'react-native' +import {List, Menu, Text} from 'react-native-paper' +import ConfirmDialog from './ConfirmDialog' +import {setRepo} from './db' +import GymSet from './gym-set' +import {useSettings} from './use-settings' +import {WorkoutsPageParams} from './WorkoutsPage' export default function WorkoutItem({ item, onRemove, }: { - item: GymSet; - onRemove: () => void; + item: GymSet + onRemove: () => void }) { - const [showMenu, setShowMenu] = useState(false); - const [anchor, setAnchor] = useState({x: 0, y: 0}); - const [showRemove, setShowRemove] = useState(''); - const {settings} = useSettings(); - const navigation = useNavigation>(); + const [showMenu, setShowMenu] = useState(false) + const [anchor, setAnchor] = useState({x: 0, y: 0}) + const [showRemove, setShowRemove] = useState('') + const {settings} = useSettings() + const navigation = useNavigation>() const remove = useCallback(async () => { - await setRepo.delete({name: item.name}); - setShowMenu(false); - onRemove(); - }, [setShowMenu, onRemove, item.name]); + await setRepo.delete({name: item.name}) + setShowMenu(false) + onRemove() + }, [setShowMenu, onRemove, item.name]) const longPress = useCallback( (e: GestureResponderEvent) => { - setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY}); - setShowMenu(true); + setAnchor({x: e.nativeEvent.pageX, y: e.nativeEvent.pageY}) + setShowMenu(true) }, [setShowMenu, setAnchor], - ); + ) const description = useMemo(() => { - const seconds = item.seconds?.toString().padStart(2, '0'); + const seconds = item.seconds?.toString().padStart(2, '0') if (settings.alarm && settings.showSets) - return `${item.sets} x ${item.minutes || 0}:${seconds}`; + return `${item.sets} x ${item.minutes || 0}:${seconds}` else if (settings.alarm && !settings.showSets) - return `${item.minutes || 0}:${seconds}`; - return `${item.sets}`; - }, [item, settings]); + return `${item.minutes || 0}:${seconds}` + return `${item.sets}` + }, [item, settings]) return ( <> @@ -69,8 +69,8 @@ export default function WorkoutItem({ { - setShowRemove(item.name); - setShowMenu(false); + setShowRemove(item.name) + setShowMenu(false) }} title="Delete" /> @@ -87,5 +87,5 @@ export default function WorkoutItem({ sure? - ); + ) } diff --git a/WorkoutList.tsx b/WorkoutList.tsx index ca5e7f5..94050c6 100644 --- a/WorkoutList.tsx +++ b/WorkoutList.tsx @@ -2,26 +2,26 @@ import { NavigationProp, useFocusEffect, useNavigation, -} from '@react-navigation/native'; -import {useCallback, useState} from 'react'; -import {FlatList} from 'react-native'; -import {List} from 'react-native-paper'; -import DrawerHeader from './DrawerHeader'; -import Page from './Page'; -import GymSet from './gym-set'; -import SetList from './SetList'; -import WorkoutItem from './WorkoutItem'; -import {WorkoutsPageParams} from './WorkoutsPage'; -import {setRepo} from './db'; +} from '@react-navigation/native' +import {useCallback, useState} from 'react' +import {FlatList} from 'react-native' +import {List} from 'react-native-paper' +import DrawerHeader from './DrawerHeader' +import Page from './Page' +import GymSet from './gym-set' +import SetList from './SetList' +import WorkoutItem from './WorkoutItem' +import {WorkoutsPageParams} from './WorkoutsPage' +import {setRepo} from './db' -const limit = 15; +const limit = 15 export default function WorkoutList() { - const [workouts, setWorkouts] = useState(); - const [offset, setOffset] = useState(0); - const [term, setTerm] = useState(''); - const [end, setEnd] = useState(false); - const navigation = useNavigation>(); + const [workouts, setWorkouts] = useState() + const [offset, setOffset] = useState(0) + const [term, setTerm] = useState('') + const [end, setEnd] = useState(false) + const navigation = useNavigation>() const refresh = useCallback(async (value: string) => { const newWorkouts = await setRepo @@ -31,35 +31,35 @@ export default function WorkoutList() { .groupBy('name') .orderBy('name') .limit(limit) - .getMany(); - console.log(`${WorkoutList.name}`, {newWorkout: newWorkouts[0]}); - setWorkouts(newWorkouts); - setOffset(0); - setEnd(false); - }, []); + .getMany() + console.log(`${WorkoutList.name}`, {newWorkout: newWorkouts[0]}) + setWorkouts(newWorkouts) + setOffset(0) + setEnd(false) + }, []) useFocusEffect( useCallback(() => { - refresh(term); + refresh(term) }, [refresh, term]), - ); + ) const renderItem = useCallback( ({item}: {item: GymSet}) => ( refresh(term)} /> ), [refresh, term], - ); + ) const next = useCallback(async () => { - if (end) return; - const newOffset = offset + limit; + if (end) return + const newOffset = offset + limit console.log(`${SetList.name}.next:`, { offset, limit, newOffset, term, - }); + }) const newWorkouts = await setRepo .createQueryBuilder() .select() @@ -68,27 +68,27 @@ export default function WorkoutList() { .orderBy('name') .limit(limit) .offset(newOffset) - .getMany(); - if (newWorkouts.length === 0) return setEnd(true); - if (!workouts) return; - setWorkouts([...workouts, ...newWorkouts]); - if (newWorkouts.length < limit) return setEnd(true); - setOffset(newOffset); - }, [term, end, offset, workouts]); + .getMany() + if (newWorkouts.length === 0) return setEnd(true) + if (!workouts) return + setWorkouts([...workouts, ...newWorkouts]) + if (newWorkouts.length < limit) return setEnd(true) + setOffset(newOffset) + }, [term, end, offset, workouts]) const onAdd = useCallback(async () => { navigation.navigate('EditWorkout', { value: new GymSet(), - }); - }, [navigation]); + }) + }, [navigation]) const search = useCallback( (value: string) => { - setTerm(value); - refresh(value); + setTerm(value) + refresh(value) }, [refresh], - ); + ) return ( <> @@ -110,5 +110,5 @@ export default function WorkoutList() { )} - ); + ) } diff --git a/WorkoutsPage.tsx b/WorkoutsPage.tsx index 35cb780..8419b1d 100644 --- a/WorkoutsPage.tsx +++ b/WorkoutsPage.tsx @@ -1,16 +1,16 @@ -import {createStackNavigator} from '@react-navigation/stack'; -import EditWorkout from './EditWorkout'; -import GymSet from './gym-set'; -import WorkoutList from './WorkoutList'; +import {createStackNavigator} from '@react-navigation/stack' +import EditWorkout from './EditWorkout' +import GymSet from './gym-set' +import WorkoutList from './WorkoutList' export type WorkoutsPageParams = { - WorkoutList: {}; + WorkoutList: {} EditWorkout: { - value: GymSet; - }; -}; + value: GymSet + } +} -const Stack = createStackNavigator(); +const Stack = createStackNavigator() export default function WorkoutsPage() { return ( @@ -19,5 +19,5 @@ export default function WorkoutsPage() { - ); + ) } diff --git a/best.service.ts b/best.service.ts index 9dc22a0..92450fb 100644 --- a/best.service.ts +++ b/best.service.ts @@ -1,5 +1,5 @@ -import {setRepo} from './db'; -import GymSet from './gym-set'; +import {setRepo} from './db' +import GymSet from './gym-set' export const getBestSet = async (name: string): Promise => { return setRepo @@ -11,5 +11,5 @@ export const getBestSet = async (name: string): Promise => { .addGroupBy('reps') .orderBy('weight', 'DESC') .addOrderBy('reps', 'DESC') - .getOne(); -}; + .getOne() +} diff --git a/color.ts b/color.ts index 2f5f353..51acc05 100644 --- a/color.ts +++ b/color.ts @@ -1,11 +1,11 @@ -import React, {useContext} from 'react'; +import React, {useContext} from 'react' export const Color = React.createContext({ color: '', setColor: (_value: string) => {}, -}); +}) export const useColor = () => { - const context = useContext(Color); - return context; -}; + const context = useContext(Color) + return context +} diff --git a/colors.ts b/colors.ts index 3491c72..1e6339c 100644 --- a/colors.ts +++ b/colors.ts @@ -3,34 +3,34 @@ export const lightColors = [ {hex: '#B3E5FC', name: 'Cyan'}, {hex: '#FFC0CB', name: 'Pink'}, {hex: '#E9DCC9', name: 'Linen'}, -]; +] export const darkColors = [ {hex: '#8156A7', name: 'Purple'}, {hex: '#007AFF', name: 'Blue'}, {hex: '#000000', name: 'Black'}, {hex: '#CD5C5C', name: 'Red'}, -]; +] export const colorShade = (color: any, amount: number) => { - color = color.replace(/^#/, ''); + color = color.replace(/^#/, '') if (color.length === 3) - color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]; + color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2] - let [r, g, b] = color.match(/.{2}/g); - [r, g, b] = [ + let [r, g, b] = color.match(/.{2}/g) + ;[r, g, b] = [ parseInt(r, 16) + amount, parseInt(g, 16) + amount, parseInt(b, 16) + amount, - ]; + ] - r = Math.max(Math.min(255, r), 0).toString(16); - g = Math.max(Math.min(255, g), 0).toString(16); - b = Math.max(Math.min(255, b), 0).toString(16); + r = Math.max(Math.min(255, r), 0).toString(16) + g = Math.max(Math.min(255, g), 0).toString(16) + b = Math.max(Math.min(255, b), 0).toString(16) - const rr = (r.length < 2 ? '0' : '') + r; - const gg = (g.length < 2 ? '0' : '') + g; - const bb = (b.length < 2 ? '0' : '') + b; + const rr = (r.length < 2 ? '0' : '') + r + const gg = (g.length < 2 ? '0' : '') + g + const bb = (b.length < 2 ? '0' : '') + b - return `#${rr}${gg}${bb}`; -}; + return `#${rr}${gg}${bb}` +} diff --git a/constants.ts b/constants.ts index ae645e4..f8c3a3a 100644 --- a/constants.ts +++ b/constants.ts @@ -1,2 +1,2 @@ -export const MARGIN = 10; -export const PADDING = 10; +export const MARGIN = 10 +export const PADDING = 10 diff --git a/count-many.ts b/count-many.ts index 0fbcafd..cf82397 100644 --- a/count-many.ts +++ b/count-many.ts @@ -1,5 +1,5 @@ export default interface CountMany { - name: string; - total: number; - sets?: number; + name: string + total: number + sets?: number } diff --git a/data-source.ts b/data-source.ts index 9faa6a7..6f7e296 100644 --- a/data-source.ts +++ b/data-source.ts @@ -1,29 +1,29 @@ -import {DataSource} from 'typeorm'; -import GymSet from './gym-set'; -import {Sets1667185586014} from './migrations/1667185586014-sets'; -import {plans1667186124792} from './migrations/1667186124792-plans'; -import {settings1667186130041} from './migrations/1667186130041-settings'; -import {addSound1667186139844} from './migrations/1667186139844-add-sound'; -import {addHidden1667186159379} from './migrations/1667186159379-add-hidden'; -import {addNotify1667186166140} from './migrations/1667186166140-add-notify'; -import {addImage1667186171548} from './migrations/1667186171548-add-image'; -import {addImages1667186179488} from './migrations/1667186179488-add-images'; -import {insertSettings1667186203827} from './migrations/1667186203827-insert-settings'; -import {addSteps1667186211251} from './migrations/1667186211251-add-steps'; -import {addSets1667186250618} from './migrations/1667186250618-add-sets'; -import {addMinutes1667186255650} from './migrations/1667186255650-add-minutes'; -import {addSeconds1667186259174} from './migrations/1667186259174-add-seconds'; -import {addShowUnit1667186265588} from './migrations/1667186265588-add-show-unit'; -import {addColor1667186320954} from './migrations/1667186320954-add-color'; -import {addSteps1667186348425} from './migrations/1667186348425-add-steps'; -import {addDate1667186431804} from './migrations/1667186431804-add-date'; -import {addShowDate1667186435051} from './migrations/1667186435051-add-show-date'; -import {addTheme1667186439366} from './migrations/1667186439366-add-theme'; -import {addShowSets1667186443614} from './migrations/1667186443614-add-show-sets'; -import {addSetsCreated1667186451005} from './migrations/1667186451005-add-sets-created'; -import {addNoSound1667186456118} from './migrations/1667186456118-add-no-sound'; -import {Plan} from './plan'; -import Settings from './settings'; +import {DataSource} from 'typeorm' +import GymSet from './gym-set' +import {Sets1667185586014} from './migrations/1667185586014-sets' +import {plans1667186124792} from './migrations/1667186124792-plans' +import {settings1667186130041} from './migrations/1667186130041-settings' +import {addSound1667186139844} from './migrations/1667186139844-add-sound' +import {addHidden1667186159379} from './migrations/1667186159379-add-hidden' +import {addNotify1667186166140} from './migrations/1667186166140-add-notify' +import {addImage1667186171548} from './migrations/1667186171548-add-image' +import {addImages1667186179488} from './migrations/1667186179488-add-images' +import {insertSettings1667186203827} from './migrations/1667186203827-insert-settings' +import {addSteps1667186211251} from './migrations/1667186211251-add-steps' +import {addSets1667186250618} from './migrations/1667186250618-add-sets' +import {addMinutes1667186255650} from './migrations/1667186255650-add-minutes' +import {addSeconds1667186259174} from './migrations/1667186259174-add-seconds' +import {addShowUnit1667186265588} from './migrations/1667186265588-add-show-unit' +import {addColor1667186320954} from './migrations/1667186320954-add-color' +import {addSteps1667186348425} from './migrations/1667186348425-add-steps' +import {addDate1667186431804} from './migrations/1667186431804-add-date' +import {addShowDate1667186435051} from './migrations/1667186435051-add-show-date' +import {addTheme1667186439366} from './migrations/1667186439366-add-theme' +import {addShowSets1667186443614} from './migrations/1667186443614-add-show-sets' +import {addSetsCreated1667186451005} from './migrations/1667186451005-add-sets-created' +import {addNoSound1667186456118} from './migrations/1667186456118-add-no-sound' +import {Plan} from './plan' +import Settings from './settings' export const AppDataSource = new DataSource({ type: 'react-native', @@ -55,4 +55,4 @@ export const AppDataSource = new DataSource({ addSetsCreated1667186451005, addNoSound1667186456118, ], -}); +}) diff --git a/db.ts b/db.ts index bde42d1..3888f8a 100644 --- a/db.ts +++ b/db.ts @@ -1,26 +1,26 @@ -import {enablePromise, SQLiteDatabase} from 'react-native-sqlite-storage'; -import {AppDataSource} from './data-source'; -import GymSet from './gym-set'; -import {Plan} from './plan'; -import Settings from './settings'; +import {enablePromise, SQLiteDatabase} from 'react-native-sqlite-storage' +import {AppDataSource} from './data-source' +import GymSet from './gym-set' +import {Plan} from './plan' +import Settings from './settings' -enablePromise(true); +enablePromise(true) -export let db: SQLiteDatabase; +export let db: SQLiteDatabase -export const setRepo = AppDataSource.manager.getRepository(GymSet); -export const planRepo = AppDataSource.manager.getRepository(Plan); -export const settingsRepo = AppDataSource.manager.getRepository(Settings); +export const setRepo = AppDataSource.manager.getRepository(GymSet) +export const planRepo = AppDataSource.manager.getRepository(Plan) +export const settingsRepo = AppDataSource.manager.getRepository(Settings) export const getNow = (): Promise<{now: string}[]> => { return AppDataSource.manager.query( "SELECT STRFTIME('%Y-%m-%dT%H:%M:%S','now','localtime') AS now", - ); -}; + ) +} export const runMigrations = async () => { - console.log(`${runMigrations.name}:`, 'Initializing...'); - await AppDataSource.initialize(); - console.log(`${runMigrations.name}:`, 'Running migrations...'); - await AppDataSource.runMigrations(); -}; + console.log(`${runMigrations.name}:`, 'Initializing...') + await AppDataSource.initialize() + console.log(`${runMigrations.name}:`, 'Running migrations...') + await AppDataSource.runMigrations() +} diff --git a/drawer-param-list.ts b/drawer-param-list.ts index e590f4c..a7e8ac5 100644 --- a/drawer-param-list.ts +++ b/drawer-param-list.ts @@ -1,7 +1,7 @@ export type DrawerParamList = { - Home: {}; - Settings: {}; - Best: {}; - Plans: {}; - Workouts: {}; -}; + Home: {} + Settings: {} + Best: {} + Plans: {} + Workouts: {} +} diff --git a/gym-set.ts b/gym-set.ts index f0c8a59..4bab6f6 100644 --- a/gym-set.ts +++ b/gym-set.ts @@ -1,40 +1,40 @@ -import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm'; +import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm' @Entity('sets') export default class GymSet { @PrimaryGeneratedColumn() - id?: number; + id?: number @Column('text') - name: string; + name: string @Column('int') - reps: number; + reps: number @Column('int') - weight: number; + weight: number @Column('int') - sets = 3; + sets = 3 @Column('int') - minutes = 3; + minutes = 3 @Column('int') - seconds = 30; + seconds = 30 @Column('boolean') - hidden = false; + hidden = false @Column('text') - created: string; + created: string @Column('text') - unit: string; + unit: string @Column('text') - image: string; + image: string @Column('text') - steps?: string; + steps?: string } diff --git a/home-page-params.ts b/home-page-params.ts index a19a8d7..7d7c907 100644 --- a/home-page-params.ts +++ b/home-page-params.ts @@ -1,8 +1,8 @@ -import GymSet from './gym-set'; +import GymSet from './gym-set' export type HomePageParams = { - Sets: {}; + Sets: {} EditSet: { - set: GymSet; - }; -}; + set: GymSet + } +} diff --git a/input.ts b/input.ts index 15a2cff..22e7fde 100644 --- a/input.ts +++ b/input.ts @@ -1,5 +1,5 @@ export default interface Input { - name: string; - value?: T; - onChange: (value: T) => void; + name: string + value?: T + onChange: (value: T) => void } diff --git a/jestSetup.ts b/jestSetup.ts index 15a7964..64d7f6f 100644 --- a/jestSetup.ts +++ b/jestSetup.ts @@ -1,5 +1,5 @@ -import 'react-native-gesture-handler/jestSetup'; -import {NativeModules as RNNativeModules} from 'react-native'; +import 'react-native-gesture-handler/jestSetup' +import {NativeModules as RNNativeModules} from 'react-native' //RNNativeModules.UIManager = RNNativeModules.UIManager || {}; //RNNativeModules.UIManager.RCTView = RNNativeModules.UIManager.RCTView || {}; @@ -16,14 +16,14 @@ import {NativeModules as RNNativeModules} from 'react-native'; //}; RNNativeModules.RNViewShot = RNNativeModules.RNViewShot || { captureScreen: jest.fn(), -}; +} -jest.mock('react-native-file-access', () => jest.fn()); -jest.mock('react-native-share', () => jest.fn()); -jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); -jest.useFakeTimers(); +jest.mock('react-native-file-access', () => jest.fn()) +jest.mock('react-native-share', () => jest.fn()) +jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper') +jest.useFakeTimers() jest.mock('react-native-reanimated', () => { - const Reanimated = require('react-native-reanimated/mock'); - Reanimated.default.call = () => {}; - return Reanimated; -}); + const Reanimated = require('react-native-reanimated/mock') + Reanimated.default.call = () => {} + return Reanimated +}) diff --git a/mock-providers.tsx b/mock-providers.tsx index ed1b337..60af785 100644 --- a/mock-providers.tsx +++ b/mock-providers.tsx @@ -1,21 +1,21 @@ -import {NavigationContainer} from '@react-navigation/native'; -import React from 'react'; -import {Provider as PaperProvider} from 'react-native-paper'; -import {Color} from './color'; -import {lightColors} from './colors'; -import MassiveSnack from './MassiveSnack'; -import {defaultSettings, SettingsContext} from './use-settings'; -import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; +import {NavigationContainer} from '@react-navigation/native' +import React from 'react' +import {Provider as PaperProvider} from 'react-native-paper' +import {Color} from './color' +import {lightColors} from './colors' +import MassiveSnack from './MassiveSnack' +import {defaultSettings, SettingsContext} from './use-settings' +import MaterialIcon from 'react-native-vector-icons/MaterialIcons' -const color = lightColors[0].hex; -export const setColor = jest.fn(); -const settings = defaultSettings; -export const setSettings = jest.fn(); +const color = lightColors[0].hex +export const setColor = jest.fn() +const settings = defaultSettings +export const setSettings = jest.fn() export const MockProviders = ({ children, }: { - children: JSX.Element | JSX.Element[]; + children: JSX.Element | JSX.Element[] }) => ( }}> @@ -26,4 +26,4 @@ export const MockProviders = ({ -); +) diff --git a/plan-page-params.ts b/plan-page-params.ts index 425a4a4..f336b1d 100644 --- a/plan-page-params.ts +++ b/plan-page-params.ts @@ -1,11 +1,11 @@ -import {Plan} from './plan'; +import {Plan} from './plan' export type PlanPageParams = { - PlanList: {}; + PlanList: {} EditPlan: { - plan: Plan; - }; + plan: Plan + } StartPlan: { - plan: Plan; - }; -}; + plan: Plan + } +} diff --git a/plan.ts b/plan.ts index 2012639..ce258e6 100644 --- a/plan.ts +++ b/plan.ts @@ -1,13 +1,13 @@ -import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm'; +import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm' @Entity('plans') export class Plan { @PrimaryGeneratedColumn() - id?: number; + id?: number @Column('text') - days: string; + days: string @Column('text') - workouts: string; + workouts: string } diff --git a/route.ts b/route.ts index b8d37c1..8d09a3e 100644 --- a/route.ts +++ b/route.ts @@ -1,7 +1,7 @@ -import {DrawerParamList} from './drawer-param-list'; +import {DrawerParamList} from './drawer-param-list' export default interface Route { - name: keyof DrawerParamList; - component: React.ComponentType; - icon: string; + name: keyof DrawerParamList + component: React.ComponentType + icon: string } diff --git a/settings.ts b/settings.ts index 283353b..f925efc 100644 --- a/settings.ts +++ b/settings.ts @@ -1,43 +1,43 @@ -import {Column, Entity, PrimaryColumn} from 'typeorm'; +import {Column, Entity, PrimaryColumn} from 'typeorm' @Entity() export default class Settings { @PrimaryColumn('boolean') - alarm: boolean; + alarm: boolean @Column('boolean') - vibrate: boolean; + vibrate: boolean @Column('text') - sound: string; + sound: string @Column('boolean') - notify: boolean; + notify: boolean @Column('boolean') - images: boolean; + images: boolean @Column('boolean') - showUnit: boolean; + showUnit: boolean @Column('text') - color: string; + color: string @Column('boolean') - steps: boolean; + steps: boolean @Column('text') - date: string; + date: string @Column('boolean') - showDate: boolean; + showDate: boolean @Column('text') - theme: string; + theme: string @Column('boolean') - showSets: boolean; + showSets: boolean @Column('boolean') - noSound: boolean; + noSound: boolean } diff --git a/time.ts b/time.ts index 8bbec8b..465658b 100644 --- a/time.ts +++ b/time.ts @@ -6,26 +6,26 @@ export const DAYS = [ 'Thursday', 'Friday', 'Saturday', -]; +] export function formatMonth(iso: string) { - const date = new Date(iso); - const dd = date.getDate().toString(); - const mm = (date.getMonth() + 1).toString(); - return `${dd}/${mm}`; + const date = new Date(iso) + const dd = date.getDate().toString() + const mm = (date.getMonth() + 1).toString() + return `${dd}/${mm}` } function twelveHour(twentyFourHour: string) { - const [hourString, minute] = twentyFourHour.split(':'); - const hour = +hourString % 24; - return (hour % 12 || 12) + ':' + minute + (hour < 12 ? ' AM' : ' PM'); + const [hourString, minute] = twentyFourHour.split(':') + const hour = +hourString % 24 + return (hour % 12 || 12) + ':' + minute + (hour < 12 ? ' AM' : ' PM') } function dayOfWeek(iso: string) { - const date = new Date(iso); - const day = date.getDay(); - const target = DAYS[day]; - return target.slice(0, 3); + const date = new Date(iso) + const day = date.getDay() + const target = DAYS[day] + return target.slice(0, 3) } /** @@ -33,29 +33,29 @@ function dayOfWeek(iso: string) { * @param kind Intended format for the date, e.g. '%Y-%m-%d %H:%M' */ export function format(iso: string, kind: string) { - const split = iso.split('T'); - const [year, month, day] = split[0].split('-'); - const time = twelveHour(split[1]); + const split = iso.split('T') + const [year, month, day] = split[0].split('-') + const time = twelveHour(split[1]) switch (kind) { case '%Y-%m-%d %H:%M': - return iso.replace('T', ' ').replace(/:\d{2}/, ''); + return iso.replace('T', ' ').replace(/:\d{2}/, '') case '%Y-%m-%d': - return split[0]; + return split[0] case '%H:%M': - return split[1].replace(/:\d{2}/, ''); + return split[1].replace(/:\d{2}/, '') case '%d/%m/%y %h:%M %p': - return `${day}/${month}/${year} ${time}`; + return `${day}/${month}/${year} ${time}` case '%d/%m %h:%M %p': - return `${day}/${month} ${time}`; + return `${day}/${month} ${time}` case '%d/%m/%y': - return `${day}/${month}/${year}`; + return `${day}/${month}/${year}` case '%d/%m': - return `${day}/${month}`; + return `${day}/${month}` case '%h:%M %p': - return time; + return time case '%A %h:%M %p': - return dayOfWeek(iso) + ' ' + time; + return dayOfWeek(iso) + ' ' + time default: - return iso; + return iso } } diff --git a/use-dark.ts b/use-dark.ts index 5629c3f..345746c 100644 --- a/use-dark.ts +++ b/use-dark.ts @@ -1,11 +1,11 @@ -import {useColorScheme} from 'react-native'; -import {useSettings} from './use-settings'; +import {useColorScheme} from 'react-native' +import {useSettings} from './use-settings' export default function useDark() { - const dark = useColorScheme() === 'dark'; - const {settings} = useSettings(); + const dark = useColorScheme() === 'dark' + const {settings} = useSettings() - if (settings.theme === 'dark') return true; - if (settings.theme === 'light') return false; - return dark; + if (settings.theme === 'dark') return true + if (settings.theme === 'light') return false + return dark } diff --git a/use-settings.ts b/use-settings.ts index 8c40c20..2f72009 100644 --- a/use-settings.ts +++ b/use-settings.ts @@ -1,5 +1,5 @@ -import React, {useContext} from 'react'; -import Settings from './settings'; +import React, {useContext} from 'react' +import Settings from './settings' export const defaultSettings: Settings = { alarm: 0, @@ -15,16 +15,16 @@ export const defaultSettings: Settings = { theme: 'system', vibrate: 1, noSound: 0, -}; +} export const SettingsContext = React.createContext<{ - settings: Settings; - setSettings: (value: Settings) => void; + settings: Settings + setSettings: (value: Settings) => void }>({ settings: defaultSettings, setSettings: () => null, -}); +}) export function useSettings() { - return useContext(SettingsContext); + return useContext(SettingsContext) } diff --git a/volume.ts b/volume.ts index 786b73e..d019f36 100644 --- a/volume.ts +++ b/volume.ts @@ -1,6 +1,6 @@ export default interface Volume { - name: string; - created: string; - value: number; - unit: string; + name: string + created: string + value: number + unit: string } diff --git a/write.ts b/write.ts index 679acf7..16d9387 100644 --- a/write.ts +++ b/write.ts @@ -1,18 +1,18 @@ -import {NativeModules, PermissionsAndroid} from 'react-native'; -import {Dirs, FileSystem} from 'react-native-file-access'; +import {NativeModules, PermissionsAndroid} from 'react-native' +import {Dirs, FileSystem} from 'react-native-file-access' export const write = async (name: string, data: string) => { - const filePath = `${Dirs.DocumentDir}/${name}`; + const filePath = `${Dirs.DocumentDir}/${name}` const permission = async () => { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, - ); - return granted === PermissionsAndroid.RESULTS.GRANTED; - }; - const granted = await permission(); - if (!granted) return; - await FileSystem.writeFile(filePath, data); - if (!FileSystem.exists(filePath)) return; - await FileSystem.cpExternal(filePath, name, 'downloads'); - NativeModules.DownloadModule.show(name); -}; + ) + return granted === PermissionsAndroid.RESULTS.GRANTED + } + const granted = await permission() + if (!granted) return + await FileSystem.writeFile(filePath, data) + if (!FileSystem.exists(filePath)) return + await FileSystem.cpExternal(filePath, name, 'downloads') + NativeModules.DownloadModule.show(name) +} From eafad1f47eb7908c9e26ee0927b0730e169cf8e1 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Mon, 31 Oct 2022 18:16:11 +1300 Subject: [PATCH 29/78] Simplify migrations in App.tsx --- App.tsx | 7 ++++--- db.ts | 7 ------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/App.tsx b/App.tsx index 42aa61f..2122fd6 100644 --- a/App.tsx +++ b/App.tsx @@ -13,7 +13,8 @@ import { import MaterialIcon from 'react-native-vector-icons/MaterialIcons' import {Color} from './color' import {lightColors} from './colors' -import {runMigrations, settingsRepo} from './db' +import {AppDataSource} from './data-source' +import {settingsRepo} from './db' import MassiveSnack from './MassiveSnack' import Routes from './Routes' import Settings from './settings' @@ -49,9 +50,9 @@ const App = () => { ) useEffect(() => { - runMigrations().then(async () => { + AppDataSource.initialize().then(async () => { const gotSettings = await settingsRepo.findOne({where: {}}) - console.log(`${App.name}.runMigrations:`, {gotSettings}) + console.log(`${App.name}.useEffect:`, {gotSettings}) setSettings(gotSettings) if (gotSettings.color) setColor(gotSettings.color) }) diff --git a/db.ts b/db.ts index 3888f8a..41d7b52 100644 --- a/db.ts +++ b/db.ts @@ -17,10 +17,3 @@ export const getNow = (): Promise<{now: string}[]> => { "SELECT STRFTIME('%Y-%m-%dT%H:%M:%S','now','localtime') AS now", ) } - -export const runMigrations = async () => { - console.log(`${runMigrations.name}:`, 'Initializing...') - await AppDataSource.initialize() - console.log(`${runMigrations.name}:`, 'Running migrations...') - await AppDataSource.runMigrations() -} From bd6b20fb4eb51eaa89c357974f7311a7628bbcbe Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Mon, 31 Oct 2022 18:16:19 +1300 Subject: [PATCH 30/78] Add migration to drop old migrations table --- data-source.ts | 7 +++++-- migrations/1667190214743-drop-migrations.ts | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 migrations/1667190214743-drop-migrations.ts diff --git a/data-source.ts b/data-source.ts index 6f7e296..e8b78fb 100644 --- a/data-source.ts +++ b/data-source.ts @@ -1,6 +1,6 @@ import {DataSource} from 'typeorm' import GymSet from './gym-set' -import {Sets1667185586014} from './migrations/1667185586014-sets' +import {Sets1667185586014 as sets1667185586014} from './migrations/1667185586014-sets' import {plans1667186124792} from './migrations/1667186124792-plans' import {settings1667186130041} from './migrations/1667186130041-settings' import {addSound1667186139844} from './migrations/1667186139844-add-sound' @@ -22,6 +22,7 @@ import {addTheme1667186439366} from './migrations/1667186439366-add-theme' import {addShowSets1667186443614} from './migrations/1667186443614-add-show-sets' import {addSetsCreated1667186451005} from './migrations/1667186451005-add-sets-created' import {addNoSound1667186456118} from './migrations/1667186456118-add-no-sound' +import {dropMigrations1667190214743} from './migrations/1667190214743-drop-migrations' import {Plan} from './plan' import Settings from './settings' @@ -30,9 +31,10 @@ export const AppDataSource = new DataSource({ database: 'massive.db', location: 'default', entities: [GymSet, Plan, Settings], + migrationsRun: true, migrationsTableName: 'typeorm_migrations', migrations: [ - Sets1667185586014, + sets1667185586014, plans1667186124792, settings1667186130041, addSound1667186139844, @@ -54,5 +56,6 @@ export const AppDataSource = new DataSource({ addShowSets1667186443614, addSetsCreated1667186451005, addNoSound1667186456118, + dropMigrations1667190214743, ], }) diff --git a/migrations/1667190214743-drop-migrations.ts b/migrations/1667190214743-drop-migrations.ts new file mode 100644 index 0000000..ea475e4 --- /dev/null +++ b/migrations/1667190214743-drop-migrations.ts @@ -0,0 +1,19 @@ +import {MigrationInterface, QueryRunner, Table, TableColumn} from 'typeorm' + +export class dropMigrations1667190214743 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable('migrations').catch(() => null) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: 'migrations', + columns: [ + new TableColumn({name: 'id', type: 'integer'}), + new TableColumn({name: 'command', type: 'text'}), + ], + }), + ) + } +} From 09ee09f509fec7f3a24fbfdfc248180f0d762ba0 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Mon, 31 Oct 2022 20:58:51 +1300 Subject: [PATCH 31/78] Ran prettier on __tests__ --- __tests__/BestList-test.tsx | 18 +++++++++--------- __tests__/PlanItem-test.tsx | 32 ++++++++++++++++---------------- __tests__/PlanList-test.tsx | 18 +++++++++--------- __tests__/SetItem-test.tsx | 30 +++++++++++++++--------------- __tests__/SetList-test.tsx | 18 +++++++++--------- __tests__/WorkoutItem-test.tsx | 26 +++++++++++++------------- __tests__/WorkoutList-test.tsx | 18 +++++++++--------- 7 files changed, 80 insertions(+), 80 deletions(-) diff --git a/__tests__/BestList-test.tsx b/__tests__/BestList-test.tsx index cf438c2..d716ec3 100644 --- a/__tests__/BestList-test.tsx +++ b/__tests__/BestList-test.tsx @@ -1,15 +1,15 @@ -import {render, screen} from '@testing-library/react-native'; -import React from 'react'; -import 'react-native'; -import BestList from '../BestList'; -import {MockProviders} from '../mock-providers'; +import {render, screen} from '@testing-library/react-native' +import React from 'react' +import 'react-native' +import BestList from '../BestList' +import {MockProviders} from '../mock-providers' it('renders correctly', () => { render( , - ); - expect(screen.getByText('Best')).toBeDefined(); - expect(screen.getByPlaceholderText('Search')).toBeDefined(); -}); + ) + expect(screen.getByText('Best')).toBeDefined() + expect(screen.getByPlaceholderText('Search')).toBeDefined() +}) diff --git a/__tests__/PlanItem-test.tsx b/__tests__/PlanItem-test.tsx index c9d6f31..f3cc4ae 100644 --- a/__tests__/PlanItem-test.tsx +++ b/__tests__/PlanItem-test.tsx @@ -1,26 +1,26 @@ -import {render, screen} from '@testing-library/react-native'; -import React from 'react'; -import 'react-native'; -import {MockProviders} from '../mock-providers'; -import {Plan} from '../plan'; -import PlanItem from '../PlanItem'; +import {render, screen} from '@testing-library/react-native' +import React from 'react' +import 'react-native' +import {MockProviders} from '../mock-providers' +import {Plan} from '../plan' +import PlanItem from '../PlanItem' const plan: Plan = { days: 'Monday,Tuesday,Wednesday', workouts: 'Bench press,Bicep curls,Overhead press', -}; +} it('renders correctly', () => { - const onRemove = jest.fn(); + const onRemove = jest.fn() render( , - ); - expect(screen.getByText(/Monday/i)).toBeDefined(); - expect(screen.getByText(/Tuesday/i)).toBeDefined(); - expect(screen.getByText(/Wednesday/i)).toBeDefined(); - expect(screen.getByText(/Bench press/i)).toBeDefined(); - expect(screen.getByText(/Bicep curls/i)).toBeDefined(); - expect(screen.getByText(/Overhead press/i)).toBeDefined(); -}); + ) + expect(screen.getByText(/Monday/i)).toBeDefined() + expect(screen.getByText(/Tuesday/i)).toBeDefined() + expect(screen.getByText(/Wednesday/i)).toBeDefined() + expect(screen.getByText(/Bench press/i)).toBeDefined() + expect(screen.getByText(/Bicep curls/i)).toBeDefined() + expect(screen.getByText(/Overhead press/i)).toBeDefined() +}) diff --git a/__tests__/PlanList-test.tsx b/__tests__/PlanList-test.tsx index d34a255..c93bad0 100644 --- a/__tests__/PlanList-test.tsx +++ b/__tests__/PlanList-test.tsx @@ -1,15 +1,15 @@ -import {render, screen} from '@testing-library/react-native'; -import React from 'react'; -import 'react-native'; -import {MockProviders} from '../mock-providers'; -import PlanList from '../PlanList'; +import {render, screen} from '@testing-library/react-native' +import React from 'react' +import 'react-native' +import {MockProviders} from '../mock-providers' +import PlanList from '../PlanList' it('renders correctly', () => { render( , - ); - expect(screen.getByText('Plans')).toBeDefined(); - expect(screen.getByPlaceholderText('Search')).toBeDefined(); -}); + ) + expect(screen.getByText('Plans')).toBeDefined() + expect(screen.getByPlaceholderText('Search')).toBeDefined() +}) diff --git a/__tests__/SetItem-test.tsx b/__tests__/SetItem-test.tsx index c397eda..335e76b 100644 --- a/__tests__/SetItem-test.tsx +++ b/__tests__/SetItem-test.tsx @@ -1,26 +1,26 @@ -import {render, screen} from '@testing-library/react-native'; -import React from 'react'; -import 'react-native'; -import {MockProviders} from '../mock-providers'; -import Set from '../set'; -import SetItem from '../SetItem'; +import {render, screen} from '@testing-library/react-native' +import React from 'react' +import 'react-native' +import {MockProviders} from '../mock-providers' +import Set from '../set' +import SetItem from '../SetItem' const set: Set = { name: 'Bench press', reps: 6, weight: 20, -}; +} it('renders correctly', () => { - const onRemove = jest.fn(); + const onRemove = jest.fn() render( , - ); - expect(screen.getByText(set.name)).toBeDefined(); - const reps = RegExp(set.reps.toString()); - expect(screen.getByText(reps)).toBeDefined(); - const weight = RegExp(set.weight.toString()); - expect(screen.getByText(weight)).toBeDefined(); -}); + ) + expect(screen.getByText(set.name)).toBeDefined() + const reps = RegExp(set.reps.toString()) + expect(screen.getByText(reps)).toBeDefined() + const weight = RegExp(set.weight.toString()) + expect(screen.getByText(weight)).toBeDefined() +}) diff --git a/__tests__/SetList-test.tsx b/__tests__/SetList-test.tsx index d385a31..87b9ad4 100644 --- a/__tests__/SetList-test.tsx +++ b/__tests__/SetList-test.tsx @@ -1,15 +1,15 @@ -import {render, screen} from '@testing-library/react-native'; -import React from 'react'; -import 'react-native'; -import {MockProviders} from '../mock-providers'; -import SetList from '../SetList'; +import {render, screen} from '@testing-library/react-native' +import React from 'react' +import 'react-native' +import {MockProviders} from '../mock-providers' +import SetList from '../SetList' it('renders correctly', () => { render( , - ); - expect(screen.getByText('Home')).toBeDefined(); - expect(screen.getByPlaceholderText('Search')).toBeDefined(); -}); + ) + expect(screen.getByText('Home')).toBeDefined() + expect(screen.getByPlaceholderText('Search')).toBeDefined() +}) diff --git a/__tests__/WorkoutItem-test.tsx b/__tests__/WorkoutItem-test.tsx index c892c33..ce763c6 100644 --- a/__tests__/WorkoutItem-test.tsx +++ b/__tests__/WorkoutItem-test.tsx @@ -1,9 +1,9 @@ -import {render, screen} from '@testing-library/react-native'; -import React from 'react'; -import 'react-native'; -import {MockProviders} from '../mock-providers'; -import Set from '../set'; -import WorkoutItem from '../WorkoutItem'; +import {render, screen} from '@testing-library/react-native' +import React from 'react' +import 'react-native' +import {MockProviders} from '../mock-providers' +import Set from '../set' +import WorkoutItem from '../WorkoutItem' const set: Set = { name: 'Bench press', @@ -12,16 +12,16 @@ const set: Set = { seconds: 40, minutes: 3, sets: 5, -}; +} it('renders correctly', () => { - const onRemove = jest.fn(); + const onRemove = jest.fn() render( , - ); - expect(screen.getByText(set.name)).toBeDefined(); - const sets = RegExp(set.sets?.toString() || ''); - expect(screen.getByText(sets)).toBeDefined(); -}); + ) + expect(screen.getByText(set.name)).toBeDefined() + const sets = RegExp(set.sets?.toString() || '') + expect(screen.getByText(sets)).toBeDefined() +}) diff --git a/__tests__/WorkoutList-test.tsx b/__tests__/WorkoutList-test.tsx index b20a508..5fe5823 100644 --- a/__tests__/WorkoutList-test.tsx +++ b/__tests__/WorkoutList-test.tsx @@ -1,15 +1,15 @@ -import {render, screen} from '@testing-library/react-native'; -import React from 'react'; -import 'react-native'; -import {MockProviders} from '../mock-providers'; -import WorkoutList from '../WorkoutList'; +import {render, screen} from '@testing-library/react-native' +import React from 'react' +import 'react-native' +import {MockProviders} from '../mock-providers' +import WorkoutList from '../WorkoutList' it('renders correctly', () => { render( , - ); - expect(screen.getByText('Workouts')).toBeDefined(); - expect(screen.getByPlaceholderText('Search')).toBeDefined(); -}); + ) + expect(screen.getByText('Workouts')).toBeDefined() + expect(screen.getByPlaceholderText('Search')).toBeDefined() +}) From b782d66bf21a6fe6712ad6e5c0dba5361b75f341 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Mon, 31 Oct 2022 20:59:40 +1300 Subject: [PATCH 32/78] Fix adding new set from homepage --- SetList.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/SetList.tsx b/SetList.tsx index e612fa4..e3f6cf2 100644 --- a/SetList.tsx +++ b/SetList.tsx @@ -73,18 +73,20 @@ export default function SetList() { const onAdd = useCallback(async () => { console.log(`${SetList.name}.onAdd`, {set}) const [{now}] = await getNow() - navigation.navigate('EditSet', { - set: set || { - hidden: false, - minutes: 3, - name: '', - reps: 0, - seconds: 30, - sets: 3, - weight: 0, - created: now, - }, - }) + const newSet: GymSet = set || { + hidden: false, + minutes: 3, + name: '', + reps: 0, + seconds: 30, + sets: 3, + weight: 0, + created: now, + image: '', + unit: 'kg', + } + delete newSet.id + navigation.navigate('EditSet', {set: newSet}) }, [navigation, set]) const search = useCallback( From bdb27894f70b3e69119ea38e888e98ba2a14d27a Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Mon, 31 Oct 2022 21:00:10 +1300 Subject: [PATCH 33/78] Optimize root context --- .eslintrc.js | 2 +- App.tsx | 15 ++++++++-- EditWorkout.tsx | 1 - PlanItem.tsx | 1 - Routes.tsx | 18 +++++++----- SettingsPage.tsx | 71 ++++++++++++++++++++++-------------------------- StartPlan.tsx | 2 +- ViewBest.tsx | 6 ++-- babel.config.js | 6 ++-- jest.config.js | 2 +- metro.config.js | 2 +- use-settings.ts | 18 ++++++------ 12 files changed, 75 insertions(+), 69 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index dcf0be0..6b1f939 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,4 +13,4 @@ module.exports = { }, }, ], -}; +} diff --git a/App.tsx b/App.tsx index 2122fd6..573238e 100644 --- a/App.tsx +++ b/App.tsx @@ -42,6 +42,7 @@ export const CombinedDarkTheme = { const App = () => { const isDark = useColorScheme() === 'dark' + const [initialized, setInitialized] = useState(false) const [settings, setSettings] = useState() const [color, setColor] = useState( isDark @@ -55,6 +56,7 @@ const App = () => { console.log(`${App.name}.useEffect:`, {gotSettings}) setSettings(gotSettings) if (gotSettings.color) setColor(gotSettings.color) + setInitialized(true) }) }, [setColor]) @@ -73,15 +75,22 @@ const App = () => { return value }, [color, isDark, settings]) + const settingsContext = useMemo( + () => ({settings, setSettings}), + [settings, setSettings], + ) + + const colorContext = useMemo(() => ({color, setColor}), [color, setColor]) + return ( - + }}> - {settings && ( - + {initialized && ( + )} diff --git a/EditWorkout.tsx b/EditWorkout.tsx index 46870b0..a435d61 100644 --- a/EditWorkout.tsx +++ b/EditWorkout.tsx @@ -3,7 +3,6 @@ import {useCallback, useRef, useState} from 'react' import {ScrollView, TextInput, View} from 'react-native' import DocumentPicker from 'react-native-document-picker' import {Button, Card, TouchableRipple} from 'react-native-paper' -import {Like} from 'typeorm' import ConfirmDialog from './ConfirmDialog' import {MARGIN, PADDING} from './constants' import {getNow, planRepo, setRepo} from './db' diff --git a/PlanItem.tsx b/PlanItem.tsx index a95b68d..aeaa826 100644 --- a/PlanItem.tsx +++ b/PlanItem.tsx @@ -6,7 +6,6 @@ import { import {useCallback, useMemo, useState} from 'react' import {GestureResponderEvent, Text} from 'react-native' import {Divider, List, Menu} from 'react-native-paper' -import {getBestSet} from './best.service' import {planRepo} from './db' import {Plan} from './plan' import {PlanPageParams} from './plan-page-params' diff --git a/Routes.tsx b/Routes.tsx index de8bb32..23b2060 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -1,4 +1,5 @@ import {createDrawerNavigator} from '@react-navigation/drawer' +import {useMemo} from 'react' import {IconButton} from 'react-native-paper' import BestPage from './BestPage' import {DrawerParamList} from './drawer-param-list' @@ -14,13 +15,16 @@ const Drawer = createDrawerNavigator() export default function Routes() { const dark = useDark() - const routes: Route[] = [ - {name: 'Home', component: HomePage, icon: 'home'}, - {name: 'Plans', component: PlanPage, icon: 'event'}, - {name: 'Best', component: BestPage, icon: 'insights'}, - {name: 'Workouts', component: WorkoutsPage, icon: 'fitness-center'}, - {name: 'Settings', component: SettingsPage, icon: 'settings'}, - ] + const routes: Route[] = useMemo( + () => [ + {name: 'Home', component: HomePage, icon: 'home'}, + {name: 'Plans', component: PlanPage, icon: 'event'}, + {name: 'Best', component: BestPage, icon: 'insights'}, + {name: 'Workouts', component: WorkoutsPage, icon: 'fitness-center'}, + {name: 'Settings', component: SettingsPage, icon: 'settings'}, + ], + [], + ) return ( [] = [ - {name: 'Rest timers', value: alarm, onChange: changeAlarmEnabled}, - {name: 'Vibrate', value: vibrate, onChange: changeVibrate}, - {name: 'Disable sound', value: noSound, onChange: changeNoSound}, - {name: 'Record notifications', value: notify, onChange: changeNotify}, - {name: 'Show images', value: images, onChange: changeImages}, - {name: 'Show unit', value: showUnit, onChange: changeUnit}, - {name: 'Show steps', value: steps, onChange: changeSteps}, - {name: 'Show date', value: showDate, onChange: changeShowDate}, - {name: 'Show sets', value: showSets, onChange: changeShowSets}, + {name: 'Rest timers', value: settings.alarm, onChange: changeAlarmEnabled}, + {name: 'Vibrate', value: settings.vibrate, onChange: changeVibrate}, + {name: 'Disable sound', value: settings.noSound, onChange: changeNoSound}, + {name: 'Notifications', value: settings.notify, onChange: changeNotify}, + {name: 'Show images', value: settings.images, onChange: changeImages}, + {name: 'Show unit', value: settings.showUnit, onChange: changeUnit}, + {name: 'Show steps', value: settings.steps, onChange: changeSteps}, + {name: 'Show date', value: settings.showDate, onChange: changeShowDate}, + {name: 'Show sets', value: settings.showSets, onChange: changeShowSets}, ] const changeTheme = useCallback( @@ -177,6 +164,27 @@ export default function SettingsPage() { [settings, setSettings], ) + const sound = useMemo(() => { + if (!settings.sound) return null + const split = settings.sound.split('/') + return split.pop() + }, [settings.sound]) + + const theme = useMemo(() => { + if (!'theme'.includes(term.toLowerCase())) return null + return ( + + + + + + ) + }, [term, color, changeTheme, settings.theme]) + return ( <> @@ -195,17 +203,7 @@ export default function SettingsPage() { {input.name} ))} - {'theme'.includes(term.toLowerCase()) && ( - - - - - - )} + {theme} {'color'.includes(term.toLowerCase()) && ( - Alarm sound - {sound - ? ': ' + sound.split('/')[sound.split('/').length - 1] - : null} + Alarm sound: {sound} )} diff --git a/StartPlan.tsx b/StartPlan.tsx index 53fac07..3aff1d9 100644 --- a/StartPlan.tsx +++ b/StartPlan.tsx @@ -86,7 +86,7 @@ export default function StartPlan() { useFocusEffect( useCallback(() => { refresh().then(newCounts => select(0, newCounts)) - }, [refresh]), + }, [refresh, select]), ) const handleSubmit = async () => { diff --git a/ViewBest.tsx b/ViewBest.tsx index 6dbdb4b..5c0d8ae 100644 --- a/ViewBest.tsx +++ b/ViewBest.tsx @@ -56,9 +56,9 @@ export default function ViewBest() { builder .addSelect('MAX(weight / (1.0278 - 0.0278 * reps))', 'weight') .getRawMany() - .then(weights => { - console.log({weights}) - setWeights(weights) + .then(newWeights => { + console.log({weights: newWeights}) + setWeights(newWeights) }) } }, [params.best.name, metric, period]) diff --git a/babel.config.js b/babel.config.js index edd6299..f67d31d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -2,8 +2,8 @@ module.exports = { presets: ['module:metro-react-native-babel-preset'], plugins: [ '@babel/plugin-transform-flow-strip-types', - ['@babel/plugin-proposal-decorators', { legacy: true }], - ['@babel/plugin-proposal-class-properties', { loose: true }], + ['@babel/plugin-proposal-decorators', {legacy: true}], + ['@babel/plugin-proposal-class-properties', {loose: true}], 'react-native-reanimated/plugin', 'react-native-paper/babel', ], @@ -12,4 +12,4 @@ module.exports = { plugins: ['transform-remove-console'], }, }, -}; +} diff --git a/jest.config.js b/jest.config.js index a561637..619f6bf 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,4 +11,4 @@ module.exports = { './node_modules/react-native-gesture-handler/jestSetup', './jestSetup.ts', ], -}; +} diff --git a/metro.config.js b/metro.config.js index e91aba9..c81b3ca 100644 --- a/metro.config.js +++ b/metro.config.js @@ -14,4 +14,4 @@ module.exports = { }, }), }, -}; +} diff --git a/use-settings.ts b/use-settings.ts index 2f72009..f955a55 100644 --- a/use-settings.ts +++ b/use-settings.ts @@ -2,19 +2,19 @@ import React, {useContext} from 'react' import Settings from './settings' export const defaultSettings: Settings = { - alarm: 0, + alarm: true, color: '', date: '', - images: 1, - notify: 0, - showDate: 0, - showSets: 1, - showUnit: 1, + images: true, + notify: false, + showDate: false, + showSets: true, + showUnit: true, sound: '', - steps: 0, + steps: false, theme: 'system', - vibrate: 1, - noSound: 0, + vibrate: true, + noSound: false, } export const SettingsContext = React.createContext<{ From 13ca9cef3e48be2077eb3c2cc2b5b676f68c6cc2 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Mon, 31 Oct 2022 21:00:53 +1300 Subject: [PATCH 34/78] Reword "maximum" as "target" for sets There isn't any restriction involved in the sets for each workout, it's more like a guide. --- SettingsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SettingsPage.tsx b/SettingsPage.tsx index e992167..835e3d4 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -121,8 +121,8 @@ export default function SettingsPage() { const changeShowSets = useCallback( (enabled: boolean) => { update(enabled, 'showSets') - if (enabled) toast('Show maximum sets for workouts.', 4000) - else toast('Stopped showing maximum sets for workouts.', 4000) + if (enabled) toast('Show target sets for workouts.', 4000) + else toast('Stopped showing target sets for workouts.', 4000) }, [toast, update], ) From 1a53fa324b8bfdec1ba0ace64ec27b28f6441cde Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Mon, 31 Oct 2022 21:32:33 +1300 Subject: [PATCH 35/78] Remove redundant Color context Settings already stores the color set by the user. --- App.tsx | 70 ++++++++++++++++++++++------------------------ Chart.tsx | 6 ++-- MassiveFab.tsx | 12 ++++---- SettingsPage.tsx | 25 +++++++++-------- StartPlanItem.tsx | 7 ++--- Switch.tsx | 13 ++++----- color.ts | 11 -------- mock-providers.tsx | 19 +++++-------- use-settings.ts | 3 +- 9 files changed, 76 insertions(+), 90 deletions(-) delete mode 100644 color.ts diff --git a/App.tsx b/App.tsx index 573238e..72586fd 100644 --- a/App.tsx +++ b/App.tsx @@ -11,14 +11,13 @@ import { Provider as PaperProvider, } from 'react-native-paper' import MaterialIcon from 'react-native-vector-icons/MaterialIcons' -import {Color} from './color' import {lightColors} from './colors' import {AppDataSource} from './data-source' import {settingsRepo} from './db' import MassiveSnack from './MassiveSnack' import Routes from './Routes' import Settings from './settings' -import {SettingsContext} from './use-settings' +import {defaultSettings, SettingsContext} from './use-settings' export const CombinedDefaultTheme = { ...NavigationDefaultTheme, @@ -43,61 +42,60 @@ export const CombinedDarkTheme = { const App = () => { const isDark = useColorScheme() === 'dark' const [initialized, setInitialized] = useState(false) - const [settings, setSettings] = useState() - const [color, setColor] = useState( - isDark - ? CombinedDarkTheme.colors.primary.toUpperCase() - : CombinedDefaultTheme.colors.primary.toUpperCase(), - ) + const [settings, setSettings] = useState({ + ...defaultSettings, + color: isDark + ? CombinedDarkTheme.colors.primary + : CombinedDefaultTheme.colors.primary, + }) useEffect(() => { AppDataSource.initialize().then(async () => { const gotSettings = await settingsRepo.findOne({where: {}}) console.log(`${App.name}.useEffect:`, {gotSettings}) setSettings(gotSettings) - if (gotSettings.color) setColor(gotSettings.color) setInitialized(true) }) - }, [setColor]) + }, []) const theme = useMemo(() => { - const darkTheme = { - ...CombinedDarkTheme, - colors: {...CombinedDarkTheme.colors, primary: color}, - } - const lightTheme = { - ...CombinedDefaultTheme, - colors: {...CombinedDefaultTheme.colors, primary: color}, - } + const darkTheme = settings?.color + ? { + ...CombinedDarkTheme, + colors: {...CombinedDarkTheme.colors, primary: settings.color}, + } + : CombinedDarkTheme + const lightTheme = settings?.color + ? { + ...CombinedDefaultTheme, + colors: {...CombinedDefaultTheme.colors, primary: settings.color}, + } + : CombinedDefaultTheme let value = isDark ? darkTheme : lightTheme if (settings?.theme === 'dark') value = darkTheme else if (settings?.theme === 'light') value = lightTheme return value - }, [color, isDark, settings]) + }, [isDark, settings?.theme, settings?.color]) const settingsContext = useMemo( () => ({settings, setSettings}), [settings, setSettings], ) - const colorContext = useMemo(() => ({color, setColor}), [color, setColor]) - return ( - - }}> - - - {initialized && ( - - - - )} - - - - + }}> + + + {initialized && ( + + + + )} + + + ) } diff --git a/Chart.tsx b/Chart.tsx index ae27db0..75fe5ec 100644 --- a/Chart.tsx +++ b/Chart.tsx @@ -1,8 +1,8 @@ +import {useTheme} from '@react-navigation/native' import * as shape from 'd3-shape' import {View} from 'react-native' import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts' import {CombinedDarkTheme, CombinedDefaultTheme} from './App' -import {useColor} from './color' import {MARGIN, PADDING} from './constants' import GymSet from './gym-set' import useDark from './use-dark' @@ -18,7 +18,7 @@ export default function Chart({ xFormat: (value: any, index: number) => string yFormat: (value: any) => string }) { - const {color} = useColor() + const {colors} = useTheme() const dark = useDark() const axesSvg = { fontSize: 10, @@ -46,7 +46,7 @@ export default function Chart({ contentInset={verticalContentInset} curve={shape.curveBasis} svg={{ - stroke: color, + stroke: colors.primary, }}> diff --git a/MassiveFab.tsx b/MassiveFab.tsx index 0991091..7f45f1d 100644 --- a/MassiveFab.tsx +++ b/MassiveFab.tsx @@ -1,11 +1,13 @@ import {ComponentProps} from 'react' -import {FAB} from 'react-native-paper' -import {useColor} from './color' +import {FAB, useTheme} from 'react-native-paper' import {lightColors} from './colors' export default function MassiveFab(props: Partial>) { - const {color} = useColor() - const fabColor = lightColors.map(lightColor => lightColor.hex).includes(color) + const {colors} = useTheme() + + const fabColor = lightColors + .map(lightColor => lightColor.hex) + .includes(colors.primary) ? 'black' : undefined @@ -17,7 +19,7 @@ export default function MassiveFab(props: Partial>) { position: 'absolute', right: 10, bottom: 10, - backgroundColor: color, + backgroundColor: colors.primary, }} {...props} /> diff --git a/SettingsPage.tsx b/SettingsPage.tsx index 835e3d4..748d846 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -4,7 +4,6 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {NativeModules, ScrollView} from 'react-native' import DocumentPicker from 'react-native-document-picker' import {Button} from 'react-native-paper' -import {useColor} from './color' import {darkColors, lightColors} from './colors' import ConfirmDialog from './ConfirmDialog' import {MARGIN} from './constants' @@ -22,7 +21,6 @@ export default function SettingsPage() { const [ignoring, setIgnoring] = useState(false) const [term, setTerm] = useState('') const {settings, setSettings} = useSettings() - const {color, setColor} = useColor() const {toast} = useSnackbar() useEffect(() => { @@ -174,8 +172,8 @@ export default function SettingsPage() { if (!'theme'.includes(term.toLowerCase())) return null return ( @@ -183,7 +181,12 @@ export default function SettingsPage() { ) - }, [term, color, changeTheme, settings.theme]) + }, [term, settings.color, changeTheme, settings.theme]) + + const changeColor = useCallback((value: string) => { + setSettings({...settings, color: value}) + settingsRepo.update({}, {color: value}) + }, []) return ( <> @@ -206,10 +209,10 @@ export default function SettingsPage() { {theme} {'color'.includes(term.toLowerCase()) && ( setColor(value)}> + style={{color: settings.color, marginTop: -10}} + dropdownIconColor={settings.color} + selectedValue={settings.color} + onValueChange={changeColor}> {lightColors.concat(darkColors).map(colorOption => ( diff --git a/StartPlanItem.tsx b/StartPlanItem.tsx index cce51ce..e51fd29 100644 --- a/StartPlanItem.tsx +++ b/StartPlanItem.tsx @@ -1,7 +1,6 @@ import React, {useCallback, useState} from 'react' import {GestureResponderEvent, ListRenderItemInfo, View} from 'react-native' -import {List, Menu, RadioButton} from 'react-native-paper' -import {useColor} from './color' +import {List, Menu, RadioButton, useTheme} from 'react-native-paper' import CountMany from './count-many' import {setRepo} from './db' @@ -13,7 +12,7 @@ interface Props extends ListRenderItemInfo { export default function StartPlanItem(props: Props) { const {index, item, onSelect, selected, onUndo} = props - const {color} = useColor() + const {colors} = useTheme() const [anchor, setAnchor] = useState({x: 0, y: 0}) const [showMenu, setShowMenu] = useState(false) @@ -48,7 +47,7 @@ export default function StartPlanItem(props: Props) { onPress={() => onSelect(index)} value={index.toString()} status={selected === index ? 'checked' : 'unchecked'} - color={color} + color={colors.primary} /> )} diff --git a/Switch.tsx b/Switch.tsx index 11f6f5d..2c86b50 100644 --- a/Switch.tsx +++ b/Switch.tsx @@ -1,8 +1,7 @@ import {useMemo} from 'react' import {Pressable} from 'react-native' -import {Switch as PaperSwitch, Text} from 'react-native-paper' +import {Switch as PaperSwitch, Text, useTheme} from 'react-native-paper' import {CombinedDarkTheme, CombinedDefaultTheme} from './App' -import {useColor} from './color' import {colorShade} from './colors' import {MARGIN} from './constants' import useDark from './use-dark' @@ -18,20 +17,20 @@ export default function Switch({ onPress: () => void children: string }) { - const {color} = useColor() + const {colors} = useTheme() const dark = useDark() const track = useMemo(() => { if (dark) return { false: CombinedDarkTheme.colors.placeholder, - true: colorShade(color, -40), + true: colorShade(colors.primary, -40), } return { false: CombinedDefaultTheme.colors.placeholder, - true: colorShade(color, -40), + true: colorShade(colors.primary, -40), } - }, [dark, color]) + }, [dark, colors.primary]) return ( {}, -}) - -export const useColor = () => { - const context = useContext(Color) - return context -} diff --git a/mock-providers.tsx b/mock-providers.tsx index 60af785..386bc92 100644 --- a/mock-providers.tsx +++ b/mock-providers.tsx @@ -1,13 +1,10 @@ import {NavigationContainer} from '@react-navigation/native' import React from 'react' import {Provider as PaperProvider} from 'react-native-paper' -import {Color} from './color' -import {lightColors} from './colors' import MassiveSnack from './MassiveSnack' import {defaultSettings, SettingsContext} from './use-settings' import MaterialIcon from 'react-native-vector-icons/MaterialIcons' -const color = lightColors[0].hex export const setColor = jest.fn() const settings = defaultSettings export const setSettings = jest.fn() @@ -17,13 +14,11 @@ export const MockProviders = ({ }: { children: JSX.Element | JSX.Element[] }) => ( - - }}> - - - {children} - - - - + }}> + + + {children} + + + ) diff --git a/use-settings.ts b/use-settings.ts index f955a55..10f2caa 100644 --- a/use-settings.ts +++ b/use-settings.ts @@ -1,9 +1,10 @@ import React, {useContext} from 'react' +import {darkColors} from './colors' import Settings from './settings' export const defaultSettings: Settings = { alarm: true, - color: '', + color: darkColors[0].hex, date: '', images: true, notify: false, From 3c4bba3f85ec5471bc84a1946c215731f4aed697 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 12:29:54 +1300 Subject: [PATCH 36/78] Fix infinite refreshing on first load of StartPlan --- StartPlan.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/StartPlan.tsx b/StartPlan.tsx index 3aff1d9..5d8ba35 100644 --- a/StartPlan.tsx +++ b/StartPlan.tsx @@ -86,7 +86,8 @@ export default function StartPlan() { useFocusEffect( useCallback(() => { refresh().then(newCounts => select(0, newCounts)) - }, [refresh, select]), + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [refresh]), ) const handleSubmit = async () => { From f56f0063c42d978facdb11be165728cb8a1f7bba Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 12:30:06 +1300 Subject: [PATCH 37/78] Turn off some eslint rules --- .eslintrc.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 6b1f939..b31c8dc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,6 +10,10 @@ module.exports = { '@typescript-eslint/no-shadow': ['error'], 'no-shadow': 'off', 'no-undef': 'off', + semi: 'off', + curly: 'off', + 'react/react-in-jsx-scope': 'off', + 'react-native/no-inline-styles': 'off', }, }, ], From ace327ecad12e1daa0c2b3a81078c04f9ac20767 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 12:30:31 +1300 Subject: [PATCH 38/78] Remove vestiges of react-native-sqlite-storage --- db.ts | 5 ----- index.js | 1 - 2 files changed, 6 deletions(-) diff --git a/db.ts b/db.ts index 41d7b52..8b90b3d 100644 --- a/db.ts +++ b/db.ts @@ -1,13 +1,8 @@ -import {enablePromise, SQLiteDatabase} from 'react-native-sqlite-storage' import {AppDataSource} from './data-source' import GymSet from './gym-set' import {Plan} from './plan' import Settings from './settings' -enablePromise(true) - -export let db: SQLiteDatabase - export const setRepo = AppDataSource.manager.getRepository(GymSet) export const planRepo = AppDataSource.manager.getRepository(Plan) export const settingsRepo = AppDataSource.manager.getRepository(Settings) diff --git a/index.js b/index.js index 7b2f6c7..870f5b8 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ import {AppRegistry} from 'react-native' import 'react-native-gesture-handler' -import 'react-native-sqlite-storage' import App from './App' import {name as appName} from './app.json' From 49b5eb48c66e4c785307c4157fe5a09c6ed0e7cd Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 15:55:37 +1300 Subject: [PATCH 39/78] Refactor MassiveSnack Instead of using a context for the whole app use DeviceEventEmitter with root state. This will probably improve performance, since I think the react context was re-rendering the entire DOM tree. --- App.tsx | 34 +++++++++++++++------ DrawerMenu.tsx | 20 ++++++------- EditSet.tsx | 12 +++----- EditWorkout.tsx | 7 ++--- MassiveSnack.tsx | 49 ------------------------------ Routes.tsx | 5 ++-- SetForm.tsx | 11 +++---- SettingsPage.tsx | 78 ++++++++++++++++++++++++++---------------------- StartPlan.tsx | 22 ++++++-------- toast.ts | 7 +++++ 10 files changed, 109 insertions(+), 136 deletions(-) delete mode 100644 MassiveSnack.tsx create mode 100644 toast.ts diff --git a/App.tsx b/App.tsx index 72586fd..889c03b 100644 --- a/App.tsx +++ b/App.tsx @@ -4,19 +4,20 @@ import { NavigationContainer, } from '@react-navigation/native' import {useEffect, useMemo, useState} from 'react' -import {useColorScheme} from 'react-native' +import {DeviceEventEmitter, useColorScheme} from 'react-native' import { DarkTheme as PaperDarkTheme, DefaultTheme as PaperDefaultTheme, Provider as PaperProvider, + Snackbar, } from 'react-native-paper' import MaterialIcon from 'react-native-vector-icons/MaterialIcons' import {lightColors} from './colors' import {AppDataSource} from './data-source' import {settingsRepo} from './db' -import MassiveSnack from './MassiveSnack' import Routes from './Routes' import Settings from './settings' +import {TOAST} from './toast' import {defaultSettings, SettingsContext} from './use-settings' export const CombinedDefaultTheme = { @@ -48,6 +49,7 @@ const App = () => { ? CombinedDarkTheme.colors.primary : CombinedDefaultTheme.colors.primary, }) + const [snackbar, setSnackbar] = useState('') useEffect(() => { AppDataSource.initialize().then(async () => { @@ -56,6 +58,10 @@ const App = () => { setSettings(gotSettings) setInitialized(true) }) + DeviceEventEmitter.addListener(TOAST, ({value}: {value: string}) => { + console.log(`${Routes.name}.toast:`, {value}) + setSnackbar(value) + }) }, []) const theme = useMemo(() => { @@ -87,14 +93,24 @@ const App = () => { theme={theme} settings={{icon: props => }}> - - {initialized && ( - - - - )} - + {initialized && ( + + + + )} + + setSnackbar('')} + visible={!!snackbar} + action={{ + label: 'Close', + onPress: () => setSnackbar(''), + color: theme.colors.primary, + }}> + {snackbar} + ) } diff --git a/DrawerMenu.tsx b/DrawerMenu.tsx index 8a7ca82..0189025 100644 --- a/DrawerMenu.tsx +++ b/DrawerMenu.tsx @@ -8,8 +8,8 @@ import {AppDataSource} from './data-source' import {planRepo} from './db' import {DrawerParamList} from './drawer-param-list' import GymSet from './gym-set' -import {useSnackbar} from './MassiveSnack' import {Plan} from './plan' +import {toast} from './toast' import useDark from './use-dark' import {write} from './write' @@ -20,7 +20,6 @@ const setRepo = AppDataSource.manager.getRepository(GymSet) export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { const [showMenu, setShowMenu] = useState(false) const [showRemove, setShowRemove] = useState(false) - const {toast} = useSnackbar() const {reset} = useNavigation>() const dark = useDark() @@ -65,7 +64,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { console.log(`${DrawerMenu.name}.uploadSets:`, file.length) const lines = file.split('\n') console.log(lines[0]) - if (!setFields.includes(lines[0])) return toast('Invalid csv.', 3000) + if (!setFields.includes(lines[0])) return toast('Invalid csv.') const values = lines .slice(1) .filter(line => line) @@ -92,21 +91,22 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { sets: +sets, minutes: +minutes, seconds: +seconds, + image: '', } return set }) console.log(`${DrawerMenu.name}.uploadSets:`, {values}) await setRepo.insert(values) - toast('Data imported.', 3000) + toast('Data imported.') reset({index: 0, routes: [{name}]}) - }, [reset, name, toast]) + }, [reset, name]) const uploadPlans = useCallback(async () => { const result = await DocumentPicker.pickSingle() const file = await FileSystem.readFile(result.uri) console.log(`${DrawerMenu.name}.uploadPlans:`, file.length) const lines = file.split('\n') - if (lines[0] != planFields) return toast('Invalid csv.', 3000) + if (lines[0] !== planFields) return toast('Invalid csv.') const values = file .split('\n') .slice(1) @@ -122,8 +122,8 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { return plan }) await planRepo.insert(values) - toast('Data imported.', 3000) - }, [toast]) + toast('Data imported.') + }, []) const upload = useCallback(async () => { setShowMenu(false) @@ -137,9 +137,9 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { setShowRemove(false) if (name === 'Home') await setRepo.delete({}) else if (name === 'Plans') await planRepo.delete({}) - toast('All data has been deleted.', 4000) + toast('All data has been deleted.') reset({index: 0, routes: [{name}]}) - }, [reset, name, toast]) + }, [reset, name]) if (name === 'Home' || name === 'Plans') return ( diff --git a/EditSet.tsx b/EditSet.tsx index 13e7000..a3f8f6a 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -2,19 +2,18 @@ import {RouteProp, useNavigation, useRoute} from '@react-navigation/native' import {useCallback} from 'react' import {NativeModules, View} from 'react-native' import {PADDING} from './constants' -import {getNow, setRepo} from './db' +import {setRepo} from './db' import GymSet from './gym-set' import {HomePageParams} from './home-page-params' -import {useSnackbar} from './MassiveSnack' import SetForm from './SetForm' import StackHeader from './StackHeader' +import {toast} from './toast' import {useSettings} from './use-settings' export default function EditSet() { const {params} = useRoute>() const {set} = params const navigation = useNavigation() - const {toast} = useSnackbar() const {settings} = useSettings() const startTimer = useCallback( @@ -35,9 +34,6 @@ export default function EditSet() { const add = useCallback( async (value: GymSet) => { startTimer(value.name) - const [{now}] = await getNow() - value.created = now - value.hidden = false console.log(`${EditSet.name}.add`, {set: value}) const result = await setRepo.save(value) console.log({result}) @@ -46,9 +42,9 @@ export default function EditSet() { value.weight > set.weight || (value.reps > set.reps && value.weight === set.weight) ) - toast("Great work King! That's a new record.", 3000) + toast("Great work King! That's a new record.") }, - [startTimer, set, toast, settings], + [startTimer, set, settings], ) const save = useCallback( diff --git a/EditWorkout.tsx b/EditWorkout.tsx index a435d61..006bfe9 100644 --- a/EditWorkout.tsx +++ b/EditWorkout.tsx @@ -7,8 +7,8 @@ import ConfirmDialog from './ConfirmDialog' import {MARGIN, PADDING} from './constants' import {getNow, planRepo, setRepo} from './db' import MassiveInput from './MassiveInput' -import {useSnackbar} from './MassiveSnack' import StackHeader from './StackHeader' +import {toast} from './toast' import {useSettings} from './use-settings' import {WorkoutsPageParams} from './WorkoutsPage' @@ -26,7 +26,6 @@ export default function EditWorkout() { params.value.seconds?.toString() ?? '30', ) const [sets, setSets] = useState(params.value.sets?.toString() ?? '3') - const {toast} = useSnackbar() const navigation = useNavigation() const setsRef = useRef(null) const stepsRef = useRef(null) @@ -94,13 +93,13 @@ export default function EditWorkout() { const handleName = (value: string) => { setName(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000) + toast('Commas and single quotes would break CSV exports') } const handleSteps = (value: string) => { setSteps(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000) + toast('Commas and single quotes would break CSV exports') } const submitName = () => { diff --git a/MassiveSnack.tsx b/MassiveSnack.tsx deleted file mode 100644 index 3613189..0000000 --- a/MassiveSnack.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import {createContext, useContext, useState} from 'react' -import {Snackbar} from 'react-native-paper' -import {CombinedDarkTheme, CombinedDefaultTheme} from './App' -import useDark from './use-dark' - -export const SnackbarContext = createContext<{ - toast: (value: string, timeout: number) => void -}>({toast: () => null}) - -export const useSnackbar = () => { - return useContext(SnackbarContext) -} - -export default function MassiveSnack({ - children, -}: { - children?: JSX.Element[] | JSX.Element -}) { - const [snackbar, setSnackbar] = useState('') - const [timeoutId, setTimeoutId] = useState(0) - const dark = useDark() - - const toast = (value: string, timeout: number) => { - setSnackbar(value) - clearTimeout(timeoutId) - const id = setTimeout(() => setSnackbar(''), timeout) - setTimeoutId(id) - } - - return ( - <> - - {children} - - setSnackbar('')} - visible={!!snackbar} - action={{ - label: 'Close', - onPress: () => setSnackbar(''), - color: dark - ? CombinedDarkTheme.colors.background - : CombinedDefaultTheme.colors.background, - }}> - {snackbar} - - - ) -} diff --git a/Routes.tsx b/Routes.tsx index 23b2060..b93685d 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -1,6 +1,7 @@ import {createDrawerNavigator} from '@react-navigation/drawer' -import {useMemo} from 'react' -import {IconButton} from 'react-native-paper' +import {useEffect, useMemo, useState} from 'react' +import {DeviceEventEmitter} from 'react-native' +import {IconButton, Snackbar, useTheme} from 'react-native-paper' import BestPage from './BestPage' import {DrawerParamList} from './drawer-param-list' import HomePage from './HomePage' diff --git a/SetForm.tsx b/SetForm.tsx index f07a4a6..0658d78 100644 --- a/SetForm.tsx +++ b/SetForm.tsx @@ -4,10 +4,10 @@ import DocumentPicker from 'react-native-document-picker' import {Button, Card, TouchableRipple} from 'react-native-paper' import ConfirmDialog from './ConfirmDialog' import {MARGIN} from './constants' -import {setRepo} from './db' +import {getNow, setRepo} from './db' import GymSet from './gym-set' import MassiveInput from './MassiveInput' -import {useSnackbar} from './MassiveSnack' +import {toast} from './toast' import {useSettings} from './use-settings' export default function SetForm({ @@ -28,7 +28,6 @@ export default function SetForm({ end: set.reps.toString().length, }) const [removeImage, setRemoveImage] = useState(false) - const {toast} = useSnackbar() const {settings} = useSettings() const weightRef = useRef(null) const repsRef = useRef(null) @@ -42,8 +41,10 @@ export default function SetForm({ image = await setRepo.findOne({where: {name}}).then(s => s?.image) console.log(`${SetForm.name}.handleSubmit:`, {image}) + const [{now}] = await getNow() save({ name, + created: now, reps: Number(reps), weight: Number(weight), id: set.id, @@ -59,13 +60,13 @@ export default function SetForm({ const handleName = (value: string) => { setName(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000) + toast('Commas and single quotes would break CSV exports') } const handleUnit = (value: string) => { setUnit(value.replace(/,|'/g, '')) if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000) + toast('Commas and single quotes would break CSV exports') } const changeImage = useCallback(async () => { diff --git a/SettingsPage.tsx b/SettingsPage.tsx index 748d846..b4e10eb 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -1,7 +1,7 @@ import {Picker} from '@react-native-picker/picker' import {useFocusEffect} from '@react-navigation/native' import {useCallback, useEffect, useMemo, useState} from 'react' -import {NativeModules, ScrollView} from 'react-native' +import {DeviceEventEmitter, NativeModules, ScrollView} from 'react-native' import DocumentPicker from 'react-native-document-picker' import {Button} from 'react-native-paper' import {darkColors, lightColors} from './colors' @@ -10,10 +10,10 @@ import {MARGIN} from './constants' import {settingsRepo} from './db' import DrawerHeader from './DrawerHeader' import Input from './input' -import {useSnackbar} from './MassiveSnack' import Page from './Page' import Settings from './settings' import Switch from './Switch' +import {toast} from './toast' import {useSettings} from './use-settings' export default function SettingsPage() { @@ -21,7 +21,6 @@ export default function SettingsPage() { const [ignoring, setIgnoring] = useState(false) const [term, setTerm] = useState('') const {settings, setSettings} = useSettings() - const {toast} = useSnackbar() useEffect(() => { console.log(`${SettingsPage.name}.useEffect:`, {settings}) @@ -43,21 +42,25 @@ export default function SettingsPage() { const changeAlarmEnabled = useCallback( (enabled: boolean) => { - if (enabled) toast('Timers will now run after each set.', 4000) - else toast('Stopped timers running after each set.', 4000) + if (enabled) + DeviceEventEmitter.emit('toast', { + value: 'Timers will now run after each set', + timeout: 4000, + }) + else toast('Stopped timers running after each set.') if (enabled && !ignoring) setBattery(true) update(enabled, 'alarm') }, - [setBattery, ignoring, toast, update], + [setBattery, ignoring, update], ) const changeVibrate = useCallback( (enabled: boolean) => { - if (enabled) toast('When a timer completes, vibrate your phone.', 4000) - else toast('Stop vibrating at the end of timers.', 4000) + if (enabled) toast('When a timer completes, vibrate your phone.') + else toast('Stop vibrating at the end of timers.') update(enabled, 'vibrate') }, - [toast, update], + [update], ) const changeSound = useCallback(async () => { @@ -68,70 +71,70 @@ export default function SettingsPage() { if (!fileCopyUri) return settingsRepo.update({}, {sound: fileCopyUri}) setSettings({...settings, sound: fileCopyUri}) - toast('This song will now play after rest timers complete.', 4000) - }, [toast, setSettings, settings]) + toast('This song will now play after rest timers complete.') + }, [setSettings, settings]) const changeNotify = useCallback( (enabled: boolean) => { update(enabled, 'notify') - if (enabled) toast('Show when a set is a new record.', 4000) - else toast('Stopped showing notifications for new records.', 4000) + if (enabled) toast('Show when a set is a new record.') + else toast('Stopped showing notifications for new records.') }, - [toast, update], + [update], ) const changeImages = useCallback( (enabled: boolean) => { update(enabled, 'images') - if (enabled) toast('Show images for sets.', 4000) - else toast('Stopped showing images for sets.', 4000) + if (enabled) toast('Show images for sets.') + else toast('Stopped showing images for sets.') }, - [toast, update], + [update], ) const changeUnit = useCallback( (enabled: boolean) => { update(enabled, 'showUnit') - if (enabled) toast('Show option to select unit for sets.', 4000) - else toast('Hid unit option for sets.', 4000) + if (enabled) toast('Show option to select unit for sets.') + else toast('Hid unit option for sets.') }, - [toast, update], + [update], ) const changeSteps = useCallback( (enabled: boolean) => { update(enabled, 'steps') - if (enabled) toast('Show steps for a workout.', 4000) - else toast('Stopped showing steps for workouts.', 4000) + if (enabled) toast('Show steps for a workout.') + else toast('Stopped showing steps for workouts.') }, - [toast, update], + [update], ) const changeShowDate = useCallback( (enabled: boolean) => { update(enabled, 'showDate') - if (enabled) toast('Show date for sets by default.', 4000) - else toast('Stopped showing date for sets by default.', 4000) + if (enabled) toast('Show date for sets by default.') + else toast('Stopped showing date for sets by default.') }, - [toast, update], + [update], ) const changeShowSets = useCallback( (enabled: boolean) => { update(enabled, 'showSets') - if (enabled) toast('Show target sets for workouts.', 4000) - else toast('Stopped showing target sets for workouts.', 4000) + if (enabled) toast('Show target sets for workouts.') + else toast('Stopped showing target sets for workouts.') }, - [toast, update], + [update], ) const changeNoSound = useCallback( (enabled: boolean) => { update(enabled, 'noSound') - if (enabled) toast('Disable sound on rest timer alarms.', 4000) - else toast('Enabled sound for rest timer alarms.', 4000) + if (enabled) toast('Disable sound on rest timer alarms.') + else toast('Enabled sound for rest timer alarms.') }, - [toast, update], + [update], ) const switches: Input[] = [ @@ -183,10 +186,13 @@ export default function SettingsPage() { ) }, [term, settings.color, changeTheme, settings.theme]) - const changeColor = useCallback((value: string) => { - setSettings({...settings, color: value}) - settingsRepo.update({}, {color: value}) - }, []) + const changeColor = useCallback( + (value: string) => { + setSettings({...settings, color: value}) + settingsRepo.update({}, {color: value}) + }, + [setSettings, settings], + ) return ( <> diff --git a/StartPlan.tsx b/StartPlan.tsx index 5d8ba35..2d87dac 100644 --- a/StartPlan.tsx +++ b/StartPlan.tsx @@ -10,11 +10,11 @@ import {AppDataSource} from './data-source' import {getNow, setRepo} from './db' import GymSet from './gym-set' import MassiveInput from './MassiveInput' -import {useSnackbar} from './MassiveSnack' import {PlanPageParams} from './plan-page-params' import SetForm from './SetForm' import StackHeader from './StackHeader' import StartPlanItem from './StartPlanItem' +import {toast} from './toast' import {useSettings} from './use-settings' export default function StartPlan() { @@ -23,7 +23,6 @@ export default function StartPlan() { const [reps, setReps] = useState('') const [weight, setWeight] = useState('') const [unit, setUnit] = useState('kg') - const {toast} = useSnackbar() const [minutes, setMinutes] = useState(3) const [seconds, setSeconds] = useState(30) const [best, setBest] = useState() @@ -109,9 +108,9 @@ export default function StartPlan() { settings.notify && (+weight > best.weight || (+reps > best.reps && +weight === best.weight)) ) - toast("Great work King! That's a new record.", 5000) - else if (settings.alarm) toast('Resting...', 3000) - else toast('Added set', 3000) + toast("Great work King! That's a new record.") + else if (settings.alarm) toast('Resting...') + else toast('Added set') if (!settings.alarm) return const milliseconds = Number(minutes) * 60 * 1000 + Number(seconds) * 1000 const {vibrate, sound, noSound} = settings @@ -119,14 +118,11 @@ export default function StartPlan() { NativeModules.AlarmModule.timer(...args) } - const handleUnit = useCallback( - (value: string) => { - setUnit(value.replace(/,|'/g, '')) - if (value.match(/,|'/)) - toast('Commas and single quotes would break CSV exports', 6000) - }, - [toast], - ) + const handleUnit = useCallback((value: string) => { + setUnit(value.replace(/,|'/g, '')) + if (value.match(/,|'/)) + toast('Commas and single quotes would break CSV exports') + }, []) return ( <> diff --git a/toast.ts b/toast.ts new file mode 100644 index 0000000..8227283 --- /dev/null +++ b/toast.ts @@ -0,0 +1,7 @@ +import {DeviceEventEmitter} from 'react-native' + +export const TOAST = 'toast' + +export function toast(value: string) { + DeviceEventEmitter.emit(TOAST, {value}) +} From fadab1f30be655820f20725724b20436482f4b38 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 16:06:25 +1300 Subject: [PATCH 40/78] Fix colors of pickers in SettingsPage --- Select.tsx | 24 ++++++++++++++++++++++++ SettingsPage.tsx | 33 ++++++++++++--------------------- 2 files changed, 36 insertions(+), 21 deletions(-) create mode 100644 Select.tsx diff --git a/Select.tsx b/Select.tsx new file mode 100644 index 0000000..da13817 --- /dev/null +++ b/Select.tsx @@ -0,0 +1,24 @@ +import {Picker} from '@react-native-picker/picker' +import {useTheme} from 'react-native-paper' + +export default function Select({ + value, + onChange, + children, +}: { + value: string + onChange: (value: string) => void + children: JSX.Element | JSX.Element[] +}) { + const {colors} = useTheme() + + return ( + + {children} + + ) +} diff --git a/SettingsPage.tsx b/SettingsPage.tsx index b4e10eb..faeb1d8 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -11,9 +11,11 @@ import {settingsRepo} from './db' import DrawerHeader from './DrawerHeader' import Input from './input' import Page from './Page' +import Select from './Select' import Settings from './settings' import Switch from './Switch' import {toast} from './toast' +import useDark from './use-dark' import {useSettings} from './use-settings' export default function SettingsPage() { @@ -21,6 +23,7 @@ export default function SettingsPage() { const [ignoring, setIgnoring] = useState(false) const [term, setTerm] = useState('') const {settings, setSettings} = useSettings() + const dark = useDark() useEffect(() => { console.log(`${SettingsPage.name}.useEffect:`, {settings}) @@ -168,23 +171,19 @@ export default function SettingsPage() { const sound = useMemo(() => { if (!settings.sound) return null const split = settings.sound.split('/') - return split.pop() + return ': ' + split.pop() }, [settings.sound]) const theme = useMemo(() => { if (!'theme'.includes(term.toLowerCase())) return null return ( - + ) - }, [term, settings.color, changeTheme, settings.theme]) + }, [term, changeTheme, settings.theme]) const changeColor = useCallback( (value: string) => { @@ -214,11 +213,7 @@ export default function SettingsPage() { ))} {theme} {'color'.includes(term.toLowerCase()) && ( - + )} {'date format'.includes(term.toLowerCase()) && ( - + )} {'alarm sound'.includes(term.toLowerCase()) && ( )} From 139d75493e1d3dffb002130ad6b5d0ce149024d1 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 16:08:02 +1300 Subject: [PATCH 41/78] Memoize action in App.tsx --- App.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/App.tsx b/App.tsx index 889c03b..dade68d 100644 --- a/App.tsx +++ b/App.tsx @@ -88,6 +88,15 @@ const App = () => { [settings, setSettings], ) + const action = useMemo( + () => ({ + label: 'Close', + onPress: () => setSnackbar(''), + color: theme.colors.primary, + }), + [theme.colors.primary], + ) + return ( { duration={3000} onDismiss={() => setSnackbar('')} visible={!!snackbar} - action={{ - label: 'Close', - onPress: () => setSnackbar(''), - color: theme.colors.primary, - }}> + action={action}> {snackbar} From 8d7fe149f5b49e7dbda1e2f5dd63cdbd9d20add1 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 16:11:39 +1300 Subject: [PATCH 42/78] Remove unused code --- App.tsx | 3 ++- SettingsPage.tsx | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/App.tsx b/App.tsx index dade68d..32bc2f1 100644 --- a/App.tsx +++ b/App.tsx @@ -43,13 +43,14 @@ export const CombinedDarkTheme = { const App = () => { const isDark = useColorScheme() === 'dark' const [initialized, setInitialized] = useState(false) + const [snackbar, setSnackbar] = useState('') + const [settings, setSettings] = useState({ ...defaultSettings, color: isDark ? CombinedDarkTheme.colors.primary : CombinedDefaultTheme.colors.primary, }) - const [snackbar, setSnackbar] = useState('') useEffect(() => { AppDataSource.initialize().then(async () => { diff --git a/SettingsPage.tsx b/SettingsPage.tsx index faeb1d8..ad97c63 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -15,7 +15,6 @@ import Select from './Select' import Settings from './settings' import Switch from './Switch' import {toast} from './toast' -import useDark from './use-dark' import {useSettings} from './use-settings' export default function SettingsPage() { @@ -23,7 +22,6 @@ export default function SettingsPage() { const [ignoring, setIgnoring] = useState(false) const [term, setTerm] = useState('') const {settings, setSettings} = useSettings() - const dark = useDark() useEffect(() => { console.log(`${SettingsPage.name}.useEffect:`, {settings}) From 31f1528c35840f6464019d928e7a5870bd4d9415 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 16:50:03 +1300 Subject: [PATCH 43/78] Replace settings context with theme context The settings context was having a big performance impact on the app. We only truly need the theme + color to be a global context. --- .eslintrc.js | 1 + App.tsx | 51 ++++++++++++++++++++++-------------------------- BestList.tsx | 12 +++++++++--- EditSet.tsx | 27 +++++++++++++++++-------- EditWorkout.tsx | 29 ++++++++++++++++++--------- SetForm.tsx | 13 ++++++------ SetItem.tsx | 9 +++++---- SetList.tsx | 19 +++++++++++------- SettingsPage.tsx | 4 ++-- StartPlan.tsx | 9 +++++---- WorkoutItem.tsx | 14 +++++-------- WorkoutList.tsx | 14 ++++++++++--- use-dark.ts | 8 ++++---- use-settings.ts | 31 ----------------------------- use-theme.ts | 17 ++++++++++++++++ 15 files changed, 140 insertions(+), 118 deletions(-) delete mode 100644 use-settings.ts create mode 100644 use-theme.ts diff --git a/.eslintrc.js b/.eslintrc.js index b31c8dc..b5ae89a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,6 +14,7 @@ module.exports = { curly: 'off', 'react/react-in-jsx-scope': 'off', 'react-native/no-inline-styles': 'off', + 'no-spaced-func': 'off', }, }, ], diff --git a/App.tsx b/App.tsx index 32bc2f1..e53e39d 100644 --- a/App.tsx +++ b/App.tsx @@ -16,9 +16,8 @@ import {lightColors} from './colors' import {AppDataSource} from './data-source' import {settingsRepo} from './db' import Routes from './Routes' -import Settings from './settings' import {TOAST} from './toast' -import {defaultSettings, SettingsContext} from './use-settings' +import {ThemeContext} from './use-theme' export const CombinedDefaultTheme = { ...NavigationDefaultTheme, @@ -44,19 +43,20 @@ const App = () => { const isDark = useColorScheme() === 'dark' const [initialized, setInitialized] = useState(false) const [snackbar, setSnackbar] = useState('') + const [theme, setTheme] = useState('system') - const [settings, setSettings] = useState({ - ...defaultSettings, - color: isDark + const [color, setColor] = useState( + isDark ? CombinedDarkTheme.colors.primary : CombinedDefaultTheme.colors.primary, - }) + ) useEffect(() => { AppDataSource.initialize().then(async () => { - const gotSettings = await settingsRepo.findOne({where: {}}) - console.log(`${App.name}.useEffect:`, {gotSettings}) - setSettings(gotSettings) + const settings = await settingsRepo.findOne({where: {}}) + console.log(`${App.name}.useEffect:`, {gotSettings: settings}) + setTheme(settings.theme) + setColor(settings.color) setInitialized(true) }) DeviceEventEmitter.addListener(TOAST, ({value}: {value: string}) => { @@ -65,48 +65,43 @@ const App = () => { }) }, []) - const theme = useMemo(() => { - const darkTheme = settings?.color + const paperTheme = useMemo(() => { + const darkTheme = color ? { ...CombinedDarkTheme, - colors: {...CombinedDarkTheme.colors, primary: settings.color}, + colors: {...CombinedDarkTheme.colors, primary: color}, } : CombinedDarkTheme - const lightTheme = settings?.color + const lightTheme = color ? { ...CombinedDefaultTheme, - colors: {...CombinedDefaultTheme.colors, primary: settings.color}, + colors: {...CombinedDefaultTheme.colors, primary: color}, } : CombinedDefaultTheme let value = isDark ? darkTheme : lightTheme - if (settings?.theme === 'dark') value = darkTheme - else if (settings?.theme === 'light') value = lightTheme + if (theme === 'dark') value = darkTheme + else if (theme === 'light') value = lightTheme return value - }, [isDark, settings?.theme, settings?.color]) - - const settingsContext = useMemo( - () => ({settings, setSettings}), - [settings, setSettings], - ) + }, [isDark, theme, color]) const action = useMemo( () => ({ label: 'Close', onPress: () => setSnackbar(''), - color: theme.colors.primary, + color: paperTheme.colors.primary, }), - [theme.colors.primary], + [paperTheme.colors.primary], ) return ( }}> - + {initialized && ( - + - + )} diff --git a/BestList.tsx b/BestList.tsx index 3846d4d..6118c3e 100644 --- a/BestList.tsx +++ b/BestList.tsx @@ -7,17 +7,23 @@ import {useCallback, useState} from 'react' import {FlatList, Image} from 'react-native' import {List} from 'react-native-paper' import {BestPageParams} from './BestPage' -import {setRepo} from './db' +import {setRepo, settingsRepo} from './db' import DrawerHeader from './DrawerHeader' import GymSet from './gym-set' import Page from './Page' -import {useSettings} from './use-settings' +import Settings from './settings' export default function BestList() { const [bests, setBests] = useState() const [term, setTerm] = useState('') const navigation = useNavigation>() - const {settings} = useSettings() + const [settings, setSettings] = useState() + + useFocusEffect( + useCallback(() => { + settingsRepo.findOne({where: {}}).then(setSettings) + }, []), + ) const refresh = useCallback(async (value: string) => { const weights = await setRepo diff --git a/EditSet.tsx b/EditSet.tsx index a3f8f6a..6a8e73a 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -1,20 +1,31 @@ -import {RouteProp, useNavigation, useRoute} from '@react-navigation/native' -import {useCallback} from 'react' +import { + RouteProp, + useFocusEffect, + useNavigation, + useRoute, +} from '@react-navigation/native' +import {useCallback, useState} from 'react' import {NativeModules, View} from 'react-native' import {PADDING} from './constants' -import {setRepo} from './db' +import {setRepo, settingsRepo} from './db' import GymSet from './gym-set' import {HomePageParams} from './home-page-params' import SetForm from './SetForm' +import Settings from './settings' import StackHeader from './StackHeader' import {toast} from './toast' -import {useSettings} from './use-settings' export default function EditSet() { const {params} = useRoute>() const {set} = params const navigation = useNavigation() - const {settings} = useSettings() + const [settings, setSettings] = useState() + + useFocusEffect( + useCallback(() => { + settingsRepo.findOne({where: {}}).then(setSettings) + }, []), + ) const startTimer = useCallback( async (name: string) => { @@ -23,9 +34,9 @@ export default function EditSet() { const milliseconds = (minutes ?? 3) * 60 * 1000 + (seconds ?? 0) * 1000 NativeModules.AlarmModule.timer( milliseconds, - !!settings.vibrate, + settings.vibrate, settings.sound, - !!settings.noSound, + settings.noSound, ) }, [settings], @@ -60,7 +71,7 @@ export default function EditSet() { <> - + {settings && } ) diff --git a/EditWorkout.tsx b/EditWorkout.tsx index 006bfe9..f92f92d 100644 --- a/EditWorkout.tsx +++ b/EditWorkout.tsx @@ -1,15 +1,20 @@ -import {RouteProp, useNavigation, useRoute} from '@react-navigation/native' +import { + RouteProp, + useFocusEffect, + useNavigation, + useRoute, +} from '@react-navigation/native' import {useCallback, useRef, useState} from 'react' import {ScrollView, TextInput, View} from 'react-native' import DocumentPicker from 'react-native-document-picker' import {Button, Card, TouchableRipple} from 'react-native-paper' import ConfirmDialog from './ConfirmDialog' import {MARGIN, PADDING} from './constants' -import {getNow, planRepo, setRepo} from './db' +import {getNow, planRepo, setRepo, settingsRepo} from './db' import MassiveInput from './MassiveInput' +import Settings from './settings' import StackHeader from './StackHeader' import {toast} from './toast' -import {useSettings} from './use-settings' import {WorkoutsPageParams} from './WorkoutsPage' export default function EditWorkout() { @@ -31,7 +36,13 @@ export default function EditWorkout() { const stepsRef = useRef(null) const minutesRef = useRef(null) const secondsRef = useRef(null) - const {settings} = useSettings() + const [settings, setSettings] = useState() + + useFocusEffect( + useCallback(() => { + settingsRepo.findOne({where: {}}).then(setSettings) + }, []), + ) const update = async () => { await setRepo.update( @@ -119,7 +130,7 @@ export default function EditWorkout() { onChangeText={handleName} onSubmitEditing={submitName} /> - {!!settings.steps && ( + {settings.steps && ( setsRef.current?.focus()} /> )} - {!!settings.showSets && ( + {settings.showSets && ( minutesRef.current?.focus()} /> )} - {!!settings.alarm && ( + {settings.alarm && ( <> )} - {!!settings.images && uri && ( + {settings.images && uri && ( )} - {!!settings.images && !uri && ( + {settings.images && !uri && ( )} From e9c2ee743edb9cd5e84a93d555881e4c92d8e7fb Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 19:22:34 +1300 Subject: [PATCH 48/78] Make purple the default primary color --- colors.ts | 2 +- migrations/1667186320954-add-color.ts | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/colors.ts b/colors.ts index 1e6339c..8ac11ee 100644 --- a/colors.ts +++ b/colors.ts @@ -1,6 +1,6 @@ export const lightColors = [ - {hex: '#FA8072', name: 'Salmon'}, {hex: '#B3E5FC', name: 'Cyan'}, + {hex: '#FA8072', name: 'Salmon'}, {hex: '#FFC0CB', name: 'Pink'}, {hex: '#E9DCC9', name: 'Linen'}, ] diff --git a/migrations/1667186320954-add-color.ts b/migrations/1667186320954-add-color.ts index 373ebab..d1b64c3 100644 --- a/migrations/1667186320954-add-color.ts +++ b/migrations/1667186320954-add-color.ts @@ -1,10 +1,19 @@ -import {MigrationInterface, QueryRunner} from 'typeorm' +import {MigrationInterface, QueryRunner, TableColumn} from 'typeorm' +import {darkColors} from '../colors' export class addColor1667186320954 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner - .query(`ALTER TABLE settings ADD COLUMN color TEXT NULL`) - .catch(() => null) + .addColumn( + 'settings', + new TableColumn({ + name: 'color', + type: 'text', + isNullable: false, + default: `'${darkColors[0].hex}'`, + }), + ) + .catch(console.error) } public async down(queryRunner: QueryRunner): Promise { From 6a4d167e08190ca2a178f9cc271beaf5305f23a2 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 1 Nov 2022 19:25:05 +1300 Subject: [PATCH 49/78] Fix error editing a workout --- EditWorkout.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/EditWorkout.tsx b/EditWorkout.tsx index f92f92d..89cbd55 100644 --- a/EditWorkout.tsx +++ b/EditWorkout.tsx @@ -130,7 +130,7 @@ export default function EditWorkout() { onChangeText={handleName} onSubmitEditing={submitName} /> - {settings.steps && ( + {settings?.steps && ( setsRef.current?.focus()} /> )} - {settings.showSets && ( + {settings?.showSets && ( minutesRef.current?.focus()} /> )} - {settings.alarm && ( + {settings?.alarm && ( <> )} - {settings.images && uri && ( + {settings?.images && uri && ( )} - {settings.images && !uri && ( + {settings?.images && !uri && (