import { createContext, useContext, useState, useEffect, useCallback } from "react"; import { useColorScheme } from "react-native"; import AsyncStorage from "@react-native-async-storage/async-storage"; type ThemeColors = { primary: string; primaryLight: string; primaryDark: string; primaryMuted: string; secondary: string; secondaryLight: string; background: string; surface: string; surfaceAlt: string; surfaceElevated: string; card: string; border: string; borderLight: string; text: string; textSecondary: string; textTertiary: string; textInverse: string; error: string; errorLight: string; success: string; successLight: string; star: string; starLight: string; tabActive: string; tabInactive: string; divider: string; overlay: string; shadow: string; }; type Shadow = { shadowColor: string; shadowOffset: { width: number; height: number }; shadowOpacity: number; shadowRadius: number; elevation: number; }; type ThemeShadows = { xs: Shadow; sm: Shadow; md: Shadow; lg: Shadow; xl: Shadow; }; type Typography = { h1: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; h2: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; h3: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; h4: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; body: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; bodyBold: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; caption: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; captionBold: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; label: { fontSize: number; fontWeight: "700" | "600" | "500" | "400"; lineHeight: number }; }; type Theme = { isDarkMode: boolean; colors: ThemeColors; spacing: { xs: number; sm: number; md: number; lg: number; xl: number; xxl: number; }; radius: { xs: number; sm: number; md: number; lg: number; xl: number; full: number; }; shadows: ThemeShadows; typography: Typography; }; const makeShadow = (elevation: number, color: string): Shadow => { const shadows: Record = { 1: { shadowColor: color, shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 2, elevation: 1 }, 2: { shadowColor: color, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.06, shadowRadius: 6, elevation: 2 }, 4: { shadowColor: color, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.08, shadowRadius: 12, elevation: 4 }, 8: { shadowColor: color, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.1, shadowRadius: 20, elevation: 8 }, 16: { shadowColor: color, shadowOffset: { width: 0, height: 12 }, shadowOpacity: 0.12, shadowRadius: 32, elevation: 16 }, }; return shadows[elevation] || shadows[2]; }; const spacing = { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48 }; const radius = { xs: 4, sm: 10, md: 14, lg: 20, xl: 28, full: 9999 }; const typography: Typography = { h1: { fontSize: 34, fontWeight: "700", lineHeight: 42 }, h2: { fontSize: 26, fontWeight: "700", lineHeight: 34 }, h3: { fontSize: 20, fontWeight: "600", lineHeight: 28 }, h4: { fontSize: 17, fontWeight: "600", lineHeight: 24 }, body: { fontSize: 15, fontWeight: "400", lineHeight: 22 }, bodyBold: { fontSize: 15, fontWeight: "600", lineHeight: 22 }, caption: { fontSize: 13, fontWeight: "400", lineHeight: 18 }, captionBold: { fontSize: 13, fontWeight: "600", lineHeight: 18 }, label: { fontSize: 11, fontWeight: "500", lineHeight: 14 }, }; const shadowColorLight = "rgba(26, 42, 58, 0.12)"; const shadowColorDark = "rgba(0, 0, 0, 0.35)"; const LightTheme: Theme = { isDarkMode: false, colors: { primary: "#D4764A", primaryLight: "#FBF0EB", primaryDark: "#B85E34", primaryMuted: "#E8CFC3", secondary: "#1A2A3A", secondaryLight: "#E8ECF0", background: "#FDF8F3", surface: "#FFFFFF", surfaceAlt: "#FAF6F2", surfaceElevated: "#FFFFFF", card: "#FFFFFF", border: "#EDE8E3", borderLight: "#F3EFEB", text: "#1A1A1A", textSecondary: "#6B6560", textTertiary: "#9E9A95", textInverse: "#FFFFFF", error: "#C23A3A", errorLight: "#FEECEC", success: "#2E8B57", successLight: "#E8F5EC", star: "#D4953A", starLight: "#FFF5E1", tabActive: "#D4764A", tabInactive: "#9E9A95", divider: "#F0EBE6", overlay: "rgba(26, 42, 58, 0.45)", shadow: shadowColorLight, }, spacing, radius, shadows: { xs: makeShadow(1, shadowColorLight), sm: makeShadow(2, shadowColorLight), md: makeShadow(4, shadowColorLight), lg: makeShadow(8, shadowColorLight), xl: makeShadow(16, shadowColorLight), }, typography, }; const DarkTheme: Theme = { isDarkMode: true, colors: { primary: "#E8956F", primaryLight: "#2D2018", primaryDark: "#E8956F", primaryMuted: "#4A3A2E", secondary: "#E8ECF0", secondaryLight: "#2A3040", background: "#121212", surface: "#1E1E1E", surfaceAlt: "#252525", surfaceElevated: "#2A2A2A", card: "#1E1E1E", border: "#333333", borderLight: "#2A2A2A", text: "#F5F5F5", textSecondary: "#AAAAAA", textTertiary: "#777777", textInverse: "#1A1A1A", error: "#EF5350", errorLight: "#2A1A1A", success: "#66BB6A", successLight: "#1A2A1E", star: "#FFB74D", starLight: "#2A2510", tabActive: "#E8956F", tabInactive: "#777777", divider: "#2A2A2A", overlay: "rgba(0,0,0,0.7)", shadow: shadowColorDark, }, spacing, radius, shadows: { xs: makeShadow(1, shadowColorDark), sm: makeShadow(2, shadowColorDark), md: makeShadow(4, shadowColorDark), lg: makeShadow(8, shadowColorDark), xl: makeShadow(16, shadowColorDark), }, typography, }; export type { Theme, ThemeColors, ThemeShadows, Typography }; export { LightTheme, DarkTheme }; const THEME_KEY = "mojmajstor_theme_preference"; const ThemeContext = createContext(LightTheme); export type ThemeContextType = { theme: Theme; isDarkMode: boolean; toggleTheme: () => void; setDarkMode: (dark: boolean) => void; }; const ThemeCtx = createContext({ theme: LightTheme, isDarkMode: false, toggleTheme: () => {}, setDarkMode: () => {}, }); export function ThemeProvider({ children }: { children: React.ReactNode }) { const systemScheme = useColorScheme(); const [preference, setPreference] = useState<"system" | "light" | "dark">("system"); const [loaded, setLoaded] = useState(false); useEffect(() => { AsyncStorage.getItem(THEME_KEY).then((val) => { if (val === "light" || val === "dark" || val === "system") { setPreference(val); } setLoaded(true); }); }, []); const isDarkMode = preference === "dark" || (preference === "system" && systemScheme === "dark"); const theme = isDarkMode ? DarkTheme : LightTheme; const setDarkMode = useCallback((dark: boolean) => { const val = dark ? "dark" : "light"; setPreference(val); AsyncStorage.setItem(THEME_KEY, val); }, []); const toggleTheme = useCallback(() => { setDarkMode(!isDarkMode); }, [isDarkMode, setDarkMode]); if (!loaded) { return {children}; } return ( {children} ); } export function useTheme() { return useContext(ThemeContext); } export function useThemeToggle() { return useContext(ThemeCtx); }