managarten/apps-archived/mukke/apps/mobile/utils/themeContext.tsx
Till JS 7a56699d45 feat(mukke): rename LightWrite to Mukke and add music library, player, playlists
Combines LightWrite (beat/lyrics editor) and Mukke (iOS music player) into
a single web-based music workspace app. Archives the old Mukke mobile app.

- Rename: @lightwrite/* → @mukke/*, all branding, configs, Dockerfiles
- New DB schemas: songs, playlists, playlist_songs + songId FK on projects
- New backend modules: SongModule, PlaylistModule, LibraryModule
- New web: app shell with sidebar, library (songs/albums/artists/genres),
  web player (queue/shuffle/repeat/MediaSession), playlists, search,
  upload, dashboard, album/artist/genre detail pages
- Auth: add forgot-password + reset-password pages, extend auth store
- Tests: 40 backend unit tests (song, playlist, library services)
- Config: env generation, MinIO bucket, docker-compose prod, cloudflare
- Docs: update CLAUDE.md, auth guidelines with SvelteKit checklist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 09:55:56 +01:00

190 lines
4.6 KiB
TypeScript

import AsyncStorage from '@react-native-async-storage/async-storage';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { useColorScheme } from 'react-native';
export type ThemeVariant = 'classic' | 'ocean' | 'sunset';
export type ThemeMode = 'light' | 'dark';
export interface ThemeColors {
primary: string;
primaryDark: string;
accent: string;
background: string;
backgroundSecondary: string;
backgroundTertiary: string;
text: string;
textSecondary: string;
textTertiary: string;
border: string;
card: string;
success: string;
warning: string;
error: string;
shadow: string;
}
const THEME_VARIANTS = {
classic: {
primary: '#FF6B35',
primaryDark: '#E55A2B',
accent: '#FF8F65',
},
ocean: {
primary: '#2196F3',
primaryDark: '#1976D2',
accent: '#42A5F5',
},
sunset: {
primary: '#FF6B6B',
primaryDark: '#FF5252',
accent: '#FF8A80',
},
};
const getThemeColors = (variant: ThemeVariant, isDark: boolean): ThemeColors => {
const variantColors = THEME_VARIANTS[variant];
if (isDark) {
return {
primary: variantColors.primary,
primaryDark: variantColors.primaryDark,
accent: variantColors.accent,
background: '#121212',
backgroundSecondary: '#1E1E1E',
backgroundTertiary: '#2D2D2D',
text: '#FFFFFF',
textSecondary: '#AAAAAA',
textTertiary: '#888888',
border: '#333333',
card: '#1E1E1E',
success: '#4CAF50',
warning: '#FF9800',
error: '#FF6B6B',
shadow: '#000000',
};
} else {
return {
primary: variantColors.primary,
primaryDark: variantColors.primaryDark,
accent: variantColors.accent,
background: '#F5F5F5',
backgroundSecondary: '#FFFFFF',
backgroundTertiary: '#F0F0F0',
text: '#000000',
textSecondary: '#666666',
textTertiary: '#999999',
border: '#E0E0E0',
card: '#FFFFFF',
success: '#4CAF50',
warning: '#FF9800',
error: '#F44336',
shadow: '#000000',
};
}
};
type ThemeContextType = {
isDarkMode: boolean;
themeVariant: ThemeVariant;
colors: ThemeColors;
toggleTheme: () => void;
setDarkMode: (isDark: boolean) => void;
setThemeVariant: (variant: ThemeVariant) => void;
};
const ThemeContext = createContext<ThemeContextType>({
isDarkMode: false,
themeVariant: 'classic',
colors: getThemeColors('classic', false),
toggleTheme: () => {},
setDarkMode: () => {},
setThemeVariant: () => {},
});
export const useTheme = () => useContext(ThemeContext);
const THEME_PREFERENCE_KEY = '@mukke_theme_preference';
const THEME_VARIANT_KEY = '@mukke_theme_variant';
type ThemeProviderProps = {
children: React.ReactNode | ((themeProps: ThemeContextType) => React.ReactNode);
};
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
const systemColorScheme = useColorScheme();
const [isDarkMode, setIsDarkMode] = useState(false);
const [themeVariant, setThemeVariantState] = useState<ThemeVariant>('classic');
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const loadThemePreferences = async () => {
try {
const [savedPreference, savedVariant] = await Promise.all([
AsyncStorage.getItem(THEME_PREFERENCE_KEY),
AsyncStorage.getItem(THEME_VARIANT_KEY),
]);
if (savedPreference !== null) {
setIsDarkMode(savedPreference === 'dark');
} else {
setIsDarkMode(systemColorScheme === 'dark');
}
if (savedVariant !== null && ['classic', 'ocean', 'sunset'].includes(savedVariant)) {
setThemeVariantState(savedVariant as ThemeVariant);
}
} catch (error) {
console.error('Failed to load theme preferences', error);
} finally {
setIsLoaded(true);
}
};
loadThemePreferences();
}, [systemColorScheme]);
useEffect(() => {
if (isLoaded) {
AsyncStorage.setItem(THEME_PREFERENCE_KEY, isDarkMode ? 'dark' : 'light').catch((error) =>
console.error('Failed to save theme preference', error)
);
}
}, [isDarkMode, isLoaded]);
useEffect(() => {
if (isLoaded) {
AsyncStorage.setItem(THEME_VARIANT_KEY, themeVariant).catch((error) =>
console.error('Failed to save theme variant', error)
);
}
}, [themeVariant, isLoaded]);
const toggleTheme = () => {
setIsDarkMode((prev) => !prev);
};
const setDarkMode = (isDark: boolean) => {
setIsDarkMode(isDark);
};
const setThemeVariant = (variant: ThemeVariant) => {
setThemeVariantState(variant);
};
const colors = getThemeColors(themeVariant, isDarkMode);
const themeContextValue = {
isDarkMode,
themeVariant,
colors,
toggleTheme,
setDarkMode,
setThemeVariant,
};
return (
<ThemeContext.Provider value={themeContextValue}>
{typeof children === 'function' ? children(themeContextValue) : children}
</ThemeContext.Provider>
);
};