Bluesky app fork with some witchin' additions 馃挮
at main 246 lines 7.3 kB view raw
1import React, {useCallback, useEffect} 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 = React.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] = React.useState(false) 62 const [isImageLoaded, setIsImageLoaded] = React.useState(false) 63 const [isLayoutReady, setIsLayoutReady] = React.useState(false) 64 const [reduceMotion, setReduceMotion] = React.useState<boolean | undefined>( 65 false, 66 ) 67 const isReady = 68 props.isReady && 69 isImageLoaded && 70 isLayoutReady && 71 reduceMotion !== undefined 72 73 const colorScheme = useColorScheme() 74 const isDarkMode = colorScheme === 'dark' 75 76 const logoAnimation = useAnimatedStyle(() => { 77 return { 78 transform: [ 79 { 80 scale: interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp'), 81 }, 82 { 83 scale: interpolate( 84 outroLogo.get(), 85 [0, 0.08, 1], 86 [1, 0.8, 500], 87 'clamp', 88 ), 89 }, 90 ], 91 opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 92 } 93 }) 94 const bottomLogoAnimation = useAnimatedStyle(() => { 95 return { 96 opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 97 } 98 }) 99 const reducedLogoAnimation = useAnimatedStyle(() => { 100 return { 101 transform: [ 102 { 103 scale: interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp'), 104 }, 105 ], 106 opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 107 } 108 }) 109 110 const logoWrapperAnimation = useAnimatedStyle(() => { 111 return { 112 opacity: interpolate( 113 outroAppOpacity.get(), 114 [0, 0.1, 0.2, 1], 115 [1, 1, 0, 0], 116 'clamp', 117 ), 118 } 119 }) 120 121 const appAnimation = useAnimatedStyle(() => { 122 return { 123 transform: [ 124 { 125 scale: interpolate(outroApp.get(), [0, 1], [1.1, 1], 'clamp'), 126 }, 127 ], 128 opacity: interpolate( 129 outroAppOpacity.get(), 130 [0, 0.1, 0.2, 1], 131 [0, 0, 1, 1], 132 'clamp', 133 ), 134 } 135 }) 136 137 const onFinish = useCallback(() => setIsAnimationComplete(true), []) 138 const onLayout = useCallback(() => setIsLayoutReady(true), []) 139 const onLoadEnd = useCallback(() => setIsImageLoaded(true), []) 140 141 useEffect(() => { 142 if (isReady) { 143 SplashScreen.hideAsync() 144 .then(() => { 145 intro.set(() => 146 withTiming( 147 1, 148 {duration: 400, easing: Easing.out(Easing.cubic)}, 149 () => { 150 'worklet' 151 // set these values to check animation at specific point 152 outroLogo.set(() => 153 withTiming( 154 1, 155 {duration: 1200, easing: Easing.in(Easing.cubic)}, 156 () => { 157 runOnJS(onFinish)() 158 }, 159 ), 160 ) 161 outroApp.set(() => 162 withTiming(1, { 163 duration: 1200, 164 easing: Easing.inOut(Easing.cubic), 165 }), 166 ) 167 outroAppOpacity.set(() => 168 withTiming(1, { 169 duration: 1200, 170 easing: Easing.in(Easing.cubic), 171 }), 172 ) 173 }, 174 ), 175 ) 176 }) 177 .catch(() => {}) 178 } 179 }, [onFinish, intro, outroLogo, outroApp, outroAppOpacity, isReady]) 180 181 useEffect(() => { 182 AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion) 183 }, []) 184 185 const logoAnimations = 186 reduceMotion === true ? reducedLogoAnimation : logoAnimation 187 // special off-spec color for dark mode 188 const logoBg = isDarkMode ? '#0F1824' : '#fff' 189 190 return ( 191 <View style={{flex: 1}} onLayout={onLayout}> 192 {!isAnimationComplete && ( 193 <View style={StyleSheet.absoluteFillObject}> 194 <Image 195 accessibilityIgnoresInvertColors 196 onLoadEnd={onLoadEnd} 197 source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}} 198 style={StyleSheet.absoluteFillObject} 199 /> 200 201 <Animated.View 202 style={[ 203 bottomLogoAnimation, 204 { 205 position: 'absolute', 206 bottom: insets.bottom + 40, 207 left: 0, 208 right: 0, 209 alignItems: 'center', 210 justifyContent: 'center', 211 opacity: 0, 212 }, 213 ]}> 214 <Logotype fill="#fff" width={90} /> 215 </Animated.View> 216 </View> 217 )} 218 219 {isReady && ( 220 <> 221 <Animated.View style={[{flex: 1}, appAnimation]}> 222 {props.children} 223 </Animated.View> 224 225 {!isAnimationComplete && ( 226 <Animated.View 227 style={[ 228 StyleSheet.absoluteFillObject, 229 logoWrapperAnimation, 230 { 231 flex: 1, 232 justifyContent: 'center', 233 alignItems: 'center', 234 transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px 235 }, 236 ]}> 237 <Animated.View style={[logoAnimations]}> 238 <Logo fill={logoBg} /> 239 </Animated.View> 240 </Animated.View> 241 )} 242 </> 243 )} 244 </View> 245 ) 246}