Merge branch 'master' into unit-tests
|
@ -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<ComponentProps<typeof TextInput>> & {
|
||||
innerRef?: Ref<any>
|
||||
},
|
||||
|
@ -14,7 +14,6 @@ export default function AppInput(
|
|||
return (
|
||||
<TextInput
|
||||
selectionColor={dark ? '#2A2A2A' : CombinedDefaultTheme.colors.border}
|
||||
mode="outlined"
|
||||
style={{marginBottom: MARGIN, minWidth: 100}}
|
||||
selectTextOnFocus
|
||||
ref={props.innerRef}
|
||||
|
@ -23,3 +22,5 @@ export default function AppInput(
|
|||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(AppInput)
|
||||
|
|
12
EditPlan.tsx
|
@ -80,9 +80,9 @@ export default function EditPlan() {
|
|||
<Switch
|
||||
key={day}
|
||||
onChange={value => toggleDay(value, day)}
|
||||
value={days.includes(day)}>
|
||||
{day}
|
||||
</Switch>
|
||||
value={days.includes(day)}
|
||||
title={day}
|
||||
/>
|
||||
))}
|
||||
<Text style={[styles.title, {marginTop: MARGIN}]}>Workouts</Text>
|
||||
{names.length === 0 ? (
|
||||
|
@ -94,9 +94,9 @@ export default function EditPlan() {
|
|||
<Switch
|
||||
key={name}
|
||||
onChange={value => toggleWorkout(value, name)}
|
||||
value={workouts.includes(name)}>
|
||||
{name}
|
||||
</Switch>
|
||||
value={workouts.includes(name)}
|
||||
title={name}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ScrollView>
|
||||
|
|
|
@ -162,7 +162,7 @@ export default function EditSet() {
|
|||
<AppInput
|
||||
label="Created"
|
||||
disabled
|
||||
value={format(new Date(set.created), settings.date)}
|
||||
value={format(new Date(set.created), settings.date || 'P')}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ export default function ListMenu({
|
|||
}
|
||||
|
||||
const select = () => {
|
||||
setShowMenu(false)
|
||||
onSelect()
|
||||
}
|
||||
|
||||
|
|
BIN
README.md.pdf
|
@ -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,13 +35,7 @@ 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 => (
|
||||
<Drawer.Screen
|
||||
key={route.name}
|
||||
name={route.name}
|
||||
|
|
|
@ -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({
|
|||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Select)
|
||||
|
|
133
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<string[]>(defaultFormats)
|
||||
const [formatOptions, setFormatOptions] = useState<string[]>(twelveHours)
|
||||
const [importing, setImporting] = useState(false)
|
||||
const [settings, setSettings] = useState(new Settings())
|
||||
const {reset} = useNavigation<NavigationProp<DrawerParamList>>()
|
||||
const today = new Date()
|
||||
|
||||
const {watch, setValue} = useForm<Settings>({
|
||||
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,12 +60,13 @@ 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<boolean>[] = [
|
||||
const switches: Input<boolean>[] = useMemo(
|
||||
() => [
|
||||
{name: 'Rest timers', value: settings.alarm, key: 'alarm'},
|
||||
{name: 'Vibrate', value: settings.vibrate, key: 'vibrate'},
|
||||
{name: 'Disable sound', value: settings.noSound, key: 'noSound'},
|
||||
|
@ -70,40 +75,19 @@ export default function SettingsPage() {
|
|||
{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,14 +132,49 @@ export default function SettingsPage() {
|
|||
<Switch
|
||||
key={item.name}
|
||||
value={item.value}
|
||||
onChange={value => changeBoolean(item.key, value)}>
|
||||
{item.name}
|
||||
</Switch>
|
||||
onChange={value => changeBoolean(item.key, value)}
|
||||
title={item.name}
|
||||
/>
|
||||
),
|
||||
[changeBoolean],
|
||||
)
|
||||
|
||||
const selects: Input<string>[] = [
|
||||
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
|
||||
}
|
||||
},
|
||||
[settings, setTheme, setDarkColor, setLightColor, setValue],
|
||||
)
|
||||
|
||||
const selects: Input<string>[] = useMemo(() => {
|
||||
const today = new Date()
|
||||
return [
|
||||
{name: 'Theme', value: theme, items: themeOptions, key: 'theme'},
|
||||
{
|
||||
name: 'Dark color',
|
||||
|
@ -179,6 +198,7 @@ export default function SettingsPage() {
|
|||
key: 'date',
|
||||
},
|
||||
]
|
||||
}, [settings.date, darkColor, formatOptions, theme, lightColor])
|
||||
|
||||
const renderSelect = useCallback(
|
||||
(item: Input<string>) => (
|
||||
|
@ -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,8 +240,7 @@ export default function SettingsPage() {
|
|||
}, [])
|
||||
|
||||
const buttons = useMemo(
|
||||
() =>
|
||||
[
|
||||
() => [
|
||||
{
|
||||
name: 'Alarm sound',
|
||||
element: (
|
||||
|
@ -254,8 +278,13 @@ export default function SettingsPage() {
|
|||
</Button>
|
||||
),
|
||||
},
|
||||
].filter(({name}) => name.toLowerCase().includes(term.toLowerCase())),
|
||||
[changeSound, exportDatabase, soundString, term],
|
||||
],
|
||||
[changeSound, exportDatabase, soundString],
|
||||
)
|
||||
|
||||
const buttonsMarkup = useMemo(
|
||||
() => buttons.filter(filter).map(b => b.element),
|
||||
[buttons, filter],
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -264,9 +293,9 @@ export default function SettingsPage() {
|
|||
|
||||
<Page term={term} search={setTerm} style={{flexGrow: 0}}>
|
||||
<View style={{marginTop: MARGIN}}>
|
||||
{switches.map(s => renderSwitch(s))}
|
||||
{selects.map(s => renderSelect(s))}
|
||||
{buttons.map(b => b.element)}
|
||||
{switchesMarkup}
|
||||
{selectsMarkup}
|
||||
{buttonsMarkup}
|
||||
</View>
|
||||
</Page>
|
||||
|
||||
|
|
11
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}}
|
||||
/>
|
||||
<Text>{children}</Text>
|
||||
<Text>{title}</Text>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Switch)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 184 KiB |
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 123 KiB |
|
@ -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",
|
||||
|
|
10
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"
|
||||
|
|