diff options
-rw-r--r-- | app.config.js | 12 | ||||
-rw-r--r-- | assets/splash-dark.png | bin | 0 -> 1014684 bytes | |||
-rw-r--r-- | src/Splash.tsx | 164 |
3 files changed, 119 insertions, 57 deletions
diff --git a/app.config.js b/app.config.js index f2403edd4..4f2780feb 100644 --- a/app.config.js +++ b/app.config.js @@ -66,6 +66,12 @@ module.exports = function () { 'Used for profile pictures, posts, and other kinds of content', }, associatedDomains: ['applinks:bsky.app', 'applinks:staging.bsky.app'], + splash: { + dark: { + image: './assets/splash-dark.png', + backgroundColor: '#001429', + }, + }, }, androidStatusBar: { barStyle: 'dark-content', @@ -95,6 +101,12 @@ module.exports = function () { category: ['BROWSABLE', 'DEFAULT'], }, ], + splash: { + dark: { + image: './assets/splash-dark.png', + backgroundColor: '#001429', + }, + }, }, web: { favicon: './assets/favicon.png', diff --git a/assets/splash-dark.png b/assets/splash-dark.png new file mode 100644 index 000000000..6425bf28a --- /dev/null +++ b/assets/splash-dark.png Binary files differdiff --git a/src/Splash.tsx b/src/Splash.tsx index d3b21dd63..bb2c7a175 100644 --- a/src/Splash.tsx +++ b/src/Splash.tsx @@ -1,5 +1,11 @@ import React, {useCallback, useEffect} from 'react' -import {View, StyleSheet, Image as RNImage} from 'react-native' +import { + View, + StyleSheet, + Image as RNImage, + AccessibilityInfo, + useColorScheme, +} from 'react-native' import * as SplashScreen from 'expo-splash-screen' import {Image} from 'expo-image' import Animated, { @@ -15,10 +21,17 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import Svg, {Path, SvgProps} from 'react-native-svg' import {isAndroid} from '#/platform/detection' +import {useColorMode} from 'state/shell' +import {colors} from '#/lib/styles' // @ts-ignore import splashImagePointer from '../assets/splash.png' +// @ts-ignore +import darkSplashImagePointer from '../assets/splash-dark.png' const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri +const darkSplashImageUri = RNImage.resolveAssetSource( + darkSplashImagePointer, +).uri export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) { const width = 1000 @@ -29,9 +42,9 @@ export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) { // @ts-ignore it's fiiiiine ref={ref} viewBox="0 0 64 66" - style={{width, height}}> + style={[{width, height}, props.style]}> <Path - fill="#fff" + fill={props.fill || '#fff'} d="M13.873 3.77C21.21 9.243 29.103 20.342 32 26.3v15.732c0-.335-.13.043-.41.858-1.512 4.414-7.418 21.642-20.923 7.87-7.111-7.252-3.819-14.503 9.125-16.692-7.405 1.252-15.73-.817-18.014-8.93C1.12 22.804 0 8.431 0 6.488 0-3.237 8.579-.18 13.873 3.77ZM50.127 3.77C42.79 9.243 34.897 20.342 32 26.3v15.732c0-.335.13.043.41.858 1.512 4.414 7.418 21.642 20.923 7.87 7.111-7.252 3.819-14.503-9.125-16.692 7.405 1.252 15.73-.817 18.014-8.93C62.88 22.804 64 8.431 64 6.488 64-3.237 55.422-.18 50.127 3.77Z" /> </Svg> @@ -53,7 +66,19 @@ export function Splash(props: React.PropsWithChildren<Props>) { const [isAnimationComplete, setIsAnimationComplete] = React.useState(false) const [isImageLoaded, setIsImageLoaded] = React.useState(false) const [isLayoutReady, setIsLayoutReady] = React.useState(false) - const isReady = props.isReady && isImageLoaded && isLayoutReady + const [reduceMotion, setReduceMotion] = React.useState<boolean | undefined>( + false, + ) + const isReady = + props.isReady && + isImageLoaded && + isLayoutReady && + reduceMotion !== undefined + + const colorMode = useColorMode() + const colorScheme = useColorScheme() + const themeName = colorMode === 'system' ? colorScheme : colorMode + const isDarkMode = themeName === 'dark' const logoAnimation = useAnimatedStyle(() => { return { @@ -73,6 +98,17 @@ export function Splash(props: React.PropsWithChildren<Props>) { opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'), } }) + const reducedLogoAnimation = useAnimatedStyle(() => { + return { + transform: [ + { + scale: interpolate(intro.value, [0, 1], [0.8, 1], 'clamp'), + }, + ], + opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'), + } + }) + const logoWrapperAnimation = useAnimatedStyle(() => { return { opacity: interpolate( @@ -137,71 +173,85 @@ export function Splash(props: React.PropsWithChildren<Props>) { } }, [onFinish, intro, outroLogo, outroApp, outroAppOpacity, isReady]) + useEffect(() => { + AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion) + }, []) + + const logoAnimations = + reduceMotion === true ? reducedLogoAnimation : logoAnimation + return ( <View style={{flex: 1}} onLayout={onLayout}> {!isAnimationComplete && ( <Image accessibilityIgnoresInvertColors onLoadEnd={onLoadEnd} - source={{uri: splashImageUri}} + source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}} style={StyleSheet.absoluteFillObject} /> )} - {isAndroid ? ( - // Use a simple fade on older versions of android (work around a bug) - <> - <Animated.View style={[{flex: 1}, appAnimation]}> - {props.children} - </Animated.View> - - {!isAnimationComplete && ( - <Animated.View - style={[ - StyleSheet.absoluteFillObject, - logoWrapperAnimation, - { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px - }, - ]}> - <AnimatedLogo style={[{opacity: 0}, logoAnimation]} /> + {isReady && + (isAndroid || reduceMotion === true ? ( + // Use a simple fade on older versions of android (work around a bug) + <> + <Animated.View style={[{flex: 1}, appAnimation]}> + {props.children} </Animated.View> - )} - </> - ) : ( - <MaskedView - style={[StyleSheet.absoluteFillObject]} - maskElement={ - <Animated.View - style={[ - { - // Transparent background because mask is based off alpha channel. - backgroundColor: 'transparent', - flex: 1, - justifyContent: 'center', - alignItems: 'center', - transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px - }, - ]}> - <AnimatedLogo style={[logoAnimation]} /> + + {!isAnimationComplete && ( + <Animated.View + style={[ + StyleSheet.absoluteFillObject, + logoWrapperAnimation, + { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px + }, + ]}> + <AnimatedLogo + fill={isDarkMode ? colors.blue3 : '#fff'} + style={[{opacity: 0}, logoAnimations]} + /> + </Animated.View> + )} + </> + ) : ( + <MaskedView + style={[StyleSheet.absoluteFillObject]} + maskElement={ + <Animated.View + style={[ + { + // Transparent background because mask is based off alpha channel. + backgroundColor: 'transparent', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px + }, + ]}> + <AnimatedLogo + fill={isDarkMode ? colors.blue3 : '#fff'} + style={[logoAnimations]} + /> + </Animated.View> + }> + {!isAnimationComplete && ( + <View + style={[ + StyleSheet.absoluteFillObject, + {backgroundColor: isDarkMode ? colors.blue3 : '#fff'}, + ]} + /> + )} + <Animated.View style={[{flex: 1}, appAnimation]}> + {props.children} </Animated.View> - }> - {!isAnimationComplete && ( - <View - style={[ - StyleSheet.absoluteFillObject, - {backgroundColor: 'white'}, - ]} - /> - )} - <Animated.View style={[{flex: 1}, appAnimation]}> - {props.children} - </Animated.View> - </MaskedView> - )} + </MaskedView> + ))} </View> ) } |