diff --git a/app/settings/theme.tsx b/app/settings/theme.tsx index 79feb75..aa1ab13 100644 --- a/app/settings/theme.tsx +++ b/app/settings/theme.tsx @@ -5,7 +5,6 @@ import { StyleSheet, ScrollView, TouchableOpacity, - Switch, ActivityIndicator, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -43,7 +42,7 @@ function ColorSettingRow({ label, color, onPress, colors }: ColorSettingRowProps export default function ThemeSettingsScreen() { const router = useRouter(); - const { isDark, toggleTheme, colors, reloadCustomColors } = useTheme(); + const { isDark, themeMode, setThemeMode, colors, reloadCustomColors } = useTheme(); const { showToast } = useToast(); const { t } = useTranslation(); const [colorPickerVisible, setColorPickerVisible] = useState(false); @@ -89,12 +88,26 @@ export default function ThemeSettingsScreen() { - + + {(['auto', 'light', 'dark'] as const).map((mode) => { + const selected = themeMode === mode; + return ( + setThemeMode(mode)} + activeOpacity={0.7} + > + + {t(`screens.settings.themeMode${mode.charAt(0).toUpperCase()}${mode.slice(1)}`)} + + + ); + })} + @@ -387,6 +400,21 @@ const styles = StyleSheet.create({ fontSize: 12, marginTop: 2, }, + themeModeContainer: { + flexDirection: 'row', + borderWidth: 1, + borderRadius: borderRadius.small, + overflow: 'hidden', + }, + themeModeButton: { + paddingHorizontal: spacing.sm, + paddingVertical: spacing.xs, + }, + themeModeText: { + ...typography.caption, + fontWeight: '600', + textTransform: 'capitalize', + }, separator: { height: 1, marginHorizontal: spacing.md, @@ -408,4 +436,3 @@ const styles = StyleSheet.create({ borderWidth: 2, }, }); - diff --git a/constants/changelog.ts b/constants/changelog.ts index 4706030..1302e6d 100644 --- a/constants/changelog.ts +++ b/constants/changelog.ts @@ -11,6 +11,14 @@ export interface ChangelogRelease { } export const CHANGELOG: ChangelogRelease[] = [ + { + version: '3.2.2', + date: '2026-05-29', + changes: [ + 'Added an Auto theme mode option that follows the iOS system light/dark appearance', + 'Theme settings now include Auto, Light, and Dark mode choices', + ], + }, { version: '3.2.1', date: '2026-05-29', diff --git a/context/ThemeContext.tsx b/context/ThemeContext.tsx index 96e18cb..75c9a00 100644 --- a/context/ThemeContext.tsx +++ b/context/ThemeContext.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { useColorScheme } from 'react-native'; import { storageService } from '@/services/storage'; import { colorThemeManager, ColorTheme } from '@/services/color-theme-manager'; @@ -28,7 +29,9 @@ export interface ThemeColors { interface ThemeContextType { isDark: boolean; - toggleTheme: () => void; + themeMode: 'auto' | 'light' | 'dark'; + setThemeMode: (mode: 'auto' | 'light' | 'dark') => Promise; + toggleTheme: () => Promise; reloadCustomColors: () => Promise; colors: ThemeColors; } @@ -109,9 +112,11 @@ const trueBlackColors = { const ThemeContext = createContext(undefined); export function ThemeProvider({ children }: { children: ReactNode }) { - const [isDark, setIsDark] = useState(true); // Default to dark theme + const systemColorScheme = useColorScheme(); + const [themeMode, setThemeModeState] = useState<'auto' | 'light' | 'dark'>('dark'); const [isLoading, setIsLoading] = useState(true); const [customColors, setCustomColors] = useState(null); + const isDark = themeMode === 'auto' ? systemColorScheme !== 'light' : themeMode === 'dark'; useEffect(() => { loadThemePreference(); @@ -128,7 +133,11 @@ export function ThemeProvider({ children }: { children: ReactNode }) { const preferences = await storageService.getPreferences(); const savedTheme = preferences.theme; if (savedTheme !== undefined) { - setIsDark(savedTheme === true || savedTheme === 'dark'); + if (savedTheme === 'auto' || savedTheme === 'light' || savedTheme === 'dark') { + setThemeModeState(savedTheme); + } else { + setThemeModeState(savedTheme === true ? 'dark' : 'light'); + } } // If no saved preference, default to dark (already set) } catch (error) { @@ -147,23 +156,27 @@ export function ThemeProvider({ children }: { children: ReactNode }) { } }; - const toggleTheme = async () => { - const newTheme = !isDark; - setIsDark(newTheme); + const setThemeMode = async (mode: 'auto' | 'light' | 'dark') => { + setThemeModeState(mode); try { const preferences = await storageService.getPreferences(); await storageService.savePreferences({ ...preferences, - theme: newTheme ? 'dark' : 'light', + theme: mode, }); - // Reload custom colors for new theme - const custom = await colorThemeManager.getCustomColors(newTheme); + const nextIsDark = mode === 'auto' ? systemColorScheme !== 'light' : mode === 'dark'; + const custom = await colorThemeManager.getCustomColors(nextIsDark); setCustomColors(custom); } catch (error) { // Ignore theme saving errors } }; + const toggleTheme = async () => { + const newMode = isDark ? 'light' : 'dark'; + await setThemeMode(newMode); + }; + const baseColors = isDark ? darkColors : lightColors; const colors = colorThemeManager.mergeColors(baseColors, customColors) as typeof baseColors; @@ -172,6 +185,8 @@ export function ThemeProvider({ children }: { children: ReactNode }) { return (