diff --git a/App.tsx b/App.tsx
index d7a1990b..df4f9a1b 100644
--- a/App.tsx
+++ b/App.tsx
@@ -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 (
+
+ {children}
+
+ );
+};
+
const ThemedPaperProvider = ({ children }: PropsWithChildren) => {
const theme = useTheme();
const paperTheme = useMemo(() => {
@@ -71,8 +91,8 @@ const App = () => {
return (
-
-
+
+
@@ -83,8 +103,8 @@ const App = () => {
-
-
+
+
);
};
diff --git a/plugins/withAndroidCustomizations.js b/plugins/withAndroidCustomizations.js
index 38c63c1b..4b509917 100644
--- a/plugins/withAndroidCustomizations.js
+++ b/plugins/withAndroidCustomizations.js
@@ -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);
};
diff --git a/src/navigators/ReaderStack.tsx b/src/navigators/ReaderStack.tsx
index 5bb8e04a..59e2e62f 100644
--- a/src/navigators/ReaderStack.tsx
+++ b/src/navigators/ReaderStack.tsx
@@ -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();
@@ -20,6 +20,9 @@ const Stack = createNativeStackNavigator();
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 }) => {
}}
>
-
+
);