parent
512c0d0467
commit
e516cdfdc0
42
App.tsx
42
App.tsx
|
@ -4,7 +4,7 @@ import {
|
||||||
DefaultTheme as NavigationDefaultTheme,
|
DefaultTheme as NavigationDefaultTheme,
|
||||||
NavigationContainer,
|
NavigationContainer,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import React from 'react';
|
import React, {useState} from 'react';
|
||||||
import {useColorScheme} from 'react-native';
|
import {useColorScheme} from 'react-native';
|
||||||
import {
|
import {
|
||||||
DarkTheme as PaperDarkTheme,
|
DarkTheme as PaperDarkTheme,
|
||||||
|
@ -44,20 +44,40 @@ export const CombinedDarkTheme = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CustomTheme = React.createContext({
|
||||||
|
color: '',
|
||||||
|
setColor: (_value: string) => {},
|
||||||
|
});
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const dark = useColorScheme() === 'dark';
|
const dark = useColorScheme() === 'dark';
|
||||||
|
const [color, setColor] = useState(
|
||||||
|
dark
|
||||||
|
? CombinedDarkTheme.colors.primary
|
||||||
|
: CombinedDefaultTheme.colors.primary,
|
||||||
|
);
|
||||||
|
const theme = dark
|
||||||
|
? {
|
||||||
|
...CombinedDarkTheme,
|
||||||
|
colors: {...CombinedDarkTheme.colors, primary: color},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...CombinedDefaultTheme,
|
||||||
|
colors: {...CombinedDefaultTheme.colors, primary: color},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider
|
<CustomTheme.Provider value={{color, setColor}}>
|
||||||
theme={dark ? CombinedDarkTheme : CombinedDefaultTheme}
|
<Provider
|
||||||
settings={{icon: props => <Ionicon {...props} />}}>
|
theme={theme}
|
||||||
<NavigationContainer
|
settings={{icon: props => <Ionicon {...props} />}}>
|
||||||
theme={dark ? CombinedDarkTheme : CombinedDefaultTheme}>
|
<NavigationContainer theme={theme}>
|
||||||
<MassiveSnack>
|
<MassiveSnack>
|
||||||
<Routes />
|
<Routes />
|
||||||
</MassiveSnack>
|
</MassiveSnack>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
</CustomTheme.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
12
Chart.tsx
12
Chart.tsx
|
@ -1,8 +1,8 @@
|
||||||
import * as shape from 'd3-shape';
|
import * as shape from 'd3-shape';
|
||||||
import React from 'react';
|
import React, {useContext} from 'react';
|
||||||
import {useColorScheme, View} from 'react-native';
|
import {View} from 'react-native';
|
||||||
import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts';
|
import {Grid, LineChart, XAxis, YAxis} from 'react-native-svg-charts';
|
||||||
import {CombinedDarkTheme, CombinedDefaultTheme} from './App';
|
import {CustomTheme} from './App';
|
||||||
import {MARGIN, PADDING} from './constants';
|
import {MARGIN, PADDING} from './constants';
|
||||||
import Set from './set';
|
import Set from './set';
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ export default function Chart({
|
||||||
xFormat: (value: any, index: number) => string;
|
xFormat: (value: any, index: number) => string;
|
||||||
yFormat: (value: any) => string;
|
yFormat: (value: any) => string;
|
||||||
}) {
|
}) {
|
||||||
const dark = useColorScheme() === 'dark';
|
const {color} = useContext(CustomTheme);
|
||||||
const axesSvg = {fontSize: 10, fill: 'grey'};
|
const axesSvg = {fontSize: 10, fill: 'grey'};
|
||||||
const verticalContentInset = {top: 10, bottom: 10};
|
const verticalContentInset = {top: 10, bottom: 10};
|
||||||
const xAxisHeight = 30;
|
const xAxisHeight = 30;
|
||||||
|
@ -39,9 +39,7 @@ export default function Chart({
|
||||||
contentInset={verticalContentInset}
|
contentInset={verticalContentInset}
|
||||||
curve={shape.curveBasis}
|
curve={shape.curveBasis}
|
||||||
svg={{
|
svg={{
|
||||||
stroke: dark
|
stroke: color,
|
||||||
? CombinedDarkTheme.colors.primary
|
|
||||||
: CombinedDefaultTheme.colors.primary,
|
|
||||||
}}>
|
}}>
|
||||||
<Grid />
|
<Grid />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import React from 'react';
|
import React, {useContext} from 'react';
|
||||||
import {useColorScheme} from 'react-native';
|
import {useColorScheme} from 'react-native';
|
||||||
import {FAB} from 'react-native-paper';
|
import {FAB} from 'react-native-paper';
|
||||||
import {CombinedDarkTheme, CombinedDefaultTheme} from './App';
|
import {CombinedDarkTheme, CustomTheme} from './App';
|
||||||
|
|
||||||
export default function MassiveFab(
|
export default function MassiveFab(
|
||||||
props: Partial<React.ComponentProps<typeof FAB>>,
|
props: Partial<React.ComponentProps<typeof FAB>>,
|
||||||
) {
|
) {
|
||||||
const dark = useColorScheme() === 'dark';
|
const dark = useColorScheme() === 'dark';
|
||||||
|
const {color} = useContext(CustomTheme);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FAB
|
<FAB
|
||||||
|
@ -17,9 +18,7 @@ export default function MassiveFab(
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 10,
|
right: 10,
|
||||||
bottom: 60,
|
bottom: 60,
|
||||||
backgroundColor: dark
|
backgroundColor: color,
|
||||||
? CombinedDarkTheme.colors.primary
|
|
||||||
: CombinedDefaultTheme.colors.primary,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, {useState} from 'react';
|
import React, {useContext, useState} from 'react';
|
||||||
import {useColorScheme} from 'react-native';
|
import {useColorScheme} from 'react-native';
|
||||||
import {Snackbar} from 'react-native-paper';
|
import {Snackbar} from 'react-native-paper';
|
||||||
import {CombinedDarkTheme, CombinedDefaultTheme} from './App';
|
import {CombinedDarkTheme, CustomTheme} from './App';
|
||||||
|
|
||||||
export const SnackbarContext = React.createContext<{
|
export const SnackbarContext = React.createContext<{
|
||||||
toast: (value: string, timeout: number) => void;
|
toast: (value: string, timeout: number) => void;
|
||||||
|
@ -11,6 +11,7 @@ const MassiveSnack = ({children}: {children: JSX.Element[] | JSX.Element}) => {
|
||||||
const [snackbar, setSnackbar] = useState('');
|
const [snackbar, setSnackbar] = useState('');
|
||||||
const [timeoutId, setTimeoutId] = useState(0);
|
const [timeoutId, setTimeoutId] = useState(0);
|
||||||
const dark = useColorScheme() === 'dark';
|
const dark = useColorScheme() === 'dark';
|
||||||
|
const {color} = useContext(CustomTheme);
|
||||||
|
|
||||||
const toast = (value: string, timeout: number) => {
|
const toast = (value: string, timeout: number) => {
|
||||||
setSnackbar(value);
|
setSnackbar(value);
|
||||||
|
@ -30,9 +31,7 @@ const MassiveSnack = ({children}: {children: JSX.Element[] | JSX.Element}) => {
|
||||||
action={{
|
action={{
|
||||||
label: 'Close',
|
label: 'Close',
|
||||||
onPress: () => setSnackbar(''),
|
onPress: () => setSnackbar(''),
|
||||||
color: dark
|
color: dark ? CombinedDarkTheme.colors.background : color,
|
||||||
? CombinedDarkTheme.colors.background
|
|
||||||
: CombinedDefaultTheme.colors.primary,
|
|
||||||
}}>
|
}}>
|
||||||
{snackbar}
|
{snackbar}
|
||||||
</Snackbar>
|
</Snackbar>
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
import React from 'react';
|
import React, {useContext} from 'react';
|
||||||
import {useColorScheme} from 'react-native';
|
|
||||||
import {Switch} from 'react-native-paper';
|
import {Switch} from 'react-native-paper';
|
||||||
import {CombinedDarkTheme, CombinedDefaultTheme} from './App';
|
import {CustomTheme} from './App';
|
||||||
import {MARGIN} from './constants';
|
import {MARGIN} from './constants';
|
||||||
|
|
||||||
export default function MassiveSwitch(
|
export default function MassiveSwitch(
|
||||||
props: Partial<React.ComponentProps<typeof Switch>>,
|
props: Partial<React.ComponentProps<typeof Switch>>,
|
||||||
) {
|
) {
|
||||||
const dark = useColorScheme() === 'dark';
|
const {color} = useContext(CustomTheme);
|
||||||
|
|
||||||
return (
|
return <Switch color={color} style={{marginRight: MARGIN}} {...props} />;
|
||||||
<Switch
|
|
||||||
color={
|
|
||||||
dark
|
|
||||||
? CombinedDarkTheme.colors.primary
|
|
||||||
: CombinedDefaultTheme.colors.primary
|
|
||||||
}
|
|
||||||
style={{marginRight: MARGIN}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
14
Routes.tsx
14
Routes.tsx
|
@ -1,12 +1,12 @@
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useContext, useEffect, useState} from 'react';
|
||||||
import {useColorScheme} from 'react-native';
|
import {useColorScheme} from 'react-native';
|
||||||
import {IconButton} from 'react-native-paper';
|
import {IconButton} from 'react-native-paper';
|
||||||
import {Drawer, DrawerParamList} from './App';
|
import {CustomTheme, Drawer, DrawerParamList} from './App';
|
||||||
import BestPage from './BestPage';
|
import BestPage from './BestPage';
|
||||||
import {runMigrations} from './db';
|
import {runMigrations} from './db';
|
||||||
import HomePage from './HomePage';
|
import HomePage from './HomePage';
|
||||||
import PlanPage from './PlanPage';
|
import PlanPage from './PlanPage';
|
||||||
import {getSettings} from './settings.service';
|
import {getSettings, settings} from './settings.service';
|
||||||
import SettingsPage from './SettingsPage';
|
import SettingsPage from './SettingsPage';
|
||||||
import WorkoutsPage from './WorkoutsPage';
|
import WorkoutsPage from './WorkoutsPage';
|
||||||
|
|
||||||
|
@ -19,12 +19,16 @@ interface Route {
|
||||||
export default function Routes() {
|
export default function Routes() {
|
||||||
const [migrated, setMigrated] = useState(false);
|
const [migrated, setMigrated] = useState(false);
|
||||||
const dark = useColorScheme() === 'dark';
|
const dark = useColorScheme() === 'dark';
|
||||||
|
const {setColor} = useContext(CustomTheme);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
runMigrations()
|
runMigrations()
|
||||||
.then(getSettings)
|
.then(getSettings)
|
||||||
.then(() => setMigrated(true));
|
.then(() => setMigrated(true))
|
||||||
}, []);
|
.then(() => {
|
||||||
|
if (settings.color) setColor(settings.color);
|
||||||
|
});
|
||||||
|
}, [setColor]);
|
||||||
|
|
||||||
if (!migrated) return null;
|
if (!migrated) return null;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import {Picker} from '@react-native-picker/picker';
|
||||||
import {useFocusEffect} from '@react-navigation/native';
|
import {useFocusEffect} from '@react-navigation/native';
|
||||||
import React, {useCallback, useContext, useEffect, useState} from 'react';
|
import React, {useCallback, useContext, useEffect, useState} from 'react';
|
||||||
import {NativeModules, ScrollView} from 'react-native';
|
import {NativeModules, ScrollView, StyleSheet} from 'react-native';
|
||||||
import DocumentPicker from 'react-native-document-picker';
|
import DocumentPicker from 'react-native-document-picker';
|
||||||
import {Button, Text} from 'react-native-paper';
|
import {Button, Text} from 'react-native-paper';
|
||||||
|
import {CustomTheme} from './App';
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
import {MARGIN} from './constants';
|
import {MARGIN} from './constants';
|
||||||
import {SnackbarContext} from './MassiveSnack';
|
import {SnackbarContext} from './MassiveSnack';
|
||||||
|
@ -27,6 +29,7 @@ export default function SettingsPage() {
|
||||||
const [ignoring, setIgnoring] = useState(false);
|
const [ignoring, setIgnoring] = useState(false);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [showUnit, setShowUnit] = useState(true);
|
const [showUnit, setShowUnit] = useState(true);
|
||||||
|
const {color, setColor} = useContext(CustomTheme);
|
||||||
const {toast} = useContext(SnackbarContext);
|
const {toast} = useContext(SnackbarContext);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
|
@ -35,7 +38,7 @@ export default function SettingsPage() {
|
||||||
setAlarm(!!settings.alarm);
|
setAlarm(!!settings.alarm);
|
||||||
setPredict(!!settings.predict);
|
setPredict(!!settings.predict);
|
||||||
setVibrate(!!settings.vibrate);
|
setVibrate(!!settings.vibrate);
|
||||||
setSound(settings.sound);
|
setSound(settings.sound ?? '');
|
||||||
setNotify(!!settings.notify);
|
setNotify(!!settings.notify);
|
||||||
setImages(!!settings.images);
|
setImages(!!settings.images);
|
||||||
NativeModules.AlarmModule.ignoringBattery(setIgnoring);
|
NativeModules.AlarmModule.ignoringBattery(setIgnoring);
|
||||||
|
@ -51,9 +54,10 @@ export default function SettingsPage() {
|
||||||
notify: +notify,
|
notify: +notify,
|
||||||
images: +images,
|
images: +images,
|
||||||
showUnit: +showUnit,
|
showUnit: +showUnit,
|
||||||
|
color,
|
||||||
});
|
});
|
||||||
getSettings();
|
getSettings();
|
||||||
}, [vibrate, alarm, predict, sound, notify, images, showUnit]);
|
}, [vibrate, alarm, predict, sound, notify, images, showUnit, color]);
|
||||||
|
|
||||||
const changeAlarmEnabled = useCallback(
|
const changeAlarmEnabled = useCallback(
|
||||||
(enabled: boolean) => {
|
(enabled: boolean) => {
|
||||||
|
@ -131,16 +135,16 @@ export default function SettingsPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page search={search} setSearch={setSearch}>
|
<Page search={search} setSearch={setSearch}>
|
||||||
<ScrollView style={{margin: MARGIN}}>
|
<ScrollView style={{marginTop: MARGIN}}>
|
||||||
{switches
|
{switches
|
||||||
.filter(input =>
|
.filter(input =>
|
||||||
input.name.toLowerCase().includes(search.toLowerCase()),
|
input.name.toLowerCase().includes(search.toLowerCase()),
|
||||||
)
|
)
|
||||||
.map(input => (
|
.map(input => (
|
||||||
<React.Fragment key={input.name}>
|
<React.Fragment key={input.name}>
|
||||||
<Text style={{marginBottom: MARGIN}}>{input.name}</Text>
|
<Text style={styles.item}>{input.name}</Text>
|
||||||
<MassiveSwitch
|
<MassiveSwitch
|
||||||
style={{alignSelf: 'flex-start', marginBottom: MARGIN}}
|
style={styles.item}
|
||||||
value={input.value}
|
value={input.value}
|
||||||
onValueChange={input.onChange}
|
onValueChange={input.onChange}
|
||||||
/>
|
/>
|
||||||
|
@ -154,6 +158,18 @@ export default function SettingsPage() {
|
||||||
: null}
|
: null}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{'color'.includes(search.toLowerCase()) && (
|
||||||
|
<Picker
|
||||||
|
style={{color}}
|
||||||
|
dropdownIconColor={color}
|
||||||
|
selectedValue={color}
|
||||||
|
onValueChange={value => setColor(value)}>
|
||||||
|
<Picker.Item value="#B3E5fC" label="Cyan theme" color="#B3E5fC" />
|
||||||
|
<Picker.Item value="#8156a7" label="Purple theme" color="#8156a7" />
|
||||||
|
<Picker.Item value="#007AFF" label="Blue theme" color="#007AFF" />
|
||||||
|
<Picker.Item value="#ffc0cb" label="Pink theme" color="#ffc0cb" />
|
||||||
|
</Picker>
|
||||||
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
title="Battery optimizations"
|
title="Battery optimizations"
|
||||||
|
@ -168,3 +184,11 @@ export default function SettingsPage() {
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
item: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
marginBottom: MARGIN,
|
||||||
|
marginLeft: MARGIN,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
3
db.ts
3
db.ts
|
@ -88,6 +88,9 @@ const migrations = [
|
||||||
`
|
`
|
||||||
DROP TABLE workouts
|
DROP TABLE workouts
|
||||||
`,
|
`,
|
||||||
|
`
|
||||||
|
ALTER TABLE settings ADD COLUMN color TEXT NULL
|
||||||
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
export let db: SQLiteDatabase;
|
export let db: SQLiteDatabase;
|
||||||
|
|
|
@ -2,8 +2,9 @@ export default interface Settings {
|
||||||
alarm: number;
|
alarm: number;
|
||||||
vibrate: number;
|
vibrate: number;
|
||||||
predict: number;
|
predict: number;
|
||||||
sound: string;
|
sound?: string;
|
||||||
notify: number;
|
notify?: number;
|
||||||
images: number;
|
images?: number;
|
||||||
showUnit: number;
|
showUnit?: number;
|
||||||
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue