# Theme System Documentation ## Tổng quan Hệ thống theme hỗ trợ **Light Mode**, **Dark Mode** và **System Mode** (tự động theo hệ thống). Theme preference được lưu trong AsyncStorage và tự động khôi phục khi khởi động lại ứng dụng. ## Kiến trúc Theme System ### 1. Theme Provider (`hooks/use-theme-context.tsx`) Theme Provider là core của hệ thống theme, quản lý state và đồng bộ với system theme. **Các tính năng chính:** - Quản lý `themeMode`: `'light' | 'dark' | 'system'` - Tự động detect system theme thông qua nhiều nguồn: - `Appearance.getColorScheme()` - iOS/Android system theme - `useColorScheme()` hook từ React Native - `Appearance.addChangeListener()` - listen system theme changes - `AppState` listener - sync lại khi app active - Lưu và restore theme preference từ AsyncStorage - Export ra `colorScheme` cuối cùng: `'light' | 'dark'` **ThemeContextType:** ```typescript interface ThemeContextType { themeMode: ThemeMode; // User's choice: 'light' | 'dark' | 'system' colorScheme: ColorScheme; // Final theme: 'light' | 'dark' colors: typeof Colors.light; // Theme colors object setThemeMode: (mode: ThemeMode) => Promise; getColor: (colorName: ColorName) => string; isHydrated: boolean; // AsyncStorage đã load xong } ``` **Cách hoạt động:** ```typescript // Xác định colorScheme cuối cùng const colorScheme: ColorScheme = themeMode === "system" ? systemScheme : themeMode; ``` ### 2. Colors Configuration (`constants/theme.ts`) Định nghĩa tất cả colors cho light và dark theme: ```typescript export const Colors = { light: { text: "#11181C", textSecondary: "#687076", background: "#fff", backgroundSecondary: "#f5f5f5", surface: "#ffffff", surfaceSecondary: "#f8f9fa", tint: "#0a7ea4", primary: "#007AFF", secondary: "#5AC8FA", success: "#34C759", warning: "#ff6600", error: "#FF3B30", icon: "#687076", border: "#C6C6C8", separator: "#E5E5E7", card: "#ffffff", // ... more colors }, dark: { text: "#ECEDEE", textSecondary: "#8E8E93", background: "#000000", backgroundSecondary: "#1C1C1E", surface: "#1C1C1E", surfaceSecondary: "#2C2C2E", tint: "#fff", primary: "#0A84FF", secondary: "#64D2FF", success: "#30D158", warning: "#ff6600", error: "#FF453A", icon: "#8E8E93", border: "#38383A", separator: "#38383A", card: "#1C1C1E", // ... more colors }, }; export type ColorName = keyof typeof Colors.light; ``` ### 3. Setup trong App (`app/_layout.tsx`) Theme Provider phải wrap toàn bộ app: ```tsx export default function RootLayout() { return ( ); } function AppContent() { const { colorScheme } = useThemeContext(); return ( {/* ... routes */} ); } ``` ## Cách sử dụng Theme ### 1. useThemeContext (Core Hook) Hook chính để access theme state: ```tsx import { useThemeContext } from "@/hooks/use-theme-context"; function MyComponent() { const { themeMode, // 'light' | 'dark' | 'system' colorScheme, // 'light' | 'dark' colors, // Colors object setThemeMode, // Change theme getColor, // Get color by name isHydrated, // AsyncStorage loaded } = useThemeContext(); return ( Mode: {themeMode}, Scheme: {colorScheme} ); } ``` ### 2. useColorScheme Hook Alias để lấy colorScheme nhanh: ```tsx import { useColorScheme } from "@/hooks/use-theme-context"; function MyComponent() { const colorScheme = useColorScheme(); // 'light' | 'dark' return Current theme: {colorScheme}; } ``` **⚠️ Lưu ý:** `useColorScheme` từ `use-theme-context.tsx`, KHÔNG phải từ `react-native`. ### 3. useThemeColor Hook Override colors cho specific themes: ```tsx import { useThemeColor } from "@/hooks/use-theme-color"; function MyComponent() { // Với override const backgroundColor = useThemeColor( { light: "#ffffff", dark: "#1C1C1E" }, "surface" ); // Không override, dùng color từ theme const textColor = useThemeColor({}, "text"); return ( Text ); } ``` **Cách hoạt động:** ```typescript // Ưu tiên props override trước, sau đó mới dùng Colors const colorFromProps = props[colorScheme]; return colorFromProps || Colors[colorScheme][colorName]; ``` ### 4. useAppTheme Hook (Recommended) Hook tiện lợi với pre-built styles và utilities: ```tsx import { useAppTheme } from "@/hooks/use-app-theme"; function MyComponent() { const { colors, styles, utils } = useAppTheme(); return ( Primary Button Theme is {utils.isDark ? "Dark" : "Light"} Transparent background ); } ``` ### 5. Themed Components **ThemedView** và **ThemedText** - Tự động apply theme colors: ```tsx import { ThemedText } from "@/components/themed-text"; import { ThemedView } from "@/components/themed-view"; function MyComponent() { return ( Title Text Subtitle Regular Text Link Text {/* Override với custom colors */} Custom colored text ); } ``` **ThemedText types:** - `default` - 16px regular - `title` - 32px bold - `subtitle` - 20px bold - `defaultSemiBold` - 16px semibold - `link` - 16px với color #0a7ea4 ### 6. Theme Toggle Component Component có sẵn để user chọn theme: ```tsx import { ThemeToggle } from "@/components/theme-toggle"; function SettingsScreen() { return ( ); } ``` Component này hiển thị 3 options: Light, Dark, System với icons và labels đa ngôn ngữ. ## Available Styles từ useAppTheme ```typescript const { styles } = useAppTheme(); // Container styles styles.container; // Flex 1 container với background styles.surface; // Surface với padding 16, borderRadius 12 styles.card; // Card với shadow, elevation // Button styles styles.primaryButton; // Primary button với colors.primary styles.secondaryButton; // Secondary button với border styles.primaryButtonText; // White text cho primary button styles.secondaryButtonText; // Theme text cho secondary button // Input styles styles.textInput; // Text input với border, padding // Status styles styles.successContainer; // Success background với border styles.warningContainer; // Warning background với border styles.errorContainer; // Error background với border // Utility styles.separator; // 1px line separator ``` ## Theme Utilities ```typescript const { utils } = useAppTheme(); // Check theme utils.isDark; // boolean - true nếu dark mode utils.isLight; // boolean - true nếu light mode // Toggle theme (ignores system mode) utils.toggleTheme(); // Switch giữa light ↔ dark // Get color với opacity utils.getOpacityColor("primary", 0.1); // rgba(0, 122, 255, 0.1) ``` ## Luồng hoạt động của Theme System ``` 1. App khởi động └─→ ThemeProvider mount ├─→ Load saved themeMode từ AsyncStorage ('light'/'dark'/'system') ├─→ Detect systemScheme từ OS │ ├─→ Appearance.getColorScheme() │ ├─→ useColorScheme() hook │ └─→ Appearance.addChangeListener() └─→ Tính toán colorScheme cuối cùng └─→ themeMode === 'system' ? systemScheme : themeMode 2. User thay đổi system theme └─→ Appearance listener fire └─→ Update systemScheme state └─→ Nếu themeMode === 'system' └─→ colorScheme tự động update └─→ Components re-render với colors mới 3. User chọn theme trong app └─→ setThemeMode('light'/'dark'/'system') ├─→ Update themeMode state ├─→ Save vào AsyncStorage └─→ colorScheme update └─→ Components re-render 4. App về foreground └─→ AppState listener fire └─→ Sync lại systemScheme (phòng user đổi system theme khi app background) ``` ## Storage Theme preference được lưu với key: `'theme_mode'` ```typescript // Tự động xử lý bởi ThemeProvider await setStorageItem("theme_mode", "light" | "dark" | "system"); const savedMode = await getStorageItem("theme_mode"); ``` ## Best Practices 1. **Sử dụng hooks đúng context:** - `useThemeContext()` - Khi cần full control (themeMode, setThemeMode) - `useColorScheme()` - Chỉ cần biết light/dark - `useAppTheme()` - Recommended cho UI components (có styles + utils) - `useThemeColor()` - Khi cần override colors 2. **Sử dụng Themed Components:** ```tsx // Good ✅ Hello ; // Also good ✅ const { colors } = useAppTheme(); Hello ; ``` 3. **Tận dụng pre-built styles:** ```tsx // Good ✅ const { styles } = useAppTheme(); // Less good ❌ ``` 4. **Sử dụng opacity colors:** ```tsx const { utils } = useAppTheme(); ; ``` 5. **Check theme correctly:** ```tsx // Good ✅ const { utils } = useAppTheme(); if (utils.isDark) { ... } // Also good ✅ const { colorScheme } = useThemeContext(); if (colorScheme === 'dark') { ... } ``` ## Troubleshooting ### Theme không được lưu - Kiểm tra AsyncStorage permissions - Check logs trong console: `[Theme] Failed to save theme mode:` ### Flash màu sắc khi khởi động - ThemeProvider đã xử lý với `isHydrated` state - Chờ AsyncStorage load xong trước khi render ### System theme không update - Check Appearance listener đã register: `[Theme] Registering appearance listener` - Check logs: `[Theme] System theme changed to: ...` - iOS: Restart app sau khi đổi system theme - Android: Cần `expo-system-ui` plugin trong `app.json` ### Colors không đúng - Đảm bảo app wrapped trong `` - Check `colorScheme` trong console logs - Verify Colors object trong `constants/theme.ts` ## Migration Guide Nếu đang dùng old theme system: ```tsx // Old ❌ import { useColorScheme } from "react-native"; const colorScheme = useColorScheme(); const backgroundColor = colorScheme === "dark" ? "#000" : "#fff"; // New ✅ import { useAppTheme } from "@/hooks/use-app-theme"; const { colors } = useAppTheme(); const backgroundColor = colors.background; ``` ```tsx // Old ❌ const [theme, setTheme] = useState("light"); // New ✅ import { useThemeContext } from "@/hooks/use-theme-context"; const { themeMode, setThemeMode } = useThemeContext(); ``` ## Debug Logs Enable logs để debug theme issues: ```tsx // Trong use-theme-context.tsx, uncomment các dòng: console.log("[Theme] Appearance.getColorScheme():", scheme); console.log("[Theme] System theme changed to:", newScheme); console.log("[Theme] Mode:", themeMode); console.log("[Theme] Derived colorScheme:", colorScheme); ``` ```tsx // Trong use-theme-color.ts: console.log("Detected theme:", theme); // Đã có sẵn ``` ```tsx // Trong _layout.tsx: console.log("Color Scheme: ", colorScheme); // Đã có sẵn ```