import React, { createContext, useState, useContext, useEffect, ReactNode } from 'react'; import { mmkvStorage } from '../services/mmkvStorage'; import { settingsEmitter } from '../hooks/useSettings'; import { colors as defaultColors } from '../styles/colors'; // Define the Theme interface export interface Theme { id: string; name: string; colors: typeof defaultColors; isEditable: boolean; } // Default built-in themes export const DEFAULT_THEMES: Theme[] = [ { id: 'default', name: 'Default Dark', colors: defaultColors, isEditable: false, }, { id: 'ocean', name: 'Ocean Blue', colors: { ...defaultColors, primary: '#3498db', secondary: '#2ecc71', darkBackground: '#0a192f', }, isEditable: false, }, { id: 'sunset', name: 'Sunset', colors: { ...defaultColors, primary: '#ff7e5f', secondary: '#feb47b', darkBackground: '#1a0f0b', }, isEditable: false, }, { id: 'moonlight', name: 'Moonlight', colors: { ...defaultColors, primary: '#c084fc', secondary: '#60a5fa', darkBackground: '#060609', }, isEditable: false, }, { id: 'emerald', name: 'Emerald', colors: { ...defaultColors, primary: '#2ecc71', secondary: '#3498db', darkBackground: '#0e1e13', }, isEditable: false, }, { id: 'ruby', name: 'Ruby', colors: { ...defaultColors, primary: '#e74c3c', secondary: '#9b59b6', darkBackground: '#1a0a0a', }, isEditable: false, }, { id: 'amethyst', name: 'Amethyst', colors: { ...defaultColors, primary: '#9b59b6', secondary: '#3498db', darkBackground: '#140a1c', }, isEditable: false, }, { id: 'amber', name: 'Amber', colors: { ...defaultColors, primary: '#f39c12', secondary: '#d35400', darkBackground: '#1a140a', }, isEditable: false, }, { id: 'mint', name: 'Mint', colors: { ...defaultColors, primary: '#1abc9c', secondary: '#16a085', darkBackground: '#0a1a17', }, isEditable: false, }, { id: 'slate', name: 'Slate', colors: { ...defaultColors, primary: '#7f8c8d', secondary: '#95a5a6', darkBackground: '#10191a', }, isEditable: false, }, { id: 'neon', name: 'Neon', colors: { ...defaultColors, primary: '#00ff00', secondary: '#ff00ff', darkBackground: '#0a0a0a', }, isEditable: false, }, { id: 'retro', name: 'Retro Wave', colors: { ...defaultColors, primary: '#ff00ff', secondary: '#00ffff', darkBackground: '#150036', }, isEditable: false, }, ]; // Theme context props interface ThemeContextProps { currentTheme: Theme; availableThemes: Theme[]; setCurrentTheme: (themeId: string) => void; addCustomTheme: (theme: Omit) => void; updateCustomTheme: (theme: Theme) => void; deleteCustomTheme: (themeId: string) => void; } // Create the context const ThemeContext = createContext(undefined); // Storage keys (kept for backward compatibility). Primary source of truth is app_settings const CURRENT_THEME_KEY = 'current_theme'; const CUSTOM_THEMES_KEY = 'custom_themes'; // Provider component export function ThemeProvider({ children }: { children: ReactNode }) { const [currentTheme, setCurrentThemeState] = useState(DEFAULT_THEMES[0]); const [availableThemes, setAvailableThemes] = useState(DEFAULT_THEMES); // Load themes from app_settings (scoped), with legacy fallbacks useEffect(() => { const loadThemes = async () => { try { const scope = (await mmkvStorage.getItem('@user:current')) || 'local'; const appSettingsJson = await mmkvStorage.getItem(`@user:${scope}:app_settings`); const appSettings = appSettingsJson ? JSON.parse(appSettingsJson) : {}; const savedThemeId = appSettings.themeId || (await mmkvStorage.getItem(CURRENT_THEME_KEY)); const customThemesJson = appSettings.customThemes ? JSON.stringify(appSettings.customThemes) : await mmkvStorage.getItem(CUSTOM_THEMES_KEY); const customThemes = customThemesJson ? JSON.parse(customThemesJson) : []; const allThemes = [...DEFAULT_THEMES, ...customThemes]; setAvailableThemes(allThemes); if (savedThemeId) { const theme = allThemes.find(t => t.id === savedThemeId); if (theme) setCurrentThemeState(theme); } } catch (error) { if (__DEV__) console.error('Failed to load themes:', error); } }; loadThemes(); // Stop live refresh from remote; only refresh on app restart or local changes return () => {}; }, []); // Set current theme const setCurrentTheme = async (themeId: string) => { const theme = availableThemes.find(t => t.id === themeId); if (theme) { setCurrentThemeState(theme); // Persist into scoped app_settings and legacy key for backward compat const scope = (await mmkvStorage.getItem('@user:current')) || 'local'; const key = `@user:${scope}:app_settings`; let settings = {} as any; try { settings = JSON.parse((await mmkvStorage.getItem(key)) || '{}'); } catch {} settings.themeId = themeId; await mmkvStorage.setItem(key, JSON.stringify(settings)); await mmkvStorage.setItem(CURRENT_THEME_KEY, themeId); // Do not emit global settings sync for themes (sync on app restart only) } }; // Add custom theme const addCustomTheme = async (themeData: Omit) => { try { // Generate unique ID const id = `custom_${Date.now()}`; // Create new theme object const newTheme: Theme = { id, ...themeData, isEditable: true, }; // Add to available themes const customThemes = availableThemes.filter(t => t.isEditable); const updatedCustomThemes = [...customThemes, newTheme]; const updatedAllThemes = [...DEFAULT_THEMES, ...updatedCustomThemes]; // Save to storage (scoped app_settings + legacy key) const scope = (await mmkvStorage.getItem('@user:current')) || 'local'; const key = `@user:${scope}:app_settings`; let settings = {} as any; try { settings = JSON.parse((await mmkvStorage.getItem(key)) || '{}'); } catch {} settings.customThemes = updatedCustomThemes; await mmkvStorage.setItem(key, JSON.stringify(settings)); await mmkvStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(updatedCustomThemes)); // Update state setAvailableThemes(updatedAllThemes); // Set as current theme setCurrentThemeState(newTheme); await mmkvStorage.setItem(CURRENT_THEME_KEY, id); // Do not emit global settings sync for themes } catch (error) { if (__DEV__) console.error('Failed to add custom theme:', error); } }; // Update custom theme const updateCustomTheme = async (updatedTheme: Theme) => { try { if (!updatedTheme.isEditable) { throw new Error('Cannot edit built-in themes'); } // Find and update the theme const customThemes = availableThemes.filter(t => t.isEditable); const updatedCustomThemes = customThemes.map(t => t.id === updatedTheme.id ? updatedTheme : t ); // Update available themes const updatedAllThemes = [...DEFAULT_THEMES, ...updatedCustomThemes]; // Save to storage (scoped app_settings + legacy key) const scope = (await mmkvStorage.getItem('@user:current')) || 'local'; const key = `@user:${scope}:app_settings`; let settings = {} as any; try { settings = JSON.parse((await mmkvStorage.getItem(key)) || '{}'); } catch {} settings.customThemes = updatedCustomThemes; await mmkvStorage.setItem(key, JSON.stringify(settings)); await mmkvStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(updatedCustomThemes)); // Update state setAvailableThemes(updatedAllThemes); // Update current theme if needed if (currentTheme.id === updatedTheme.id) { setCurrentThemeState(updatedTheme); } // Do not emit global settings sync for themes } catch (error) { if (__DEV__) console.error('Failed to update custom theme:', error); } }; // Delete custom theme const deleteCustomTheme = async (themeId: string) => { try { // Find theme to delete const themeToDelete = availableThemes.find(t => t.id === themeId); if (!themeToDelete || !themeToDelete.isEditable) { throw new Error('Cannot delete built-in themes or theme not found'); } // Filter out the theme const customThemes = availableThemes.filter(t => t.isEditable && t.id !== themeId); const updatedAllThemes = [...DEFAULT_THEMES, ...customThemes]; // Save to storage (scoped app_settings + legacy key) const scope = (await mmkvStorage.getItem('@user:current')) || 'local'; const key = `@user:${scope}:app_settings`; let settings = {} as any; try { settings = JSON.parse((await mmkvStorage.getItem(key)) || '{}'); } catch {} settings.customThemes = customThemes; await mmkvStorage.setItem(key, JSON.stringify(settings)); await mmkvStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(customThemes)); // Update state setAvailableThemes(updatedAllThemes); // Reset to default theme if current theme was deleted if (currentTheme.id === themeId) { setCurrentThemeState(DEFAULT_THEMES[0]); await mmkvStorage.setItem(CURRENT_THEME_KEY, DEFAULT_THEMES[0].id); } // Do not emit global settings sync for themes } catch (error) { if (__DEV__) console.error('Failed to delete custom theme:', error); } }; return ( {children} ); } // Custom hook to use the theme context export function useTheme() { const context = useContext(ThemeContext); if (context === undefined) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; }