Remove semicolons from line endings
This commit is contained in:
parent
1bc145f60c
commit
bc7aca03e8
|
@ -4,4 +4,5 @@ module.exports = {
|
|||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
}
|
||||
semi: false,
|
||||
};
|
||||
|
|
66
App.tsx
66
App.tsx
|
@ -2,22 +2,22 @@ import {
|
|||
DarkTheme as NavigationDarkTheme,
|
||||
DefaultTheme as NavigationDefaultTheme,
|
||||
NavigationContainer,
|
||||
} from '@react-navigation/native';
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import {useColorScheme} from 'react-native';
|
||||
} from '@react-navigation/native'
|
||||
import {useEffect, useMemo, useState} from 'react'
|
||||
import {useColorScheme} from 'react-native'
|
||||
import {
|
||||
DarkTheme as PaperDarkTheme,
|
||||
DefaultTheme as PaperDefaultTheme,
|
||||
Provider as PaperProvider,
|
||||
} from 'react-native-paper';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {Color} from './color';
|
||||
import {lightColors} from './colors';
|
||||
import {runMigrations, settingsRepo} from './db';
|
||||
import MassiveSnack from './MassiveSnack';
|
||||
import Routes from './Routes';
|
||||
import Settings from './settings';
|
||||
import {SettingsContext} from './use-settings';
|
||||
} from 'react-native-paper'
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons'
|
||||
import {Color} from './color'
|
||||
import {lightColors} from './colors'
|
||||
import {runMigrations, settingsRepo} from './db'
|
||||
import MassiveSnack from './MassiveSnack'
|
||||
import Routes from './Routes'
|
||||
import Settings from './settings'
|
||||
import {SettingsContext} from './use-settings'
|
||||
|
||||
export const CombinedDefaultTheme = {
|
||||
...NavigationDefaultTheme,
|
||||
|
@ -26,7 +26,7 @@ export const CombinedDefaultTheme = {
|
|||
...NavigationDefaultTheme.colors,
|
||||
...PaperDefaultTheme.colors,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const CombinedDarkTheme = {
|
||||
...NavigationDarkTheme,
|
||||
|
@ -37,40 +37,40 @@ export const CombinedDarkTheme = {
|
|||
primary: lightColors[0].hex,
|
||||
background: '#0E0E0E',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const isDark = useColorScheme() === 'dark';
|
||||
const [settings, setSettings] = useState<Settings>();
|
||||
const isDark = useColorScheme() === 'dark'
|
||||
const [settings, setSettings] = useState<Settings>()
|
||||
const [color, setColor] = useState(
|
||||
isDark
|
||||
? CombinedDarkTheme.colors.primary.toUpperCase()
|
||||
: CombinedDefaultTheme.colors.primary.toUpperCase(),
|
||||
);
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
runMigrations().then(async () => {
|
||||
const gotSettings = await settingsRepo.findOne({where: {}});
|
||||
console.log(`${App.name}.runMigrations:`, {gotSettings});
|
||||
setSettings(gotSettings);
|
||||
if (gotSettings.color) setColor(gotSettings.color);
|
||||
});
|
||||
}, [setColor]);
|
||||
const gotSettings = await settingsRepo.findOne({where: {}})
|
||||
console.log(`${App.name}.runMigrations:`, {gotSettings})
|
||||
setSettings(gotSettings)
|
||||
if (gotSettings.color) setColor(gotSettings.color)
|
||||
})
|
||||
}, [setColor])
|
||||
|
||||
const theme = useMemo(() => {
|
||||
const darkTheme = {
|
||||
...CombinedDarkTheme,
|
||||
colors: {...CombinedDarkTheme.colors, primary: color},
|
||||
};
|
||||
}
|
||||
const lightTheme = {
|
||||
...CombinedDefaultTheme,
|
||||
colors: {...CombinedDefaultTheme.colors, primary: color},
|
||||
};
|
||||
let value = isDark ? darkTheme : lightTheme;
|
||||
if (settings?.theme === 'dark') value = darkTheme;
|
||||
else if (settings?.theme === 'light') value = lightTheme;
|
||||
return value;
|
||||
}, [color, isDark, settings]);
|
||||
}
|
||||
let value = isDark ? darkTheme : lightTheme
|
||||
if (settings?.theme === 'dark') value = darkTheme
|
||||
else if (settings?.theme === 'light') value = lightTheme
|
||||
return value
|
||||
}, [color, isDark, settings])
|
||||
|
||||
return (
|
||||
<Color.Provider value={{color, setColor}}>
|
||||
|
@ -88,7 +88,7 @@ const App = () => {
|
|||
</NavigationContainer>
|
||||
</PaperProvider>
|
||||
</Color.Provider>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App
|
||||
|
|
56
BestList.tsx
56
BestList.tsx
|
@ -2,22 +2,22 @@ import {
|
|||
NavigationProp,
|
||||
useFocusEffect,
|
||||
useNavigation,
|
||||
} from '@react-navigation/native';
|
||||
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 DrawerHeader from './DrawerHeader';
|
||||
import GymSet from './gym-set';
|
||||
import Page from './Page';
|
||||
import {useSettings} from './use-settings';
|
||||
} from '@react-navigation/native'
|
||||
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 DrawerHeader from './DrawerHeader'
|
||||
import GymSet from './gym-set'
|
||||
import Page from './Page'
|
||||
import {useSettings} from './use-settings'
|
||||
|
||||
export default function BestList() {
|
||||
const [bests, setBests] = useState<GymSet[]>();
|
||||
const [term, setTerm] = useState('');
|
||||
const navigation = useNavigation<NavigationProp<BestPageParams>>();
|
||||
const {settings} = useSettings();
|
||||
const [bests, setBests] = useState<GymSet[]>()
|
||||
const [term, setTerm] = useState('')
|
||||
const navigation = useNavigation<NavigationProp<BestPageParams>>()
|
||||
const {settings} = useSettings()
|
||||
|
||||
const refresh = useCallback(async (value: string) => {
|
||||
const weights = await setRepo
|
||||
|
@ -27,9 +27,9 @@ export default function BestList() {
|
|||
.where('name LIKE :name', {name: `%${value}%`})
|
||||
.andWhere('NOT hidden')
|
||||
.groupBy('name')
|
||||
.getMany();
|
||||
console.log(`${BestList.name}.refresh:`, {length: weights.length});
|
||||
let newBest: GymSet[] = [];
|
||||
.getMany()
|
||||
console.log(`${BestList.name}.refresh:`, {length: weights.length})
|
||||
let newBest: GymSet[] = []
|
||||
for (const set of weights) {
|
||||
const reps = await setRepo
|
||||
.createQueryBuilder()
|
||||
|
@ -39,25 +39,25 @@ export default function BestList() {
|
|||
.andWhere('weight = :weight', {weight: set.weight})
|
||||
.andWhere('NOT hidden')
|
||||
.groupBy('name')
|
||||
.getMany();
|
||||
newBest.push(...reps);
|
||||
.getMany()
|
||||
newBest.push(...reps)
|
||||
}
|
||||
setBests(newBest);
|
||||
}, []);
|
||||
setBests(newBest)
|
||||
}, [])
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
refresh(term);
|
||||
refresh(term)
|
||||
}, [refresh, term]),
|
||||
);
|
||||
)
|
||||
|
||||
const search = useCallback(
|
||||
(value: string) => {
|
||||
setTerm(value);
|
||||
refresh(value);
|
||||
setTerm(value)
|
||||
refresh(value)
|
||||
},
|
||||
[refresh],
|
||||
);
|
||||
)
|
||||
|
||||
const renderItem = ({item}: {item: GymSet}) => (
|
||||
<List.Item
|
||||
|
@ -72,7 +72,7 @@ export default function BestList() {
|
|||
null
|
||||
}
|
||||
/>
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -88,5 +88,5 @@ export default function BestList() {
|
|||
)}
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
20
BestPage.tsx
20
BestPage.tsx
|
@ -1,15 +1,15 @@
|
|||
import {createStackNavigator} from '@react-navigation/stack';
|
||||
import BestList from './BestList';
|
||||
import GymSet from './gym-set';
|
||||
import ViewBest from './ViewBest';
|
||||
import {createStackNavigator} from '@react-navigation/stack'
|
||||
import BestList from './BestList'
|
||||
import GymSet from './gym-set'
|
||||
import ViewBest from './ViewBest'
|
||||
|
||||
const Stack = createStackNavigator<BestPageParams>();
|
||||
const Stack = createStackNavigator<BestPageParams>()
|
||||
export type BestPageParams = {
|
||||
BestList: {};
|
||||
BestList: {}
|
||||
ViewBest: {
|
||||
best: GymSet;
|
||||
};
|
||||
};
|
||||
best: GymSet
|
||||
}
|
||||
}
|
||||
|
||||
export default function BestPage() {
|
||||
return (
|
||||
|
@ -18,5 +18,5 @@ export default function BestPage() {
|
|||
<Stack.Screen name="BestList" component={BestList} />
|
||||
<Stack.Screen name="ViewBest" component={ViewBest} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
36
Chart.tsx
36
Chart.tsx
|
@ -1,11 +1,11 @@
|
|||
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';
|
||||
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'
|
||||
|
||||
export default function Chart({
|
||||
yData,
|
||||
|
@ -13,21 +13,21 @@ export default function Chart({
|
|||
xData,
|
||||
yFormat,
|
||||
}: {
|
||||
yData: number[];
|
||||
xData: GymSet[];
|
||||
xFormat: (value: any, index: number) => string;
|
||||
yFormat: (value: any) => string;
|
||||
yData: number[]
|
||||
xData: GymSet[]
|
||||
xFormat: (value: any, index: number) => string
|
||||
yFormat: (value: any) => string
|
||||
}) {
|
||||
const {color} = useColor();
|
||||
const dark = useDark();
|
||||
const {color} = useColor()
|
||||
const dark = useDark()
|
||||
const axesSvg = {
|
||||
fontSize: 10,
|
||||
fill: dark
|
||||
? CombinedDarkTheme.colors.text
|
||||
: CombinedDefaultTheme.colors.text,
|
||||
};
|
||||
const verticalContentInset = {top: 10, bottom: 10};
|
||||
const xAxisHeight = 30;
|
||||
}
|
||||
const verticalContentInset = {top: 10, bottom: 10}
|
||||
const xAxisHeight = 30
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -60,5 +60,5 @@ export default function Chart({
|
|||
</View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Button, Dialog, Portal, Text} from 'react-native-paper';
|
||||
import {Button, Dialog, Portal, Text} from 'react-native-paper'
|
||||
|
||||
export default function ConfirmDialog({
|
||||
title,
|
||||
|
@ -7,11 +7,11 @@ export default function ConfirmDialog({
|
|||
show,
|
||||
setShow,
|
||||
}: {
|
||||
title: string;
|
||||
children: JSX.Element | JSX.Element[] | string;
|
||||
onOk: () => void;
|
||||
show: boolean;
|
||||
setShow: (show: boolean) => void;
|
||||
title: string
|
||||
children: JSX.Element | JSX.Element[] | string
|
||||
onOk: () => void
|
||||
show: boolean
|
||||
setShow: (show: boolean) => void
|
||||
}) {
|
||||
return (
|
||||
<Portal>
|
||||
|
@ -26,5 +26,5 @@ export default function ConfirmDialog({
|
|||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import {DrawerNavigationProp} from '@react-navigation/drawer';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import {Appbar, IconButton} from 'react-native-paper';
|
||||
import {DrawerParamList} from './drawer-param-list';
|
||||
import DrawerMenu from './DrawerMenu';
|
||||
import useDark from './use-dark';
|
||||
import {DrawerNavigationProp} from '@react-navigation/drawer'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {Appbar, IconButton} from 'react-native-paper'
|
||||
import {DrawerParamList} from './drawer-param-list'
|
||||
import DrawerMenu from './DrawerMenu'
|
||||
import useDark from './use-dark'
|
||||
|
||||
export default function DrawerHeader({name}: {name: keyof DrawerParamList}) {
|
||||
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>();
|
||||
const dark = useDark();
|
||||
const navigation = useNavigation<DrawerNavigationProp<DrawerParamList>>()
|
||||
const dark = useDark()
|
||||
|
||||
return (
|
||||
<Appbar.Header>
|
||||
|
@ -19,5 +19,5 @@ export default function DrawerHeader({name}: {name: keyof DrawerParamList}) {
|
|||
<Appbar.Content title={name} />
|
||||
<DrawerMenu name={name} />
|
||||
</Appbar.Header>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
161
DrawerMenu.tsx
161
DrawerMenu.tsx
|
@ -1,72 +1,71 @@
|
|||
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 ConfirmDialog from './ConfirmDialog';
|
||||
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 useDark from './use-dark';
|
||||
import {write} from './write';
|
||||
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 ConfirmDialog from './ConfirmDialog'
|
||||
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 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);
|
||||
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);
|
||||
const [showRemove, setShowRemove] = useState(false);
|
||||
const {toast} = useSnackbar();
|
||||
const {reset} = useNavigation<NavigationProp<DrawerParamList>>();
|
||||
const dark = useDark();
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
const [showRemove, setShowRemove] = useState(false)
|
||||
const {toast} = useSnackbar()
|
||||
const {reset} = useNavigation<NavigationProp<DrawerParamList>>()
|
||||
const dark = useDark()
|
||||
|
||||
const exportSets = useCallback(async () => {
|
||||
const sets = await setRepo.find({});
|
||||
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];
|
||||
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);
|
||||
}, []);
|
||||
.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 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);
|
||||
}, []);
|
||||
.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]);
|
||||
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');
|
||||
console.log(lines[0]);
|
||||
if (!setFields.includes(lines[0])) return toast('Invalid csv.', 3000);
|
||||
const result = await DocumentPicker.pickSingle()
|
||||
const file = await FileSystem.readFile(result.uri)
|
||||
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)
|
||||
const values = lines
|
||||
.slice(1)
|
||||
.filter(line => line)
|
||||
|
@ -82,7 +81,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
|
|||
sets,
|
||||
minutes,
|
||||
seconds,
|
||||
] = line.split(',');
|
||||
] = line.split(',')
|
||||
const set: GymSet = {
|
||||
name: setName,
|
||||
reps: +reps,
|
||||
|
@ -93,21 +92,21 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
|
|||
sets: +sets,
|
||||
minutes: +minutes,
|
||||
seconds: +seconds,
|
||||
};
|
||||
return set;
|
||||
});
|
||||
console.log(`${DrawerMenu.name}.uploadSets:`, {values});
|
||||
await setRepo.insert(values);
|
||||
toast('Data imported.', 3000);
|
||||
reset({index: 0, routes: [{name}]});
|
||||
}, [reset, name, toast]);
|
||||
}
|
||||
return set
|
||||
})
|
||||
console.log(`${DrawerMenu.name}.uploadSets:`, {values})
|
||||
await setRepo.insert(values)
|
||||
toast('Data imported.', 3000)
|
||||
reset({index: 0, routes: [{name}]})
|
||||
}, [reset, name, toast])
|
||||
|
||||
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);
|
||||
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)
|
||||
const values = file
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
|
@ -115,32 +114,32 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
|
|||
.map(set => {
|
||||
const [, days, workouts] = set
|
||||
.split('","')
|
||||
.map(cell => cell.replace(/"/g, ''));
|
||||
.map(cell => cell.replace(/"/g, ''))
|
||||
const plan: Plan = {
|
||||
days,
|
||||
workouts,
|
||||
};
|
||||
return plan;
|
||||
});
|
||||
await planRepo.insert(values);
|
||||
toast('Data imported.', 3000);
|
||||
}, [toast]);
|
||||
}
|
||||
return plan
|
||||
})
|
||||
await planRepo.insert(values)
|
||||
toast('Data imported.', 3000)
|
||||
}, [toast])
|
||||
|
||||
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]);
|
||||
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);
|
||||
if (name === 'Home') await setRepo.delete({});
|
||||
else if (name === 'Plans') await planRepo.delete({});
|
||||
toast('All data has been deleted.', 4000);
|
||||
reset({index: 0, routes: [{name}]});
|
||||
}, [reset, name, toast]);
|
||||
setShowMenu(false)
|
||||
setShowRemove(false)
|
||||
if (name === 'Home') await setRepo.delete({})
|
||||
else if (name === 'Plans') await planRepo.delete({})
|
||||
toast('All data has been deleted.', 4000)
|
||||
reset({index: 0, routes: [{name}]})
|
||||
}, [reset, name, toast])
|
||||
|
||||
if (name === 'Home' || name === 'Plans')
|
||||
return (
|
||||
|
@ -170,7 +169,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) {
|
|||
This irreversibly deletes all data from the app. Are you sure?
|
||||
</ConfirmDialog>
|
||||
</Menu>
|
||||
);
|
||||
)
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
|
76
EditPlan.tsx
76
EditPlan.tsx
|
@ -3,29 +3,29 @@ import {
|
|||
RouteProp,
|
||||
useNavigation,
|
||||
useRoute,
|
||||
} from '@react-navigation/native';
|
||||
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';
|
||||
import {planRepo, setRepo} from './db';
|
||||
import {DrawerParamList} from './drawer-param-list';
|
||||
import {PlanPageParams} from './plan-page-params';
|
||||
import StackHeader from './StackHeader';
|
||||
import Switch from './Switch';
|
||||
import {DAYS} from './time';
|
||||
} from '@react-navigation/native'
|
||||
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'
|
||||
import {planRepo, setRepo} from './db'
|
||||
import {DrawerParamList} from './drawer-param-list'
|
||||
import {PlanPageParams} from './plan-page-params'
|
||||
import StackHeader from './StackHeader'
|
||||
import Switch from './Switch'
|
||||
import {DAYS} from './time'
|
||||
|
||||
export default function EditPlan() {
|
||||
const {params} = useRoute<RouteProp<PlanPageParams, 'EditPlan'>>();
|
||||
const {plan} = params;
|
||||
const {params} = useRoute<RouteProp<PlanPageParams, 'EditPlan'>>()
|
||||
const {plan} = params
|
||||
const [days, setDays] = useState<string[]>(
|
||||
plan.days ? plan.days.split(',') : [],
|
||||
);
|
||||
)
|
||||
const [workouts, setWorkouts] = useState<string[]>(
|
||||
plan.workouts ? plan.workouts.split(',') : [],
|
||||
);
|
||||
const [names, setNames] = useState<string[]>([]);
|
||||
const navigation = useNavigation<NavigationProp<DrawerParamList>>();
|
||||
)
|
||||
const [names, setNames] = useState<string[]>([])
|
||||
const navigation = useNavigation<NavigationProp<DrawerParamList>>()
|
||||
|
||||
useEffect(() => {
|
||||
setRepo
|
||||
|
@ -34,41 +34,41 @@ export default function EditPlan() {
|
|||
.distinct(true)
|
||||
.getRawMany()
|
||||
.then(values => {
|
||||
console.log(EditPlan.name, {values});
|
||||
setNames(values.map(value => value.name));
|
||||
});
|
||||
}, []);
|
||||
console.log(EditPlan.name, {values})
|
||||
setNames(values.map(value => value.name))
|
||||
})
|
||||
}, [])
|
||||
|
||||
const save = useCallback(async () => {
|
||||
console.log(`${EditPlan.name}.save`, {days, workouts, plan});
|
||||
if (!days || !workouts) return;
|
||||
const newWorkouts = workouts.filter(workout => workout).join(',');
|
||||
const newDays = days.filter(day => day).join(',');
|
||||
await planRepo.save({days: newDays, workouts: newWorkouts, id: plan.id});
|
||||
navigation.goBack();
|
||||
}, [days, workouts, plan, navigation]);
|
||||
console.log(`${EditPlan.name}.save`, {days, workouts, plan})
|
||||
if (!days || !workouts) return
|
||||
const newWorkouts = workouts.filter(workout => workout).join(',')
|
||||
const newDays = days.filter(day => day).join(',')
|
||||
await planRepo.save({days: newDays, workouts: newWorkouts, id: plan.id})
|
||||
navigation.goBack()
|
||||
}, [days, workouts, plan, navigation])
|
||||
|
||||
const toggleWorkout = useCallback(
|
||||
(on: boolean, name: string) => {
|
||||
if (on) {
|
||||
setWorkouts([...workouts, name]);
|
||||
setWorkouts([...workouts, name])
|
||||
} else {
|
||||
setWorkouts(workouts.filter(workout => workout !== name));
|
||||
setWorkouts(workouts.filter(workout => workout !== name))
|
||||
}
|
||||
},
|
||||
[setWorkouts, workouts],
|
||||
);
|
||||
)
|
||||
|
||||
const toggleDay = useCallback(
|
||||
(on: boolean, day: string) => {
|
||||
if (on) {
|
||||
setDays([...days, day]);
|
||||
setDays([...days, day])
|
||||
} else {
|
||||
setDays(days.filter(d => d !== day));
|
||||
setDays(days.filter(d => d !== day))
|
||||
}
|
||||
},
|
||||
[setDays, days],
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -107,11 +107,11 @@ export default function EditPlan() {
|
|||
disabled={workouts.length === 0 && days.length === 0}
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
navigation.goBack();
|
||||
navigation.goBack()
|
||||
navigation.navigate('Workouts', {
|
||||
screen: 'EditWorkout',
|
||||
params: {value: {name: ''}},
|
||||
});
|
||||
})
|
||||
}}>
|
||||
Add workout
|
||||
</Button>
|
||||
|
@ -127,7 +127,7 @@ export default function EditPlan() {
|
|||
)}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -135,4 +135,4 @@ const styles = StyleSheet.create({
|
|||
fontSize: 20,
|
||||
marginBottom: MARGIN,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
72
EditSet.tsx
72
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<RouteProp<HomePageParams, 'EditSet'>>();
|
||||
const {set} = params;
|
||||
const navigation = useNavigation();
|
||||
const {toast} = useSnackbar();
|
||||
const {settings} = useSettings();
|
||||
const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>()
|
||||
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() {
|
|||
<SetForm save={save} set={set} />
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
116
EditWorkout.tsx
116
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<RouteProp<WorkoutsPageParams, 'EditWorkout'>>();
|
||||
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<RouteProp<WorkoutsPageParams, 'EditWorkout'>>()
|
||||
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<TextInput>(null);
|
||||
const stepsRef = useRef<TextInput>(null);
|
||||
const minutesRef = useRef<TextInput>(null);
|
||||
const secondsRef = useRef<TextInput>(null);
|
||||
const {settings} = useSettings();
|
||||
)
|
||||
const [sets, setSets] = useState(params.value.sets?.toString() ?? '3')
|
||||
const {toast} = useSnackbar()
|
||||
const navigation = useNavigation()
|
||||
const setsRef = useRef<TextInput>(null)
|
||||
const stepsRef = useRef<TextInput>(null)
|
||||
const minutesRef = useRef<TextInput>(null)
|
||||
const secondsRef = useRef<TextInput>(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() {
|
|||
</ConfirmDialog>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
12
HomePage.tsx
12
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<HomePageParams>();
|
||||
const Stack = createStackNavigator<HomePageParams>()
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
|
@ -12,5 +12,5 @@ export default function HomePage() {
|
|||
<Stack.Screen name="Sets" component={SetList} />
|
||||
<Stack.Screen name="EditSet" component={EditSet} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<ComponentProps<typeof FAB>>) {
|
||||
const {color} = useColor();
|
||||
const {color} = useColor()
|
||||
const fabColor = lightColors.map(lightColor => lightColor.hex).includes(color)
|
||||
? 'black'
|
||||
: undefined;
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<FAB
|
||||
|
@ -21,5 +21,5 @@ export default function MassiveFab(props: Partial<ComponentProps<typeof FAB>>) {
|
|||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<ComponentProps<typeof TextInput>> & {
|
||||
innerRef?: Ref<any>;
|
||||
innerRef?: Ref<any>
|
||||
},
|
||||
) {
|
||||
const dark = useDark();
|
||||
const dark = useDark()
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
|
@ -21,5 +21,5 @@ export default function MassiveInput(
|
|||
blurOnSubmit={false}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
</Snackbar>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
20
Page.tsx
20
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 (
|
||||
<View style={styles.container}>
|
||||
|
@ -26,7 +26,7 @@ export default function Page({
|
|||
{children}
|
||||
{onAdd && <MassiveFab onPress={onAdd} />}
|
||||
</View>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -34,4 +34,4 @@ const styles = StyleSheet.create({
|
|||
flexGrow: 1,
|
||||
padding: PADDING,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
72
PlanItem.tsx
72
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<string>();
|
||||
const days = useMemo(() => item.days.split(','), [item.days]);
|
||||
const navigation = useNavigation<NavigationProp<PlanPageParams>>();
|
||||
const [show, setShow] = useState(false)
|
||||
const [anchor, setAnchor] = useState({x: 0, y: 0})
|
||||
const [today, setToday] = useState<string>()
|
||||
const days = useMemo(() => item.days.split(','), [item.days])
|
||||
const navigation = useNavigation<NavigationProp<PlanPageParams>>()
|
||||
|
||||
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({
|
|||
</Text>
|
||||
)),
|
||||
[days, today],
|
||||
);
|
||||
)
|
||||
|
||||
const description = useMemo(
|
||||
() => item.workouts.replace(/,/g, ', '),
|
||||
[item.workouts],
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -95,5 +95,5 @@ export default function PlanItem({
|
|||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
48
PlanList.tsx
48
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<Plan[]>();
|
||||
const navigation = useNavigation<NavigationProp<PlanPageParams>>();
|
||||
const [term, setTerm] = useState('')
|
||||
const [plans, setPlans] = useState<Plan[]>()
|
||||
const navigation = useNavigation<NavigationProp<PlanPageParams>>()
|
||||
|
||||
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}) => (
|
||||
<PlanItem item={item} key={item.id} onRemove={() => 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() {
|
|||
)}
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
14
PlanPage.tsx
14
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<PlanPageParams>();
|
||||
const Stack = createStackNavigator<PlanPageParams>()
|
||||
|
||||
export default function PlanPage() {
|
||||
return (
|
||||
|
@ -14,5 +14,5 @@ export default function PlanPage() {
|
|||
<Stack.Screen name="EditPlan" component={EditPlan} />
|
||||
<Stack.Screen name="StartPlan" component={StartPlan} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
28
Routes.tsx
28
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<DrawerParamList>();
|
||||
const Drawer = createDrawerNavigator<DrawerParamList>()
|
||||
|
||||
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 (
|
||||
<Drawer.Navigator
|
||||
|
@ -40,5 +40,5 @@ export default function Routes() {
|
|||
/>
|
||||
))}
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
94
SetForm.tsx
94
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<TextInput>(null);
|
||||
const repsRef = useRef<TextInput>(null);
|
||||
const unitRef = useRef<TextInput>(null);
|
||||
})
|
||||
const [removeImage, setRemoveImage] = useState(false)
|
||||
const {toast} = useSnackbar()
|
||||
const {settings} = useSettings()
|
||||
const weightRef = useRef<TextInput>(null)
|
||||
const repsRef = useRef<TextInput>(null)
|
||||
const unitRef = useRef<TextInput>(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?
|
||||
</ConfirmDialog>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
60
SetItem.tsx
60
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<NavigationProp<HomePageParams>>();
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
const [anchor, setAnchor] = useState({x: 0, y: 0})
|
||||
const {settings} = useSettings()
|
||||
const dark = useDark()
|
||||
const navigation = useNavigation<NavigationProp<HomePageParams>>()
|
||||
|
||||
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({
|
|||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
94
SetList.tsx
94
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<GymSet[]>([]);
|
||||
const [set, setSet] = useState<GymSet>();
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [term, setTerm] = useState('');
|
||||
const [end, setEnd] = useState(false);
|
||||
const navigation = useNavigation<NavigationProp<HomePageParams>>();
|
||||
const [sets, setSets] = useState<GymSet[]>([])
|
||||
const [set, setSet] = useState<GymSet>()
|
||||
const [offset, setOffset] = useState(0)
|
||||
const [term, setTerm] = useState('')
|
||||
const [end, setEnd] = useState(false)
|
||||
const navigation = useNavigation<NavigationProp<HomePageParams>>()
|
||||
|
||||
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}) => (
|
||||
<SetItem item={item} key={item.id} onRemove={() => 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() {
|
|||
)}
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
170
SettingsPage.tsx
170
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<boolean>[] = [
|
||||
{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.
|
||||
</ConfirmDialog>
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Appbar.Header>
|
||||
|
@ -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"
|
||||
/>
|
||||
</Appbar.Header>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
154
StartPlan.tsx
154
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<RouteProp<PlanPageParams, 'StartPlan'>>();
|
||||
const [name, setName] = useState('');
|
||||
const [reps, setReps] = useState('');
|
||||
const [weight, setWeight] = useState('');
|
||||
const [unit, setUnit] = useState<string>('kg');
|
||||
const {toast} = useSnackbar();
|
||||
const [minutes, setMinutes] = useState(3);
|
||||
const [seconds, setSeconds] = useState(30);
|
||||
const [best, setBest] = useState<GymSet>();
|
||||
const [selected, setSelected] = useState(0);
|
||||
const {settings} = useSettings();
|
||||
const [counts, setCounts] = useState<CountMany[]>();
|
||||
const weightRef = useRef<TextInput>(null);
|
||||
const repsRef = useRef<TextInput>(null);
|
||||
const unitRef = useRef<TextInput>(null);
|
||||
const workouts = useMemo(() => params.plan.workouts.split(','), [params]);
|
||||
const {params} = useRoute<RouteProp<PlanPageParams, 'StartPlan'>>()
|
||||
const [name, setName] = useState('')
|
||||
const [reps, setReps] = useState('')
|
||||
const [weight, setWeight] = useState('')
|
||||
const [unit, setUnit] = useState<string>('kg')
|
||||
const {toast} = useSnackbar()
|
||||
const [minutes, setMinutes] = useState(3)
|
||||
const [seconds, setSeconds] = useState(30)
|
||||
const [best, setBest] = useState<GymSet>()
|
||||
const [selected, setSelected] = useState(0)
|
||||
const {settings} = useSettings()
|
||||
const [counts, setCounts] = useState<CountMany[]>()
|
||||
const weightRef = useRef<TextInput>(null)
|
||||
const repsRef = useRef<TextInput>(null)
|
||||
const unitRef = useRef<TextInput>(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() {
|
|||
</Button>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<CountMany> {
|
||||
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 (
|
||||
<List.Item
|
||||
|
@ -63,5 +63,5 @@ export default function StartPlanItem(props: Props) {
|
|||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
36
Switch.tsx
36
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 (
|
||||
<Pressable
|
||||
|
@ -50,5 +50,5 @@ export default function Switch({
|
|||
/>
|
||||
<Text>{children}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
79
ViewBest.tsx
79
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<RouteProp<BestPageParams, 'ViewBest'>>();
|
||||
const dark = useDark();
|
||||
const [weights, setWeights] = useState<GymSet[]>([]);
|
||||
const [volumes, setVolumes] = useState<Volume[]>([]);
|
||||
const [metric, setMetric] = useState(Metrics.Weight);
|
||||
const [period, setPeriod] = useState(Periods.Monthly);
|
||||
const {params} = useRoute<RouteProp<BestPageParams, 'ViewBest'>>()
|
||||
const dark = useDark()
|
||||
const [weights, setWeights] = useState<GymSet[]>([])
|
||||
const [volumes, setVolumes] = useState<Volume[]>([])
|
||||
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() {
|
|||
)}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<NavigationProp<WorkoutsPageParams>>();
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
const [anchor, setAnchor] = useState({x: 0, y: 0})
|
||||
const [showRemove, setShowRemove] = useState('')
|
||||
const {settings} = useSettings()
|
||||
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>()
|
||||
|
||||
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({
|
|||
<Menu.Item
|
||||
icon="delete"
|
||||
onPress={() => {
|
||||
setShowRemove(item.name);
|
||||
setShowMenu(false);
|
||||
setShowRemove(item.name)
|
||||
setShowMenu(false)
|
||||
}}
|
||||
title="Delete"
|
||||
/>
|
||||
|
@ -87,5 +87,5 @@ export default function WorkoutItem({
|
|||
sure?
|
||||
</ConfirmDialog>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<GymSet[]>();
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [term, setTerm] = useState('');
|
||||
const [end, setEnd] = useState(false);
|
||||
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>();
|
||||
const [workouts, setWorkouts] = useState<GymSet[]>()
|
||||
const [offset, setOffset] = useState(0)
|
||||
const [term, setTerm] = useState('')
|
||||
const [end, setEnd] = useState(false)
|
||||
const navigation = useNavigation<NavigationProp<WorkoutsPageParams>>()
|
||||
|
||||
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}) => (
|
||||
<WorkoutItem item={item} key={item.name} onRemove={() => 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() {
|
|||
)}
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<WorkoutsPageParams>();
|
||||
const Stack = createStackNavigator<WorkoutsPageParams>()
|
||||
|
||||
export default function WorkoutsPage() {
|
||||
return (
|
||||
|
@ -19,5 +19,5 @@ export default function WorkoutsPage() {
|
|||
<Stack.Screen name="WorkoutList" component={WorkoutList} />
|
||||
<Stack.Screen name="EditWorkout" component={EditWorkout} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<GymSet> => {
|
||||
return setRepo
|
||||
|
@ -11,5 +11,5 @@ export const getBestSet = async (name: string): Promise<GymSet> => {
|
|||
.addGroupBy('reps')
|
||||
.orderBy('weight', 'DESC')
|
||||
.addOrderBy('reps', 'DESC')
|
||||
.getOne();
|
||||
};
|
||||
.getOne()
|
||||
}
|
||||
|
|
10
color.ts
10
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
|
||||
}
|
||||
|
|
30
colors.ts
30
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}`
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export const MARGIN = 10;
|
||||
export const PADDING = 10;
|
||||
export const MARGIN = 10
|
||||
export const PADDING = 10
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default interface CountMany {
|
||||
name: string;
|
||||
total: number;
|
||||
sets?: number;
|
||||
name: string
|
||||
total: number
|
||||
sets?: number
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
});
|
||||
})
|
||||
|
|
34
db.ts
34
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()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export type DrawerParamList = {
|
||||
Home: {};
|
||||
Settings: {};
|
||||
Best: {};
|
||||
Plans: {};
|
||||
Workouts: {};
|
||||
};
|
||||
Home: {}
|
||||
Settings: {}
|
||||
Best: {}
|
||||
Plans: {}
|
||||
Workouts: {}
|
||||
}
|
||||
|
|
26
gym-set.ts
26
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
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import GymSet from './gym-set';
|
||||
import GymSet from './gym-set'
|
||||
|
||||
export type HomePageParams = {
|
||||
Sets: {};
|
||||
Sets: {}
|
||||
EditSet: {
|
||||
set: GymSet;
|
||||
};
|
||||
};
|
||||
set: GymSet
|
||||
}
|
||||
}
|
||||
|
|
6
input.ts
6
input.ts
|
@ -1,5 +1,5 @@
|
|||
export default interface Input<T> {
|
||||
name: string;
|
||||
value?: T;
|
||||
onChange: (value: T) => void;
|
||||
name: string
|
||||
value?: T
|
||||
onChange: (value: T) => void
|
||||
}
|
||||
|
|
22
jestSetup.ts
22
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
|
||||
})
|
||||
|
|
|
@ -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[]
|
||||
}) => (
|
||||
<Color.Provider value={{color, setColor}}>
|
||||
<PaperProvider settings={{icon: props => <MaterialIcon {...props} />}}>
|
||||
|
@ -26,4 +26,4 @@ export const MockProviders = ({
|
|||
</SettingsContext.Provider>
|
||||
</PaperProvider>
|
||||
</Color.Provider>
|
||||
);
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
8
plan.ts
8
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
|
||||
}
|
||||
|
|
8
route.ts
8
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<any>;
|
||||
icon: string;
|
||||
name: keyof DrawerParamList
|
||||
component: React.ComponentType<any>
|
||||
icon: string
|
||||
}
|
||||
|
|
28
settings.ts
28
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
|
||||
}
|
||||
|
|
50
time.ts
50
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
|
||||
}
|
||||
}
|
||||
|
|
14
use-dark.ts
14
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default interface Volume {
|
||||
name: string;
|
||||
created: string;
|
||||
value: number;
|
||||
unit: string;
|
||||
name: string
|
||||
created: string
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
|
|
26
write.ts
26
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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user