diff --git a/AppInput.tsx b/AppInput.tsx index b837967..7c20190 100644 --- a/AppInput.tsx +++ b/AppInput.tsx @@ -1,10 +1,10 @@ -import {ComponentProps, Ref} from 'react' +import React, {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 AppInput( +function AppInput( props: Partial> & { innerRef?: Ref }, @@ -14,7 +14,6 @@ export default function AppInput( return ( ) } + +export default React.memo(AppInput) diff --git a/EditPlan.tsx b/EditPlan.tsx index 4e69281..45dfc2b 100644 --- a/EditPlan.tsx +++ b/EditPlan.tsx @@ -80,9 +80,9 @@ export default function EditPlan() { toggleDay(value, day)} - value={days.includes(day)}> - {day} - + value={days.includes(day)} + title={day} + /> ))} Workouts {names.length === 0 ? ( @@ -94,9 +94,9 @@ export default function EditPlan() { toggleWorkout(value, name)} - value={workouts.includes(name)}> - {name} - + value={workouts.includes(name)} + title={name} + /> )) )} diff --git a/EditSet.tsx b/EditSet.tsx index 59d0f4b..8e54c70 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -162,7 +162,7 @@ export default function EditSet() { )} diff --git a/ListMenu.tsx b/ListMenu.tsx index f4d1100..7a21ea0 100644 --- a/ListMenu.tsx +++ b/ListMenu.tsx @@ -44,7 +44,6 @@ export default function ListMenu({ } const select = () => { - setShowMenu(false) onSelect() } diff --git a/README.md.pdf b/README.md.pdf deleted file mode 100644 index 9db2c2c..0000000 Binary files a/README.md.pdf and /dev/null differ diff --git a/Routes.tsx b/Routes.tsx index c524b7a..b8c1cac 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -1,6 +1,5 @@ import {createDrawerNavigator} from '@react-navigation/drawer' import {useMemo} from 'react' -import {Platform} from 'react-native' import {IconButton} from 'react-native-paper' import BestPage from './BestPage' import {DrawerParamList} from './drawer-param-list' @@ -36,22 +35,16 @@ export default function Routes() { swipeEdgeWidth: 1000, headerShown: false, }}> - {} - {routes - .filter(route => { - if (Platform.OS === 'ios' && route.name === 'Timer') return false - return true - }) - .map(route => ( - , - }} - /> - ))} + {routes.map(route => ( + , + }} + /> + ))} ) } diff --git a/Select.tsx b/Select.tsx index c0f9ede..2ccddd0 100644 --- a/Select.tsx +++ b/Select.tsx @@ -1,4 +1,4 @@ -import {useCallback, useMemo, useState} from 'react' +import React, {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' import {Button, Menu, Subheading, useTheme} from 'react-native-paper' import {ITEM_PADDING} from './constants' @@ -9,7 +9,7 @@ export interface Item { color?: string } -export default function Select({ +function Select({ value, onChange, items, @@ -68,3 +68,5 @@ export default function Select({ ) } + +export default React.memo(Select) diff --git a/SettingsPage.tsx b/SettingsPage.tsx index 133bc6d..d46e116 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -19,28 +19,32 @@ import Settings from './settings' import Switch from './Switch' import {toast} from './toast' import {useTheme} from './use-theme' +import {useForm} from 'react-hook-form' -const defaultFormats = ['P', 'Pp', 'ccc p', 'p'] +const twelveHours = ['P', 'Pp', 'ccc p', 'p', 'yyyy-MM-d', 'yyyy.MM.d'] +const twentyFours = ['P', 'P, k:m', 'ccc k:m', 'k:m', 'yyyy-MM-d', 'yyyy.MM.d'] export default function SettingsPage() { const [ignoring, setIgnoring] = useState(false) const [term, setTerm] = useState('') - const [formatOptions, setFormatOptions] = useState(defaultFormats) + const [formatOptions, setFormatOptions] = useState(twelveHours) const [importing, setImporting] = useState(false) - const [settings, setSettings] = useState(new Settings()) const {reset} = useNavigation>() - const today = new Date() + + const {watch, setValue} = useForm({ + defaultValues: () => settingsRepo.findOne({where: {}}), + }) + const settings = watch() const {theme, setTheme, lightColor, setLightColor, darkColor, setDarkColor} = useTheme() useEffect(() => { - settingsRepo.findOne({where: {}}).then(setSettings) NativeModules.SettingsModule.ignoringBattery(setIgnoring) NativeModules.SettingsModule.is24().then((is24: boolean) => { console.log(`${SettingsPage.name}.focus:`, {is24}) - if (is24) setFormatOptions(['P', 'P, k:m', 'ccc k:m', 'k:m']) - else setFormatOptions(defaultFormats) + if (is24) setFormatOptions(twentyFours) + else setFormatOptions(twelveHours) }) }, []) @@ -56,54 +60,34 @@ export default function SettingsPage() { copyTo: 'documentDirectory', }) if (!fileCopyUri) return - const updated = await settingsRepo.save({...settings, sound: fileCopyUri}) - setSettings(updated) + setValue('sound', fileCopyUri) + await settingsRepo.save({...settings, sound: fileCopyUri}) toast('Sound will play after rest timers.') - }, [settings]) + }, [settings, setValue]) - const switches: Input[] = [ - {name: 'Rest timers', value: settings.alarm, key: 'alarm'}, - {name: 'Vibrate', value: settings.vibrate, key: 'vibrate'}, - {name: 'Disable sound', value: settings.noSound, key: 'noSound'}, - {name: 'Notifications', value: settings.notify, key: 'notify'}, - {name: 'Show images', value: settings.images, key: 'images'}, - {name: 'Show unit', value: settings.showUnit, key: 'showUnit'}, - {name: 'Show steps', value: settings.steps, key: 'steps'}, - {name: 'Show date', value: settings.showDate, key: 'showDate'}, - ] + const switches: Input[] = useMemo( + () => [ + {name: 'Rest timers', value: settings.alarm, key: 'alarm'}, + {name: 'Vibrate', value: settings.vibrate, key: 'vibrate'}, + {name: 'Disable sound', value: settings.noSound, key: 'noSound'}, + {name: 'Notifications', value: settings.notify, key: 'notify'}, + {name: 'Show images', value: settings.images, key: 'images'}, + {name: 'Show unit', value: settings.showUnit, key: 'showUnit'}, + {name: 'Show steps', value: settings.steps, key: 'steps'}, + {name: 'Show date', value: settings.showDate, key: 'showDate'}, + ], + [settings], + ) - const changeString = useCallback( - async (key: keyof Settings, value: string) => { - const updated = await settingsRepo.save({...settings, [key]: value}) - setSettings(updated) - switch (key) { - case 'date': - return toast('Changed date format') - case 'darkColor': - setDarkColor(value) - return toast('Set primary color for dark mode.') - case 'lightColor': - setLightColor(value) - return toast('Set primary color for light mode.') - case 'vibrate': - return toast('Set primary color for light mode.') - case 'sound': - return toast('Sound will play after rest timers.') - case 'theme': - setTheme(value as string) - if (value === 'dark') toast('Theme will always be dark.') - else if (value === 'light') toast('Theme will always be light.') - else if (value === 'system') toast('Theme will follow system.') - return - } - }, - [settings, setTheme, setDarkColor, setLightColor], + const filter = useCallback( + ({name}) => name.toLowerCase().includes(term.toLowerCase()), + [term], ) const changeBoolean = useCallback( async (key: keyof Settings, value: boolean) => { - const updated = await settingsRepo.save({...settings, [key]: value}) - setSettings(updated) + setValue(key, value) + await settingsRepo.save({...settings, [key]: value}) switch (key) { case 'alarm': if (value) toast('Timers will now run after each set.') @@ -140,7 +124,7 @@ export default function SettingsPage() { return } }, - [settings, ignoring], + [settings, ignoring, setValue], ) const renderSwitch = useCallback( @@ -148,37 +132,73 @@ export default function SettingsPage() { changeBoolean(item.key, value)}> - {item.name} - + onChange={value => changeBoolean(item.key, value)} + title={item.name} + /> ), [changeBoolean], ) - const selects: Input[] = [ - {name: 'Theme', value: theme, items: themeOptions, key: 'theme'}, - { - name: 'Dark color', - value: darkColor, - items: lightOptions, - key: 'darkColor', + const switchesMarkup = useMemo( + () => switches.filter(filter).map(s => renderSwitch(s)), + [filter, switches, renderSwitch], + ) + + const changeString = useCallback( + async (key: keyof Settings, value: string) => { + setValue(key, value) + await settingsRepo.save({...settings, [key]: value}) + switch (key) { + case 'date': + return toast('Changed date format') + case 'darkColor': + setDarkColor(value) + return toast('Set primary color for dark mode.') + case 'lightColor': + setLightColor(value) + return toast('Set primary color for light mode.') + case 'vibrate': + return toast('Set primary color for light mode.') + case 'sound': + return toast('Sound will play after rest timers.') + case 'theme': + setTheme(value as string) + if (value === 'dark') toast('Theme will always be dark.') + else if (value === 'light') toast('Theme will always be light.') + else if (value === 'system') toast('Theme will follow system.') + return + } }, - { - name: 'Light color', - value: lightColor, - items: darkOptions, - key: 'lightColor', - }, - { - name: 'Date format', - value: settings.date, - items: formatOptions.map(option => ({ - label: format(today, option), - value: option, - })), - key: 'date', - }, - ] + [settings, setTheme, setDarkColor, setLightColor, setValue], + ) + + const selects: Input[] = useMemo(() => { + const today = new Date() + return [ + {name: 'Theme', value: theme, items: themeOptions, key: 'theme'}, + { + name: 'Dark color', + value: darkColor, + items: lightOptions, + key: 'darkColor', + }, + { + name: 'Light color', + value: lightColor, + items: darkOptions, + key: 'lightColor', + }, + { + name: 'Date format', + value: settings.date, + items: formatOptions.map(option => ({ + label: format(today, option), + value: option, + })), + key: 'date', + }, + ] + }, [settings.date, darkColor, formatOptions, theme, lightColor]) const renderSelect = useCallback( (item: Input) => ( @@ -193,6 +213,11 @@ export default function SettingsPage() { [changeString], ) + const selectsMarkup = useMemo( + () => selects.filter(filter).map(renderSelect), + [filter, selects, renderSelect], + ) + const confirmImport = useCallback(async () => { setImporting(false) await AppDataSource.destroy() @@ -215,47 +240,51 @@ export default function SettingsPage() { }, []) const buttons = useMemo( - () => - [ - { - name: 'Alarm sound', - element: ( - - Alarm sound - - - ), - }, - { - name: 'Export database', - element: ( - - ), - }, - { - name: 'Import database', - element: ( - - ), - }, - ].filter(({name}) => name.toLowerCase().includes(term.toLowerCase())), - [changeSound, exportDatabase, soundString, term], + () => [ + { + name: 'Alarm sound', + element: ( + + Alarm sound + + + ), + }, + { + name: 'Export database', + element: ( + + ), + }, + { + name: 'Import database', + element: ( + + ), + }, + ], + [changeSound, exportDatabase, soundString], + ) + + const buttonsMarkup = useMemo( + () => buttons.filter(filter).map(b => b.element), + [buttons, filter], ) return ( @@ -264,9 +293,9 @@ export default function SettingsPage() { - {switches.map(s => renderSwitch(s))} - {selects.map(s => renderSelect(s))} - {buttons.map(b => b.element)} + {switchesMarkup} + {selectsMarkup} + {buttonsMarkup} diff --git a/Switch.tsx b/Switch.tsx index 33edb7b..bf27162 100644 --- a/Switch.tsx +++ b/Switch.tsx @@ -1,15 +1,16 @@ +import React from 'react' import {Platform, Pressable} from 'react-native' import {Switch as PaperSwitch, Text, useTheme} from 'react-native-paper' import {MARGIN} from './constants' -export default function Switch({ +function Switch({ value, onChange, - children, + title, }: { value?: boolean onChange: (value: boolean) => void - children: string + title: string }) { const {colors} = useTheme() @@ -29,7 +30,9 @@ export default function Switch({ onValueChange={onChange} trackColor={{true: colors.primary + '80', false: colors.disabled}} /> - {children} + {title} ) } + +export default React.memo(Switch) diff --git a/android/Gemfile.lock b/android/Gemfile.lock index a5cd217..9933bc4 100644 --- a/android/Gemfile.lock +++ b/android/Gemfile.lock @@ -8,16 +8,16 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.657.0) - aws-sdk-core (3.166.0) + aws-partitions (1.686.0) + aws-sdk-core (3.168.4) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.59.0) + aws-sdk-kms (1.61.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.117.1) + aws-sdk-s3 (1.117.2) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.93.1) + excon (0.95.0) faraday (1.10.2) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.210.1) + fastlane (2.211.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -106,9 +106,9 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.31.0) + google-apis-androidpublisher_v3 (0.32.0) google-apis-core (>= 0.9.1, < 2.a) - google-apis-core (0.9.1) + google-apis-core (0.9.2) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -148,11 +148,11 @@ GEM http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.6.1) - json (2.6.2) - jwt (2.5.0) + jmespath (1.6.2) + json (2.6.3) + jwt (2.6.0) memoist (0.16.2) - mini_magick (4.11.0) + mini_magick (4.12.0) mini_mime (1.1.2) multi_json (1.15.0) multipart-post (2.0.0) @@ -161,7 +161,7 @@ GEM optparse (0.1.1) os (1.1.4) plist (3.6.0) - public_suffix (5.0.0) + public_suffix (5.0.1) rake (13.0.6) representable (3.2.0) declarative (< 0.1.0) diff --git a/android/app/build.gradle b/android/app/build.gradle index 96987aa..e891116 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -41,8 +41,8 @@ android { missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 36128 - versionName "1.102" + versionCode 36133 + versionName "1.107" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { diff --git a/metadata/en-US/images/phoneScreenshots/best-view.png b/metadata/en-US/images/phoneScreenshots/best-view.png index 2f9547a..546aef6 100644 Binary files a/metadata/en-US/images/phoneScreenshots/best-view.png and b/metadata/en-US/images/phoneScreenshots/best-view.png differ diff --git a/metadata/en-US/images/phoneScreenshots/drawer.png b/metadata/en-US/images/phoneScreenshots/drawer.png index fb41699..55ea552 100644 Binary files a/metadata/en-US/images/phoneScreenshots/drawer.png and b/metadata/en-US/images/phoneScreenshots/drawer.png differ diff --git a/metadata/en-US/images/phoneScreenshots/edit.png b/metadata/en-US/images/phoneScreenshots/edit.png index 5a0242c..09d2630 100644 Binary files a/metadata/en-US/images/phoneScreenshots/edit.png and b/metadata/en-US/images/phoneScreenshots/edit.png differ diff --git a/metadata/en-US/images/phoneScreenshots/home.png b/metadata/en-US/images/phoneScreenshots/home.png index c0ffb94..4109737 100644 Binary files a/metadata/en-US/images/phoneScreenshots/home.png and b/metadata/en-US/images/phoneScreenshots/home.png differ diff --git a/metadata/en-US/images/phoneScreenshots/plan-start.png b/metadata/en-US/images/phoneScreenshots/plan-start.png index 0de9f9e..38587da 100644 Binary files a/metadata/en-US/images/phoneScreenshots/plan-start.png and b/metadata/en-US/images/phoneScreenshots/plan-start.png differ diff --git a/metadata/en-US/images/phoneScreenshots/settings.png b/metadata/en-US/images/phoneScreenshots/settings.png index b00d134..66d80b1 100644 Binary files a/metadata/en-US/images/phoneScreenshots/settings.png and b/metadata/en-US/images/phoneScreenshots/settings.png differ diff --git a/metadata/en-US/images/phoneScreenshots/timer.png b/metadata/en-US/images/phoneScreenshots/timer.png index 8e4425a..3720b27 100644 Binary files a/metadata/en-US/images/phoneScreenshots/timer.png and b/metadata/en-US/images/phoneScreenshots/timer.png differ diff --git a/metadata/en-US/images/phoneScreenshots/workout.png b/metadata/en-US/images/phoneScreenshots/workout.png index 9957c72..3c2de87 100644 Binary files a/metadata/en-US/images/phoneScreenshots/workout.png and b/metadata/en-US/images/phoneScreenshots/workout.png differ diff --git a/metadata/en-US/images/phoneScreenshots/workouts.png b/metadata/en-US/images/phoneScreenshots/workouts.png index 138b0e5..50494d1 100644 Binary files a/metadata/en-US/images/phoneScreenshots/workouts.png and b/metadata/en-US/images/phoneScreenshots/workouts.png differ diff --git a/package.json b/package.json index 5d5de6e..a312798 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "massive", - "version": "1.102", + "version": "1.107", "private": true, "license": "GPL-3.0-only", "scripts": { @@ -30,6 +30,7 @@ "eslint-plugin-flowtype": "^8.0.3", "jest": "^29.2.2", "react": "^18.2.0", + "react-hook-form": "^7.41.2", "react-native": "^0.70.5", "react-native-document-picker": "^8.1.2", "react-native-file-access": "^2.5.0", diff --git a/yarn.lock b/yarn.lock index 2b25a91..18534d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7295,6 +7295,7 @@ __metadata: jest: ^29.2.2 metro-react-native-babel-preset: ^0.73.3 react: ^18.2.0 + react-hook-form: ^7.41.2 react-native: ^0.70.5 react-native-document-picker: ^8.1.2 react-native-file-access: ^2.5.0 @@ -8752,6 +8753,15 @@ __metadata: languageName: node linkType: hard +"react-hook-form@npm:^7.41.2": + version: 7.41.2 + resolution: "react-hook-form@npm:7.41.2" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + checksum: bc923b74018d55289838f820d49e32043dbc683d97ea2f93a6f3b75ff58fea9ee4536d6487adcb02912b4bc90a09ea07a63c4c24f930ec59f598bdafd5e8c8d3 + languageName: node + linkType: hard + "react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.2.0": version: 18.2.0 resolution: "react-is@npm:18.2.0"