diff --git a/packages/mobile/src/screens/splash-screen/SplashScreen.tsx b/packages/mobile/src/screens/splash-screen/SplashScreen.tsx index 6505db6200..1fc2474238 100644 --- a/packages/mobile/src/screens/splash-screen/SplashScreen.tsx +++ b/packages/mobile/src/screens/splash-screen/SplashScreen.tsx @@ -1,11 +1,15 @@ import { useRef, useState, useEffect, useCallback } from 'react' -import { accountSelectors, Status } from '@audius/common' +import { accountSelectors, themeSelectors, Status } from '@audius/common' import LottieView from 'lottie-react-native' -import { StyleSheet, Animated } from 'react-native' +import { StyleSheet, Animated, StatusBar, Platform } from 'react-native' import { useSelector } from 'react-redux' +import { useEffectOnce } from 'react-use' + +import { updateStatusBarTheme } from 'app/utils/theme' const { getAccountStatus } = accountSelectors +const { getTheme, getSystemAppearance } = themeSelectors const SCALE_TO = 1.2 const ANIM_DURATION_MS = 2000 @@ -30,8 +34,23 @@ const styles = StyleSheet.create({ export const SplashScreen = () => { const [animationFinished, setAnimationFinished] = useState(false) const accountStatus = useSelector(getAccountStatus) + const theme = useSelector(getTheme) + const systemAppearance = useSelector(getSystemAppearance) const backgroundOpacityAnim = useRef(new Animated.Value(1)) + useEffectOnce(() => { + if (Platform.OS === 'ios') { + // Hide the StatusBar on ios + updateStatusBarTheme(theme, systemAppearance) + StatusBar.setHidden(true) + } else { + // Make the StatusBar translucent on android + // (hiding it on android causes the app to shift when it's unhidden) + StatusBar.setBackgroundColor('transparent') + StatusBar.setTranslucent(true) + } + }) + useEffect(() => { if (![Status.IDLE, Status.LOADING].includes(accountStatus)) { if (animationRef.current) { @@ -66,8 +85,15 @@ export const SplashScreen = () => { }, [scaleAnim]) const onAnimationFinish = useCallback(() => { + if (Platform.OS === 'ios') { + // Unhide the StatusBar on ios + StatusBar.setHidden(false, 'fade') + } else { + // Make the StatusBar opaque on android + updateStatusBarTheme(theme, systemAppearance) + } setAnimationFinished(true) - }, [setAnimationFinished]) + }, [setAnimationFinished, theme, systemAppearance]) const animationRef = useRef(null) diff --git a/packages/mobile/src/store/theme/sagas.ts b/packages/mobile/src/store/theme/sagas.ts index 13f0c95d3d..9255e6985f 100644 --- a/packages/mobile/src/store/theme/sagas.ts +++ b/packages/mobile/src/store/theme/sagas.ts @@ -5,9 +5,9 @@ import { eventEmitter, initialMode } from 'react-native-dark-mode' import { put, call, spawn, takeEvery, select } from 'typed-redux-saga' import { localStorage } from 'app/services/local-storage' -import { handleThemeChange } from 'app/utils/theme' +import { updateStatusBarTheme, setStatusBarTheme } from 'app/utils/theme' const { setTheme, setSystemAppearance } = themeActions -const { getSystemAppearance } = themeSelectors +const { getTheme, getSystemAppearance } = themeSelectors const waitForSystemAppearanceChange = async () => { let listener @@ -27,6 +27,7 @@ const waitForSystemAppearanceChange = async () => { function* watchSystemAppearanceChange() { while (true) { const systemAppearance = yield* select(getSystemAppearance) + const theme = yield* select(getTheme) if (!systemAppearance) { yield* put( setSystemAppearance({ @@ -35,14 +36,18 @@ function* watchSystemAppearanceChange() { ) } else { const systemAppearance = yield* call(waitForSystemAppearanceChange) + if (theme === Theme.AUTO) { + setStatusBarTheme(systemAppearance) + } yield* put(setSystemAppearance({ systemAppearance })) } } } function* setThemeAsync(action: PayloadAction<{ theme: Theme }>) { + const systemAppearance = yield* select(getSystemAppearance) const { theme } = action.payload - handleThemeChange(theme) + updateStatusBarTheme(theme, systemAppearance) yield* call([localStorage, 'setItem'], 'theme', theme) } diff --git a/packages/mobile/src/utils/theme.ts b/packages/mobile/src/utils/theme.ts index d1fe8f21cc..4cc88809cb 100644 --- a/packages/mobile/src/utils/theme.ts +++ b/packages/mobile/src/utils/theme.ts @@ -1,3 +1,4 @@ +import type { SystemAppearance, Nullable } from '@audius/common' import { themeSelectors } from '@audius/common' import { StatusBar } from 'react-native' import { useSelector } from 'react-redux' @@ -11,29 +12,39 @@ export enum Theme { MATRIX = 'matrix' } -export const handleThemeChange = (theme: Theme) => { +/** + * Set status bar theme in a cross-platform way + */ +export const setStatusBarTheme = (theme: 'light' | 'dark') => { + if (theme === 'light') { + StatusBar.setBarStyle('dark-content') + StatusBar.setBackgroundColor(defaultTheme.white) + } else { + StatusBar.setBarStyle('light-content') + StatusBar.setBackgroundColor(darkTheme.white) + } +} + +export const updateStatusBarTheme = ( + theme: Nullable, + systemAppearance: Nullable +) => { switch (theme) { case Theme.DEFAULT: { - StatusBar.setBarStyle('dark-content') + setStatusBarTheme('light') break } case Theme.DARK: { - StatusBar.setBarStyle('light-content') + setStatusBarTheme('dark') break } case Theme.AUTO: { - StatusBar.setBarStyle('default') + if (systemAppearance) { + setStatusBarTheme(systemAppearance) + } break } } - - // Fade in status bar after we - // get our first update, after a - // slight delay to allow Splash Screen - // to animate out. - setTimeout(() => { - StatusBar.setHidden(false, 'fade') - }, 500) } export const defaultTheme = {