`AppTheme` never set `android:windowBackground`, so it was inherited from `Theme.AppCompat.DayNight.NoActionBar` and resolved to a near-white colour whenever the system was in light mode — independently of the theme picked inside the app. Nothing between that window and the navigators painted a background of its own: the root view, `GestureHandlerRootView` and `SafeAreaProvider` are all transparent, and only the individual screens carry `contentStyle`. Any frame in which no screen was drawn — mounting a nested navigator such as `MoreStack` or `ReaderStack`, or freezing the screen being left — therefore showed the white window through. Paint the root view with the active theme background so the window stays covered, and give `AppTheme` a day/night `android:windowBackground` so the frames before React draws are themed as well. The chapter screen additionally uses the reader background instead of the app background, so pushing it no longer flashes the app theme before the reader appears. Claude-Session: https://claude.ai/code/session_014uxuC7JVkShhWnN1WhkTaH Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
bfb12b04aa
commit
4dc933ee5a
@@ -26,6 +26,26 @@ import { ThemeProvider, useTheme } from '@hooks/persisted/useTheme';
|
||||
enableFreeze(true);
|
||||
const sqliteAdapters = __DEV__ && opSqliteAdapter ? [opSqliteAdapter] : [];
|
||||
|
||||
/**
|
||||
* The Android window background is resolved from the system light/dark setting,
|
||||
* so it is bright white whenever the system is light – regardless of the theme
|
||||
* picked inside the app. Nothing between it and the navigators paints a
|
||||
* background of its own, so every frame in which no screen is drawn (mounting a
|
||||
* nested navigator, freezing the screen being left) shows through as a flash.
|
||||
* Painting the root view keeps the window covered at all times.
|
||||
*/
|
||||
const ThemedRootView = ({ children }: PropsWithChildren) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView
|
||||
style={[styles.flex, { backgroundColor: theme.background }]}
|
||||
>
|
||||
{children}
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
};
|
||||
|
||||
const ThemedPaperProvider = ({ children }: PropsWithChildren) => {
|
||||
const theme = useTheme();
|
||||
const paperTheme = useMemo(() => {
|
||||
@@ -71,8 +91,8 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<GestureHandlerRootView style={styles.flex}>
|
||||
<ThemeProvider>
|
||||
<ThemeProvider>
|
||||
<ThemedRootView>
|
||||
<AppErrorBoundary>
|
||||
<SafeAreaProvider>
|
||||
<ThemedPaperProvider>
|
||||
@@ -83,8 +103,8 @@ const App = () => {
|
||||
</ThemedPaperProvider>
|
||||
</SafeAreaProvider>
|
||||
</AppErrorBoundary>
|
||||
</ThemeProvider>
|
||||
</GestureHandlerRootView>
|
||||
</ThemedRootView>
|
||||
</ThemeProvider>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
const {
|
||||
AndroidConfig,
|
||||
withAndroidColors,
|
||||
withAndroidColorsNight,
|
||||
withAndroidStyles,
|
||||
withAppBuildGradle,
|
||||
withDangerousMod,
|
||||
} = require('@expo/config-plugins');
|
||||
@@ -35,6 +39,53 @@ const DRAWABLE_RESOURCE_NAMES = [
|
||||
];
|
||||
const ANDROID_DENSITIES = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi', 'xxxhdpi'];
|
||||
|
||||
/**
|
||||
* `AppTheme` inherits `android:windowBackground` from
|
||||
* `Theme.AppCompat.DayNight.NoActionBar`, which resolves to a near-white colour
|
||||
* whenever the system is in light mode. The window is visible for the frames
|
||||
* between the splash screen going away and React drawing its first frame, so
|
||||
* the app opens with a white flash for anyone using a dark theme on a light
|
||||
* system. These are the backgrounds of the default light and dark themes.
|
||||
*/
|
||||
const WINDOW_BACKGROUND_RESOURCE = 'lnreaderWindowBackground';
|
||||
const WINDOW_BACKGROUND_LIGHT = '#FEFBFF';
|
||||
const WINDOW_BACKGROUND_DARK = '#1B1B1F';
|
||||
|
||||
const withWindowBackgroundColors = config => {
|
||||
config = withAndroidColors(config, config => {
|
||||
config.modResults = AndroidConfig.Colors.assignColorValue(
|
||||
config.modResults,
|
||||
{ name: WINDOW_BACKGROUND_RESOURCE, value: WINDOW_BACKGROUND_LIGHT },
|
||||
);
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
return withAndroidColorsNight(config, config => {
|
||||
config.modResults = AndroidConfig.Colors.assignColorValue(
|
||||
config.modResults,
|
||||
{ name: WINDOW_BACKGROUND_RESOURCE, value: WINDOW_BACKGROUND_DARK },
|
||||
);
|
||||
|
||||
return config;
|
||||
});
|
||||
};
|
||||
|
||||
const withWindowBackgroundStyle = config =>
|
||||
withAndroidStyles(config, config => {
|
||||
config.modResults = AndroidConfig.Styles.assignStylesValue(
|
||||
config.modResults,
|
||||
{
|
||||
add: true,
|
||||
parent: AndroidConfig.Styles.getAppThemeGroup(),
|
||||
name: 'android:windowBackground',
|
||||
value: `@color/${WINDOW_BACKGROUND_RESOURCE}`,
|
||||
},
|
||||
);
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
const withBuildGradleCustomizations = config =>
|
||||
withAppBuildGradle(config, config => {
|
||||
const { contents } = config.modResults;
|
||||
@@ -133,5 +184,7 @@ const withAndroidVariantAssets = config =>
|
||||
|
||||
module.exports = config => {
|
||||
config = withBuildGradleCustomizations(config);
|
||||
config = withWindowBackgroundColors(config);
|
||||
config = withWindowBackgroundStyle(config);
|
||||
return withAndroidVariantAssets(config);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
ReaderStackParamList,
|
||||
} from './types';
|
||||
import { NovelContextProvider } from '@screens/novel/NovelContext';
|
||||
import { useTheme } from '@hooks/persisted';
|
||||
import { useChapterReaderSettings, useTheme } from '@hooks/persisted';
|
||||
|
||||
const Stack = createNativeStackNavigator<ReaderStackParamList>();
|
||||
|
||||
@@ -20,6 +20,9 @@ const Stack = createNativeStackNavigator<ReaderStackParamList>();
|
||||
const ReaderStack = ({ route }) => {
|
||||
const params = useRef(route?.params);
|
||||
const theme = useTheme();
|
||||
// The reader has a background of its own, so the screen is given the same one
|
||||
// to keep the app background from showing while the chapter is pushed.
|
||||
const { theme: readerBackground } = useChapterReaderSettings();
|
||||
// eslint-disable-next-line react-hooks/refs
|
||||
const routeParams = route?.params ?? params.current;
|
||||
|
||||
@@ -36,7 +39,11 @@ const ReaderStack = ({ route }) => {
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="Novel" component={Novel} />
|
||||
<Stack.Screen name="Chapter" component={Reader} />
|
||||
<Stack.Screen
|
||||
name="Chapter"
|
||||
component={Reader}
|
||||
options={{ contentStyle: { backgroundColor: readerBackground } }}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</NovelContextProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user