diff options
Diffstat (limited to 'src/Splash.tsx')
-rw-r--r-- | src/Splash.tsx | 216 |
1 files changed, 152 insertions, 64 deletions
diff --git a/src/Splash.tsx b/src/Splash.tsx index fc70bb3b7..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, { @@ -14,9 +20,18 @@ import MaskedView from '@react-native-masked-view/masked-view' 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 @@ -27,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> @@ -50,9 +65,22 @@ export function Splash(props: React.PropsWithChildren<Props>) { const outroAppOpacity = useSharedValue(0) const [isAnimationComplete, setIsAnimationComplete] = React.useState(false) const [isImageLoaded, setIsImageLoaded] = React.useState(false) - const isReady = props.isReady && isImageLoaded + const [isLayoutReady, setIsLayoutReady] = React.useState(false) + 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 logoAnimations = useAnimatedStyle(() => { + const logoAnimation = useAnimatedStyle(() => { return { transform: [ { @@ -62,7 +90,7 @@ export function Splash(props: React.PropsWithChildren<Props>) { scale: interpolate( outroLogo.value, [0, 0.08, 1], - [1, 0.8, 400], + [1, 0.8, 500], 'clamp', ), }, @@ -70,6 +98,27 @@ 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( + outroAppOpacity.value, + [0, 0.1, 0.2, 1], + [1, 1, 0, 0], + 'clamp', + ), + } + }) const appAnimation = useAnimatedStyle(() => { return { @@ -80,7 +129,7 @@ export function Splash(props: React.PropsWithChildren<Props>) { ], opacity: interpolate( outroAppOpacity.value, - [0, 0.08, 0.15, 1], + [0, 0.1, 0.2, 1], [0, 0, 1, 1], 'clamp', ), @@ -88,82 +137,121 @@ export function Splash(props: React.PropsWithChildren<Props>) { }) const onFinish = useCallback(() => setIsAnimationComplete(true), []) + const onLayout = useCallback(() => setIsLayoutReady(true), []) + const onLoadEnd = useCallback(() => setIsImageLoaded(true), []) useEffect(() => { if (isReady) { - // hide on mount - SplashScreen.hideAsync().catch(() => {}) - - intro.value = withTiming( - 1, - {duration: 400, easing: Easing.out(Easing.cubic)}, - async () => { - // set these values to check animation at specific point - // outroLogo.value = 0.1 - // outroApp.value = 0.1 - outroLogo.value = withTiming( + SplashScreen.hideAsync() + .then(() => { + intro.value = withTiming( 1, - {duration: 1200, easing: Easing.in(Easing.cubic)}, - () => { - runOnJS(onFinish)() + {duration: 400, easing: Easing.out(Easing.cubic)}, + async () => { + // set these values to check animation at specific point + // outroLogo.value = 0.1 + // outroApp.value = 0.1 + outroLogo.value = withTiming( + 1, + {duration: 1200, easing: Easing.in(Easing.cubic)}, + () => { + runOnJS(onFinish)() + }, + ) + outroApp.value = withTiming(1, { + duration: 1200, + easing: Easing.inOut(Easing.cubic), + }) + outroAppOpacity.value = withTiming(1, { + duration: 1200, + easing: Easing.in(Easing.cubic), + }) }, ) - outroApp.value = withTiming(1, { - duration: 1200, - easing: Easing.inOut(Easing.cubic), - }) - outroAppOpacity.value = withTiming(1, { - duration: 1200, - easing: Easing.in(Easing.cubic), - }) - }, - ) + }) + .catch(() => {}) } }, [onFinish, intro, outroLogo, outroApp, outroAppOpacity, isReady]) - const onLoadEnd = useCallback(() => { - setIsImageLoaded(true) - }, [setIsImageLoaded]) + useEffect(() => { + AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion) + }, []) + + const logoAnimations = + reduceMotion === true ? reducedLogoAnimation : logoAnimation return ( - <View style={{flex: 1}}> + <View style={{flex: 1}} onLayout={onLayout}> {!isAnimationComplete && ( <Image accessibilityIgnoresInvertColors onLoadEnd={onLoadEnd} - source={{uri: splashImageUri}} + source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}} style={StyleSheet.absoluteFillObject} /> )} - <MaskedView - style={[StyleSheet.absoluteFillObject]} - maskElement={ - <Animated.View - style={[ - StyleSheet.absoluteFillObject, - { - // 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={[logoAnimations]} /> - </Animated.View> - }> - {!isAnimationComplete && ( - <View - style={[StyleSheet.absoluteFillObject, {backgroundColor: 'white'}]} - /> - )} - - <Animated.View style={[{flex: 1}, appAnimation]}> - {props.children} - </Animated.View> - </MaskedView> + {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> + + {!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> + </MaskedView> + ))} </View> ) } |