Bluesky app fork with some witchin' additions 馃挮
at main 7.2 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.png' 25// @ts-ignore 26import darkSplashImagePointer from '../assets/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 async () => { 150 // set these values to check animation at specific point 151 outroLogo.set(() => 152 withTiming( 153 1, 154 {duration: 1200, easing: Easing.in(Easing.cubic)}, 155 () => { 156 runOnJS(onFinish)() 157 }, 158 ), 159 ) 160 outroApp.set(() => 161 withTiming(1, { 162 duration: 1200, 163 easing: Easing.inOut(Easing.cubic), 164 }), 165 ) 166 outroAppOpacity.set(() => 167 withTiming(1, { 168 duration: 1200, 169 easing: Easing.in(Easing.cubic), 170 }), 171 ) 172 }, 173 ), 174 ) 175 }) 176 .catch(() => {}) 177 } 178 }, [onFinish, intro, outroLogo, outroApp, outroAppOpacity, isReady]) 179 180 useEffect(() => { 181 AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion) 182 }, []) 183 184 const logoAnimations = 185 reduceMotion === true ? reducedLogoAnimation : logoAnimation 186 // special off-spec color for dark mode 187 const logoBg = isDarkMode ? '#0F1824' : '#fff' 188 189 return ( 190 <View style={{flex: 1}} onLayout={onLayout}> 191 {!isAnimationComplete && ( 192 <View style={StyleSheet.absoluteFillObject}> 193 <Image 194 accessibilityIgnoresInvertColors 195 onLoadEnd={onLoadEnd} 196 source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}} 197 style={StyleSheet.absoluteFillObject} 198 /> 199 200 <Animated.View 201 style={[ 202 bottomLogoAnimation, 203 { 204 position: 'absolute', 205 bottom: insets.bottom + 40, 206 left: 0, 207 right: 0, 208 alignItems: 'center', 209 justifyContent: 'center', 210 opacity: 0, 211 }, 212 ]}> 213 <Logotype fill="#fff" width={90} /> 214 </Animated.View> 215 </View> 216 )} 217 218 {isReady && ( 219 <> 220 <Animated.View style={[{flex: 1}, appAnimation]}> 221 {props.children} 222 </Animated.View> 223 224 {!isAnimationComplete && ( 225 <Animated.View 226 style={[ 227 StyleSheet.absoluteFillObject, 228 logoWrapperAnimation, 229 { 230 flex: 1, 231 justifyContent: 'center', 232 alignItems: 'center', 233 transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px 234 }, 235 ]}> 236 <Animated.View style={[logoAnimations]}> 237 <Logo fill={logoBg} /> 238 </Animated.View> 239 </Animated.View> 240 )} 241 </> 242 )} 243 </View> 244 ) 245}