feat: Improve dark mode support and splash screen handling across app
- Add dark mode background to splash and global styles - Replace LoadingScreen with SplashScreenComponent for unified splash handling - Ensure navigation and paper themes sync with dark mode - Set initial theme based on system preference - Apply background color to navigation containers and scenes
This commit is contained in:
@@ -34,7 +34,10 @@
|
||||
"image": "./assets/images/racoon-splash.png",
|
||||
"imageWidth": 200,
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
"backgroundColor": "#ffffff",
|
||||
"dark": {
|
||||
"backgroundColor": "#121212"
|
||||
}
|
||||
}
|
||||
],
|
||||
"expo-secure-store",
|
||||
|
||||
@@ -41,6 +41,9 @@ export default function TabLayout() {
|
||||
</Link>
|
||||
),
|
||||
tabBarActiveTintColor: theme.colors.primary,
|
||||
sceneContainerStyle: {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
tabBarInactiveTintColor: theme.colors.onSurfaceVariant,
|
||||
tabBarStyle: {
|
||||
backgroundColor: theme.colors.surface,
|
||||
|
||||
@@ -3,3 +3,12 @@
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
body {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
}
|
||||
@@ -2,28 +2,21 @@ import React, { useEffect } from "react";
|
||||
import {useAuth} from "@/src/stores/authStore";
|
||||
import {useThemeStore} from "@/src/stores/themeStore";
|
||||
import NotAuthenticated from "@/src/components/NotAuthenticated";
|
||||
import LoadingScreen from "@/src/components/LoadingScreen";
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
import SplashScreenComponent from "@/src/components/SplashScreenComponent";
|
||||
|
||||
const AuthenticatedWrapper: React.FC<{ children: React.ReactNode }> = ({children}) => {
|
||||
const {isAuthenticated, loading, authenticatedUser, isHydrated: authHydrated} = useAuth();
|
||||
const {isHydrated: themeHydrated} = useThemeStore();
|
||||
|
||||
// Verstecke den Splash Screen erst wenn beide Stores hydratiert sind
|
||||
useEffect(() => {
|
||||
if (authHydrated && themeHydrated) {
|
||||
SplashScreen.hideAsync().catch(() => {});
|
||||
}
|
||||
}, [authHydrated, themeHydrated]);
|
||||
|
||||
// Zeige nichts (Splash Screen bleibt) bis beide Stores hydratiert sind
|
||||
if (!authHydrated || !themeHydrated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Zeige LoadingScreen während Auth Status geprüft wird
|
||||
if (loading) {
|
||||
return <LoadingScreen />;
|
||||
if (!authHydrated || !themeHydrated || loading) {
|
||||
return <SplashScreenComponent/>;
|
||||
}
|
||||
|
||||
if (!isAuthenticated || !authenticatedUser) {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import { View, ActivityIndicator } from "react-native";
|
||||
import Logo from "./Logo";
|
||||
import { useThemeStore } from "@/src/stores/themeStore";
|
||||
|
||||
export default function LoadingScreen() {
|
||||
const isDark = useThemeStore((state) => state.isDark);
|
||||
|
||||
return (
|
||||
<View
|
||||
className="flex-1 items-center justify-center"
|
||||
style={{
|
||||
backgroundColor: isDark ? '#121212' : '#ffffff'
|
||||
}}
|
||||
>
|
||||
<Logo size="large" />
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color={isDark ? '#BB86FC' : '#6200EE'}
|
||||
style={{ marginTop: 20 }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { View, Image, useColorScheme } from 'react-native';
|
||||
import { useThemeStore } from '@/src/stores/themeStore';
|
||||
|
||||
export default function SplashScreenComponent() {
|
||||
const systemColorScheme = useColorScheme();
|
||||
|
||||
const { isDark, isHydrated } = useThemeStore();
|
||||
|
||||
const effectiveIsDark = isHydrated ? isDark : systemColorScheme === 'dark';
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: effectiveIsDark ? '#121212' : '#ffffff',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={require('@/assets/images/racoon-splash.png')}
|
||||
style={{ width: 200, resizeMode: 'contain' }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +1,40 @@
|
||||
import React, {ReactNode, useEffect} from "react";
|
||||
import {Provider as PaperProvider} from "react-native-paper";
|
||||
import {adaptNavigationTheme, Provider as PaperProvider} from "react-native-paper";
|
||||
import {useThemeStore} from "@/src/stores/themeStore";
|
||||
import {useColorScheme} from "nativewind";
|
||||
import LoadingScreen from "@/src/components/LoadingScreen";
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
import SplashScreenComponent from "@/src/components/SplashScreenComponent";
|
||||
|
||||
import { ThemeProvider as NavThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native';
|
||||
import {View} from "react-native";
|
||||
|
||||
SplashScreen.preventAutoHideAsync().catch(() => {});
|
||||
|
||||
export const ThemeProvider = ({children}: { children: ReactNode }) => {
|
||||
const { theme, isDark, isHydrated } = useThemeStore();
|
||||
const { setColorScheme } = useColorScheme();
|
||||
|
||||
const { LightTheme: NavLightTheme, DarkTheme: NavDarkTheme } = adaptNavigationTheme({
|
||||
reactNavigationLight: DefaultTheme,
|
||||
reactNavigationDark: DarkTheme,
|
||||
});
|
||||
|
||||
const navigationTheme = isDark ? NavDarkTheme : NavLightTheme;
|
||||
|
||||
useEffect(() => {
|
||||
setColorScheme(isDark ? 'dark' : 'light');
|
||||
}, [isDark, setColorScheme]);
|
||||
}, [isDark, setColorScheme, isHydrated]);
|
||||
|
||||
if (!isHydrated) {
|
||||
return <LoadingScreen />;
|
||||
return <SplashScreenComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<PaperProvider theme={theme}>{children}</PaperProvider>
|
||||
<NavThemeProvider value={navigationTheme}>
|
||||
<PaperProvider theme={theme}>
|
||||
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
|
||||
{children}
|
||||
</View>
|
||||
</PaperProvider>
|
||||
</NavThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+3
-1
@@ -1,10 +1,12 @@
|
||||
import React from "react";
|
||||
import {Slot, Stack} from "expo-router";
|
||||
import {useThemeStore} from "@/src/stores";
|
||||
|
||||
|
||||
export default function CustomStack() {
|
||||
const { theme } = useThemeStore();
|
||||
return (
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack screenOptions={{ headerShown: false, contentStyle: { backgroundColor: theme.colors.background } }}>
|
||||
<Slot />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
+29
-18
@@ -1,8 +1,15 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { Platform } from 'react-native';
|
||||
import {create} from 'zustand';
|
||||
import {persist, createJSONStorage} from 'zustand/middleware';
|
||||
import {Platform} from 'react-native';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { CustomMD3Theme, darkTheme, lightTheme } from '@/src/core/theme';
|
||||
import {CustomMD3Theme, darkTheme, lightTheme} from '@/src/core/theme';
|
||||
|
||||
const getSystemThemeIsDark = () => {
|
||||
if (Platform.OS === 'web' && typeof window !== 'undefined') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const zustandStorage = {
|
||||
getItem: async (name: string): Promise<string | null> => {
|
||||
@@ -37,23 +44,27 @@ interface ThemeState {
|
||||
|
||||
export const useThemeStore = create<ThemeState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
theme: lightTheme,
|
||||
isDark: false,
|
||||
isHydrated: false,
|
||||
toggleTheme: () => {
|
||||
const newIsDark = !get().isDark;
|
||||
set({
|
||||
isDark: newIsDark,
|
||||
theme: newIsDark ? darkTheme : lightTheme,
|
||||
});
|
||||
},
|
||||
setHydrated: () => set({ isHydrated: true }),
|
||||
}),
|
||||
(set, get) => {
|
||||
const initialDark = getSystemThemeIsDark();
|
||||
|
||||
return {
|
||||
theme: initialDark ? darkTheme : lightTheme,
|
||||
isDark: initialDark,
|
||||
isHydrated: false,
|
||||
toggleTheme: () => {
|
||||
const newIsDark = !get().isDark;
|
||||
set({
|
||||
isDark: newIsDark,
|
||||
theme: newIsDark ? darkTheme : lightTheme,
|
||||
});
|
||||
},
|
||||
setHydrated: () => set({isHydrated: true}),
|
||||
};
|
||||
},
|
||||
{
|
||||
name: 'theme-storage',
|
||||
storage: createJSONStorage(() => zustandStorage),
|
||||
partialize: (state) => ({ isDark: state.isDark }),
|
||||
partialize: (state) => ({isDark: state.isDark}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (state) {
|
||||
state.theme = state.isDark ? darkTheme : lightTheme;
|
||||
|
||||
Reference in New Issue
Block a user