this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at f4e14626aab6b95a89af86a0cade00b9d6bbf505 244 lines 7.3 kB view raw
1import {forwardRef, useCallback, useEffect, useState} from 'react' 2import { 3 AccessibilityInfo, 4 Image as RNImage, 5 StyleSheet, 6 useColorScheme, 7 View, 8} from 'react-native' 9import Animated, { 10 Easing, 11 interpolate, 12 runOnJS, 13 useAnimatedStyle, 14 useSharedValue, 15 withTiming, 16} from 'react-native-reanimated' 17import {useSafeAreaInsets} from 'react-native-safe-area-context' 18import Svg, {Path, type SvgProps} from 'react-native-svg' 19import {Image} from 'expo-image' 20import * as SplashScreen from 'expo-splash-screen' 21 22import {Logotype} from '#/view/icons/Logotype' 23// @ts-ignore 24import splashImagePointer from '../assets/splash/splash.png' 25// @ts-ignore 26import darkSplashImagePointer from '../assets/splash/splash-dark.png' 27const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri 28const darkSplashImageUri = RNImage.resolveAssetSource( 29 darkSplashImagePointer, 30).uri 31 32export const Logo = forwardRef(function LogoImpl(props: SvgProps, ref) { 33 const width = 1000 34 const height = width * (67 / 64) 35 return ( 36 <Svg 37 fill="none" 38 // @ts-ignore it's fiiiiine 39 ref={ref} 40 viewBox="0 0 64 66" 41 style={[{width, height}, props.style]}> 42 <Path 43 fill={props.fill || '#fff'} 44 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" 45 /> 46 </Svg> 47 ) 48}) 49 50type Props = { 51 isReady: boolean 52} 53 54export function Splash(props: React.PropsWithChildren<Props>) { 55 'use no memo' 56 const insets = useSafeAreaInsets() 57 const intro = useSharedValue(0) 58 const outroLogo = useSharedValue(0) 59 const outroApp = useSharedValue(0) 60 const outroAppOpacity = useSharedValue(0) 61 const [isAnimationComplete, setIsAnimationComplete] = useState(false) 62 const [isImageLoaded, setIsImageLoaded] = useState(false) 63 const [isLayoutReady, setIsLayoutReady] = useState(false) 64 const [reduceMotion, setReduceMotion] = useState<boolean | undefined>(false) 65 const isReady = 66 props.isReady && 67 isImageLoaded && 68 isLayoutReady && 69 reduceMotion !== undefined 70 71 const colorScheme = useColorScheme() 72 const isDarkMode = colorScheme === 'dark' 73 74 const logoAnimation = useAnimatedStyle(() => { 75 return { 76 transform: [ 77 { 78 scale: interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp'), 79 }, 80 { 81 scale: interpolate( 82 outroLogo.get(), 83 [0, 0.08, 1], 84 [1, 0.8, 500], 85 'clamp', 86 ), 87 }, 88 ], 89 opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 90 } 91 }) 92 const bottomLogoAnimation = useAnimatedStyle(() => { 93 return { 94 opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 95 } 96 }) 97 const reducedLogoAnimation = useAnimatedStyle(() => { 98 return { 99 transform: [ 100 { 101 scale: interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp'), 102 }, 103 ], 104 opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 105 } 106 }) 107 108 const logoWrapperAnimation = useAnimatedStyle(() => { 109 return { 110 opacity: interpolate( 111 outroAppOpacity.get(), 112 [0, 0.1, 0.2, 1], 113 [1, 1, 0, 0], 114 'clamp', 115 ), 116 } 117 }) 118 119 const appAnimation = useAnimatedStyle(() => { 120 return { 121 transform: [ 122 { 123 scale: interpolate(outroApp.get(), [0, 1], [1.1, 1], 'clamp'), 124 }, 125 ], 126 opacity: interpolate( 127 outroAppOpacity.get(), 128 [0, 0.1, 0.2, 1], 129 [0, 0, 1, 1], 130 'clamp', 131 ), 132 } 133 }) 134 135 const onFinish = useCallback(() => setIsAnimationComplete(true), []) 136 const onLayout = useCallback(() => setIsLayoutReady(true), []) 137 const onLoadEnd = useCallback(() => setIsImageLoaded(true), []) 138 139 useEffect(() => { 140 if (isReady) { 141 SplashScreen.hideAsync() 142 .then(() => { 143 intro.set(() => 144 withTiming( 145 1, 146 {duration: 400, easing: Easing.out(Easing.cubic)}, 147 () => { 148 'worklet' 149 // set these values to check animation at specific point 150 outroLogo.set(() => 151 withTiming( 152 1, 153 {duration: 1200, easing: Easing.in(Easing.cubic)}, 154 () => { 155 runOnJS(onFinish)() 156 }, 157 ), 158 ) 159 outroApp.set(() => 160 withTiming(1, { 161 duration: 1200, 162 easing: Easing.inOut(Easing.cubic), 163 }), 164 ) 165 outroAppOpacity.set(() => 166 withTiming(1, { 167 duration: 1200, 168 easing: Easing.in(Easing.cubic), 169 }), 170 ) 171 }, 172 ), 173 ) 174 }) 175 .catch(() => {}) 176 } 177 }, [onFinish, intro, outroLogo, outroApp, outroAppOpacity, isReady]) 178 179 useEffect(() => { 180 AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion) 181 }, []) 182 183 const logoAnimations = 184 reduceMotion === true ? reducedLogoAnimation : logoAnimation 185 // special off-spec color for dark mode 186 const logoBg = isDarkMode ? '#0F1824' : '#fff' 187 188 return ( 189 <View style={{flex: 1}} onLayout={onLayout}> 190 {!isAnimationComplete && ( 191 <View style={StyleSheet.absoluteFillObject}> 192 <Image 193 accessibilityIgnoresInvertColors 194 onLoadEnd={onLoadEnd} 195 source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}} 196 style={StyleSheet.absoluteFillObject} 197 /> 198 199 <Animated.View 200 style={[ 201 bottomLogoAnimation, 202 { 203 position: 'absolute', 204 bottom: insets.bottom + 40, 205 left: 0, 206 right: 0, 207 alignItems: 'center', 208 justifyContent: 'center', 209 opacity: 0, 210 }, 211 ]}> 212 <Logotype fill="#fff" width={90} /> 213 </Animated.View> 214 </View> 215 )} 216 217 {isReady && ( 218 <> 219 <Animated.View style={[{flex: 1}, appAnimation]}> 220 {props.children} 221 </Animated.View> 222 223 {!isAnimationComplete && ( 224 <Animated.View 225 style={[ 226 StyleSheet.absoluteFillObject, 227 logoWrapperAnimation, 228 { 229 flex: 1, 230 justifyContent: 'center', 231 alignItems: 'center', 232 transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px 233 }, 234 ]}> 235 <Animated.View style={[logoAnimations]}> 236 <Logo fill={logoBg} /> 237 </Animated.View> 238 </Animated.View> 239 )} 240 </> 241 )} 242 </View> 243 ) 244}