this repo has no description
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}