diff --git a/App.tsx b/App.tsx index baa1e0c..783db43 100644 --- a/App.tsx +++ b/App.tsx @@ -3,7 +3,7 @@ import { DefaultTheme as NavigationDefaultTheme, NavigationContainer, } from '@react-navigation/native'; -import React, {useState} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {useColorScheme} from 'react-native'; import { DarkTheme as PaperDarkTheme, @@ -12,8 +12,12 @@ import { } from 'react-native-paper'; import Ionicon from 'react-native-vector-icons/MaterialIcons'; import {lightColors} from './colors'; +import {runMigrations} from './db'; import MassiveSnack from './MassiveSnack'; import Routes from './Routes'; +import Settings from './settings'; +import {getSettings} from './settings.service'; +import {SettingsContext} from './use-settings'; export const CombinedDefaultTheme = { ...NavigationDefaultTheme, @@ -40,21 +44,36 @@ export const CustomTheme = React.createContext({ }); const App = () => { - const dark = useColorScheme() === 'dark'; + const isDark = useColorScheme() === 'dark'; + const [settings, setSettings] = useState(); const [color, setColor] = useState( - dark + isDark ? CombinedDarkTheme.colors.primary.toUpperCase() : CombinedDefaultTheme.colors.primary.toUpperCase(), ); - const theme = dark - ? { - ...CombinedDarkTheme, - colors: {...CombinedDarkTheme.colors, primary: color}, - } - : { - ...CombinedDefaultTheme, - colors: {...CombinedDefaultTheme.colors, primary: color}, - }; + + useEffect(() => { + runMigrations().then(async () => { + const gotSettings = await getSettings(); + 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]); return ( @@ -63,7 +82,11 @@ const App = () => { settings={{icon: props => }}> - + {settings && ( + + + + )} diff --git a/MassiveInput.tsx b/MassiveInput.tsx index f532f93..0ee3f49 100644 --- a/MassiveInput.tsx +++ b/MassiveInput.tsx @@ -1,15 +1,15 @@ import React from 'react'; -import {useColorScheme} from 'react-native'; 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> & { innerRef?: React.Ref; }, ) { - const dark = useColorScheme() === 'dark'; + const dark = useDark(); return ( void; @@ -10,11 +9,10 @@ export const SnackbarContext = React.createContext<{ 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 = useColorScheme() === 'dark'; const {color} = useContext(CustomTheme); const toast = (value: string, timeout: number) => { @@ -35,7 +33,7 @@ export default function MassiveSnack({ action={{ label: 'Close', onPress: () => setSnackbar(''), - color: dark ? CombinedDarkTheme.colors.background : color, + color, }}> {snackbar} diff --git a/Routes.tsx b/Routes.tsx index b7ab016..c6cd3ff 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -1,36 +1,19 @@ import {createDrawerNavigator} from '@react-navigation/drawer'; -import React, {useContext, useEffect, useState} from 'react'; -import {useColorScheme} from 'react-native'; +import React from 'react'; import {IconButton} from 'react-native-paper'; -import {CustomTheme} from './App'; import BestPage from './BestPage'; -import {runMigrations} from './db'; import {DrawerParamList} from './drawer-param-list'; import HomePage from './HomePage'; import PlanPage from './PlanPage'; import Route from './route'; -import Settings from './settings'; -import {getSettings} from './settings.service'; import SettingsPage from './SettingsPage'; -import {SettingsContext} from './use-settings'; +import useDark from './use-dark'; import WorkoutsPage from './WorkoutsPage'; const Drawer = createDrawerNavigator(); export default function Routes() { - const [settings, setSettings] = useState(); - const dark = useColorScheme() === 'dark'; - const {setColor} = useContext(CustomTheme); - - useEffect(() => { - runMigrations().then(async () => { - const gotSettings = await getSettings(); - setSettings(gotSettings); - if (gotSettings.color) setColor(gotSettings.color); - }); - }, [setColor]); - - if (!settings) return null; + const dark = useDark(); const routes: Route[] = [ {name: 'Home', component: HomePage, icon: 'home'}, @@ -41,23 +24,21 @@ export default function Routes() { ]; return ( - - - {routes.map(route => ( - , - }} - /> - ))} - - + + {routes.map(route => ( + , + }} + /> + ))} + ); } diff --git a/SettingsPage.tsx b/SettingsPage.tsx index b98063f..7c1ea51 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -30,6 +30,7 @@ export default function SettingsPage() { const [workouts, setWorkouts] = useState(!!settings.workouts); const [steps, setSteps] = useState(!!settings.steps); const [date, setDate] = useState(settings.date || '%Y-%m-%d %H:%M'); + const [theme, setTheme] = useState(settings.theme || 'system'); const [showDate, setShowDate] = useState(!!settings.showDate); const {color, setColor} = useContext(CustomTheme); const {toast} = useContext(SnackbarContext); @@ -54,6 +55,7 @@ export default function SettingsPage() { steps: +steps, date, showDate: +showDate, + theme, }); getSettings().then(setSettings); }, [ @@ -70,6 +72,7 @@ export default function SettingsPage() { setSettings, date, showDate, + theme, ]); const changeAlarmEnabled = useCallback( @@ -182,9 +185,36 @@ export default function SettingsPage() { {input.name} ))} + {'theme'.includes(search.toLowerCase()) && ( + setTheme(value)}> + + + + + )} + {'color'.includes(search.toLowerCase()) && ( + setColor(value)}> + {lightColors.concat(darkColors).map(colorOption => ( + + ))} + + )} {'new set'.includes(search.toLowerCase()) && ( setNewSet(value)}> @@ -193,22 +223,6 @@ export default function SettingsPage() { )} - {'theme'.includes(search.toLowerCase()) && ( - setColor(value)}> - {darkColors.concat(lightColors).map(colorOption => ( - - ))} - - )} {'date format'.includes(search.toLowerCase()) && ( { + if (dark) + return { + false: CombinedDarkTheme.colors.placeholder, + true: colorShade(color, -40), + }; + return { + false: CombinedDefaultTheme.colors.placeholder, + true: colorShade(color, -40), + }; + }, [dark, color]); return ( >(); - const dark = useColorScheme() === 'dark'; + const dark = useDark(); const [weights, setWeights] = useState([]); const [volumes, setVolumes] = useState([]); const [metric, setMetric] = useState(Metrics.Weight); diff --git a/colors.ts b/colors.ts index be71300..3491c72 100644 --- a/colors.ts +++ b/colors.ts @@ -11,3 +11,26 @@ export const darkColors = [ {hex: '#000000', name: 'Black'}, {hex: '#CD5C5C', name: 'Red'}, ]; + +export const colorShade = (color: any, amount: number) => { + color = color.replace(/^#/, ''); + if (color.length === 3) + color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]; + + 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); + + 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}`; +}; diff --git a/db.ts b/db.ts index 27bee51..b339be5 100644 --- a/db.ts +++ b/db.ts @@ -112,6 +112,9 @@ const migrations = [ ` ALTER TABLE settings ADD COLUMN showDate BOOLEAN DEFAULT 0 `, + ` + ALTER TABLE settings ADD COLUMN theme TEXT + `, ]; export let db: SQLiteDatabase; diff --git a/deploy.sh b/deploy.sh index a6580fc..5008c23 100755 --- a/deploy.sh +++ b/deploy.sh @@ -26,3 +26,4 @@ git commit --no-verify --message "Set versionCode=$versionCode" git tag "$versionCode" git push origin HEAD & git push --tags cd .. +./install.sh diff --git a/settings.ts b/settings.ts index e23cd13..aa5ad3d 100644 --- a/settings.ts +++ b/settings.ts @@ -12,4 +12,5 @@ export default interface Settings { steps?: number; date?: string; showDate: number; + theme?: 'system' | 'dark' | 'light'; } diff --git a/use-dark.ts b/use-dark.ts new file mode 100644 index 0000000..5629c3f --- /dev/null +++ b/use-dark.ts @@ -0,0 +1,11 @@ +import {useColorScheme} from 'react-native'; +import {useSettings} from './use-settings'; + +export default function useDark() { + const dark = useColorScheme() === 'dark'; + const {settings} = useSettings(); + + if (settings.theme === 'dark') return true; + if (settings.theme === 'light') return false; + return dark; +}