Merge branch 'master' into intermediary-tables

This commit is contained in:
Brandon Presley 2022-12-08 17:51:06 +13:00
commit e6542798ea
10 changed files with 60 additions and 163 deletions

View File

@ -58,6 +58,7 @@ const App = () => {
console.log(`${App.name}.useEffect:`, {gotSettings: settings})
setTheme(settings.theme)
if (settings.lightColor) setLightColor(settings.lightColor)
if (settings.darkColor) setDarkColor(settings.darkColor)
setInitialized(true)
}
init()

View File

@ -1,21 +1,11 @@
import {NavigationProp, useNavigation} from '@react-navigation/native'
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'
import {IconButton, Menu} from 'react-native-paper'
import ConfirmDialog from './ConfirmDialog'
import {AppDataSource} from './data-source'
import {planRepo} from './db'
import {planRepo, setRepo} from './db'
import {DrawerParamList} from './drawer-param-list'
import GymSet from './gym-set'
import {Plan} from './plan'
import {toast} from './toast'
import useDark from './use-dark'
import {write} from './write'
const setFields = 'id,name,reps,weight,created,unit,hidden,sets,minutes,seconds'
const planFields = 'id,days,workouts'
const setRepo = AppDataSource.manager.getRepository(GymSet)
export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const [showMenu, setShowMenu] = useState(false)
@ -23,113 +13,6 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
const {reset} = useNavigation<NavigationProp<DrawerParamList>>()
const dark = useDark()
const exportSets = useCallback(async () => {
const sets = await setRepo.find({})
const data = [setFields]
.concat(
sets.map(set =>
setFields
.split(',')
.map(fieldString => {
const field = fieldString as keyof GymSet
if (field === 'unit') return set[field] || 'kg'
return set[field]
})
.join(','),
),
)
.join('\n')
console.log(`${DrawerMenu.name}.exportSets`, {length: sets.length})
await write('sets.csv', data)
}, [])
const exportPlans = useCallback(async () => {
const plans = await planRepo.find({})
const data = [planFields]
.concat(plans.map(set => `"${set.id}","${set.days}","${set.workouts}"`))
.join('\n')
console.log(`${DrawerMenu.name}.exportPlans`, {length: plans.length})
await write('plans.csv', data)
}, [])
const download = useCallback(async () => {
setShowMenu(false)
if (name === 'Home') exportSets()
else if (name === 'Plans') exportPlans()
}, [name, exportSets, exportPlans])
const uploadSets = useCallback(async () => {
const result = await DocumentPicker.pickSingle()
const file = await FileSystem.readFile(result.uri)
console.log(`${DrawerMenu.name}.uploadSets:`, file.length)
const lines = file.split('\n')
if (!setFields.includes(lines[0])) return toast('Invalid csv.')
const values = lines
.slice(1)
.filter(line => line)
.map(line => {
let [
,
setName,
reps,
weight,
created,
unit,
hidden,
sets,
minutes,
seconds,
] = line.split(',')
const set: GymSet = {
name: setName,
reps: +reps,
weight: +weight,
created,
unit: unit ?? 'kg',
hidden: !!Number(hidden),
sets: +sets,
minutes: +minutes,
seconds: +seconds,
image: '',
}
return set
})
await setRepo.insert(values)
toast('Data imported.')
reset({index: 0, routes: [{name}]})
}, [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.')
const values = file
.split('\n')
.slice(1)
.filter(line => line)
.map(set => {
const [, days, workouts] = set
.split('","')
.map(cell => cell.replace(/"/g, ''))
const plan: Plan = {
days,
workouts,
}
return plan
})
await planRepo.insert(values)
toast('Data imported.')
}, [])
const upload = useCallback(async () => {
setShowMenu(false)
if (name === 'Home') await uploadSets()
else if (name === 'Plans') await uploadPlans()
reset({index: 0, routes: [{name}]})
}, [name, uploadPlans, uploadSets, reset])
const remove = useCallback(async () => {
setShowMenu(false)
setShowRemove(false)
@ -151,9 +34,6 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
icon="more-vert"
/>
}>
<Menu.Item icon="arrow-downward" onPress={download} title="Download" />
<Menu.Item icon="arrow-upward" onPress={upload} title="Upload" />
<Divider />
<Menu.Item
icon="delete"
onPress={() => setShowRemove(true)}

View File

@ -99,18 +99,6 @@ export default function EditSet() {
navigation.goBack()
}
const handleName = useCallback((value: string) => {
setName(value.replace(/,|'/g, ''))
if (value.match(/,|'/))
toast('Commas and single quotes would break CSV exports')
}, [])
const handleUnit = useCallback((value: string) => {
setUnit(value.replace(/,|'/g, ''))
if (value.match(/,|'/))
toast('Commas and single quotes would break CSV exports')
}, [])
const changeImage = useCallback(async () => {
const {fileCopyUri} = await DocumentPicker.pickSingle({
type: DocumentPicker.types.images,
@ -133,7 +121,7 @@ export default function EditSet() {
<MassiveInput
label="Name"
value={name}
onChangeText={handleName}
onChangeText={setName}
autoCorrect={false}
autoFocus={!name}
onSubmitEditing={() => repsRef.current?.focus()}
@ -165,7 +153,7 @@ export default function EditSet() {
autoCapitalize="none"
label="Unit"
value={unit}
onChangeText={handleUnit}
onChangeText={setUnit}
innerRef={unitRef}
/>
)}

View File

@ -15,7 +15,6 @@ import {defaultSet} from './gym-set'
import MassiveInput from './MassiveInput'
import Settings from './settings'
import StackHeader from './StackHeader'
import {toast} from './toast'
import {WorkoutsPageParams} from './WorkoutsPage'
export default function EditWorkout() {
@ -101,18 +100,6 @@ export default function EditWorkout() {
setShowRemove(false)
}, [])
const handleName = (value: string) => {
setName(value.replace(/,|'/g, ''))
if (value.match(/,|'/))
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')
}
const submitName = () => {
if (settings.steps) stepsRef.current?.focus()
else setsRef.current?.focus()
@ -127,7 +114,7 @@ export default function EditWorkout() {
autoFocus
label="Name"
value={name}
onChangeText={handleName}
onChangeText={setName}
onSubmitEditing={submitName}
/>
{settings?.steps && (
@ -135,7 +122,7 @@ export default function EditWorkout() {
innerRef={stepsRef}
selectTextOnFocus={false}
value={steps}
onChangeText={handleSteps}
onChangeText={setSteps}
label="Steps"
multiline
onSubmitEditing={() => setsRef.current?.focus()}

View File

@ -66,7 +66,7 @@ export default function SetItem({
alignSelf: 'center',
color: dark ? '#909090ff' : '#717171ff',
}}>
{format(new Date(item.created), settings.date)}
{format(new Date(item.created), settings.date || 'P')}
</Text>
)}
<Menu

View File

@ -1,4 +1,8 @@
import {useFocusEffect} from '@react-navigation/native'
import {
NavigationProp,
useFocusEffect,
useNavigation,
} from '@react-navigation/native'
import {format} from 'date-fns'
import {useCallback, useMemo, useState} from 'react'
import {
@ -9,9 +13,13 @@ import {
View,
} from 'react-native'
import DocumentPicker from 'react-native-document-picker'
import {Dirs, FileSystem} from 'react-native-file-access'
import {Button, Subheading} from 'react-native-paper'
import ConfirmDialog from './ConfirmDialog'
import {ITEM_PADDING, MARGIN} from './constants'
import {AppDataSource} from './data-source'
import {settingsRepo} from './db'
import {DrawerParamList} from './drawer-param-list'
import DrawerHeader from './DrawerHeader'
import Input from './input'
import {darkOptions, lightOptions, themeOptions} from './options'
@ -39,6 +47,8 @@ export default function SettingsPage() {
const [showDate, setShowDate] = useState(false)
const [noSound, setNoSound] = useState(false)
const [formatOptions, setFormatOptions] = useState<string[]>(defaultFormats)
const [importing, setImporting] = useState(false)
const {reset} = useNavigation<NavigationProp<DrawerParamList>>()
const today = new Date()
useFocusEffect(
@ -237,6 +247,21 @@ export default function SettingsPage() {
[],
)
const confirmImport = useCallback(async () => {
setImporting(false)
await AppDataSource.destroy()
const result = await DocumentPicker.pickSingle()
await FileSystem.cp(result.uri, Dirs.DatabaseDir + '/massive.db')
await AppDataSource.initialize()
reset({index: 0, routes: [{name: 'Settings'}]})
}, [reset])
const exportDatabase = useCallback(async () => {
const path = Dirs.DatabaseDir + '/massive.db'
await FileSystem.cpExternal(path, 'massive.db', 'downloads')
toast('Database exported. Check downloads.')
}, [])
return (
<>
<DrawerHeader name="Settings" />
@ -258,7 +283,28 @@ export default function SettingsPage() {
<Button onPress={changeSound}>{soundString || 'Default'}</Button>
</View>
)}
{'export database'.includes(term.toLowerCase()) && (
<Button style={{alignSelf: 'flex-start'}} onPress={exportDatabase}>
Export database
</Button>
)}
{'import database'.includes(term.toLowerCase()) && (
<Button
style={{alignSelf: 'flex-start'}}
onPress={() => setImporting(true)}>
Import database
</Button>
)}
</Page>
<ConfirmDialog
title="Are you sure?"
onOk={confirmImport}
setShow={setImporting}
show={importing}>
Importing a database overwrites your current data. This action cannot be
reversed!
</ConfirmDialog>
</>
)
}

View File

@ -111,12 +111,6 @@ 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')
}, [])
return (
<>
<StackHeader title={params.plan.days.replace(/,/g, ', ')} />
@ -146,7 +140,7 @@ export default function StartPlan() {
autoCapitalize="none"
label="Unit"
value={unit}
onChangeText={handleUnit}
onChangeText={setUnit}
innerRef={unitRef}
/>
)}

View File

@ -41,8 +41,8 @@ android {
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 36113
versionName "1.87"
versionCode 36115
versionName "1.89"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {

View File

@ -156,6 +156,7 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
val manager = getManager()
manager.notify(NOTIFICATION_ID_DONE, builder.build())
manager.cancel(NOTIFICATION_ID_PENDING)
Log.d("AlarmModule", "Finished: vibrate=$vibrate,sound=$sound,noSound=$noSound")
val alarmIntent = Intent(context, AlarmService::class.java).apply {
putExtra("vibrate", vibrate)
putExtra("sound", sound)

View File

@ -1,6 +1,6 @@
{
"name": "massive",
"version": "1.87",
"version": "1.89",
"private": true,
"license": "GPL-3.0-only",
"scripts": {