NuvioStreaming/src/contexts/ThemeContext.tsx
2025-10-26 12:42:34 +05:30

346 lines
No EOL
10 KiB
TypeScript

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<Theme, 'id' | 'isEditable'>) => void;
updateCustomTheme: (theme: Theme) => void;
deleteCustomTheme: (themeId: string) => void;
}
// Create the context
const ThemeContext = createContext<ThemeContextProps | undefined>(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<Theme>(DEFAULT_THEMES[0]);
const [availableThemes, setAvailableThemes] = useState<Theme[]>(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<Theme, 'id' | 'isEditable'>) => {
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 (
<ThemeContext.Provider
value={{
currentTheme,
availableThemes,
setCurrentTheme,
addCustomTheme,
updateCustomTheme,
deleteCustomTheme,
}}
>
{children}
</ThemeContext.Provider>
);
}
// 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;
}