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 }) => { }} > - + );