about summary refs log tree commit diff
path: root/src/Splash.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/Splash.tsx')
-rw-r--r--src/Splash.tsx216
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>
   )
 }