Add beenvoice mobile companion app with full dark mode support.

Expo app with dashboard, time clock, invoices, and settings — native tabs, glass UI, theme-aware components, and iOS Live Activities.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 22:36:37 -04:00
parent 8a7a8df477
commit 14c880123c
93 changed files with 8849 additions and 7849 deletions
+88
View File
@@ -0,0 +1,88 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from "react";
import { useColorScheme as useSystemColorScheme, type ColorSchemeName } from "react-native";
import { getThemeColors, type ThemeColors } from "@/lib/theme-palette";
export type ColorMode = "system" | "light" | "dark";
const STORAGE_KEY = "beenvoice:color-mode";
type ThemeContextValue = {
colorMode: ColorMode;
setColorMode: (mode: ColorMode) => Promise<void>;
colorScheme: NonNullable<ColorSchemeName>;
colors: ThemeColors;
isDark: boolean;
};
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: ReactNode }) {
const systemScheme = useSystemColorScheme();
const [colorMode, setColorModeState] = useState<ColorMode>("system");
const [ready, setReady] = useState(false);
useEffect(() => {
AsyncStorage.getItem(STORAGE_KEY)
.then((stored) => {
if (stored === "light" || stored === "dark" || stored === "system") {
setColorModeState(stored);
}
})
.finally(() => setReady(true));
}, []);
const colorScheme: NonNullable<ColorSchemeName> =
colorMode === "system" ? (systemScheme ?? "light") : colorMode;
const colors = useMemo(() => getThemeColors(colorScheme), [colorScheme]);
const setColorMode = useCallback(async (mode: ColorMode) => {
setColorModeState(mode);
await AsyncStorage.setItem(STORAGE_KEY, mode);
}, []);
const value = useMemo(
() => ({
colorMode,
setColorMode,
colorScheme,
colors,
isDark: colorScheme === "dark",
}),
[colorMode, setColorMode, colorScheme, colors],
);
if (!ready) {
return (
<ThemeContext.Provider
value={{
colorMode: "system",
setColorMode: async () => {},
colorScheme: systemScheme ?? "light",
colors: getThemeColors(systemScheme ?? "light"),
isDark: systemScheme === "dark",
}}
>
{children}
</ThemeContext.Provider>
);
}
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
export function useAppTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useAppTheme must be used within ThemeProvider");
return ctx;
}