import {forwardRef, useCallback, useEffect, useState} from 'react'
import {
AccessibilityInfo,
Image as RNImage,
useColorScheme,
View,
} from 'react-native'
import Animated, {
Easing,
interpolate,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import Svg, {Path, type SvgProps} from 'react-native-svg'
import {scheduleOnRN} from 'react-native-worklets'
import {Image} from 'expo-image'
import * as SplashScreen from 'expo-splash-screen'
import {Logotype} from '#/view/icons/Logotype'
import {atoms as a} from '#/alf'
// @ts-expect-error
import splashImagePointer from '../assets/splash/splash.png'
// @ts-expect-error
import darkSplashImagePointer from '../assets/splash/splash-dark.png'
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer)!.uri
const darkSplashImageUri = RNImage.resolveAssetSource(
darkSplashImagePointer,
)!.uri
export const Logo = forwardRef(function LogoImpl(props: SvgProps, ref) {
const width = 1000
const height = width * (67 / 64)
return (
)
})
type Props = {
isReady: boolean
}
export function Splash(props: React.PropsWithChildren) {
'use no memo'
const insets = useSafeAreaInsets()
const intro = useSharedValue(0)
const outroLogo = useSharedValue(0)
const outroApp = useSharedValue(0)
const outroAppOpacity = useSharedValue(0)
const [isAnimationComplete, setIsAnimationComplete] = useState(false)
const [isImageLoaded, setIsImageLoaded] = useState(false)
const [isLayoutReady, setIsLayoutReady] = useState(false)
const [reduceMotion, setReduceMotion] = useState(false)
const isReady =
props.isReady &&
isImageLoaded &&
isLayoutReady &&
reduceMotion !== undefined
const colorScheme = useColorScheme()
const isDarkMode = colorScheme === 'dark'
const logoAnimation = useAnimatedStyle(() => {
const introScale = interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp')
const outroScale =
reduceMotion === true
? 1
: interpolate(outroLogo.get(), [0, 0.08, 1], [1, 0.8, 500], 'clamp')
const introOpacity = interpolate(intro.get(), [0, 1], [0, 1], 'clamp')
const outroOpacity = interpolate(
outroAppOpacity.get(),
[0, 0.1, 0.2, 1],
[1, 1, 0, 0],
'clamp',
)
return {
opacity: introOpacity * outroOpacity,
transform: [
{translateY: -(insets.top / 2)},
{scale: 0.1 * outroScale * introScale},
],
}
})
const bottomLogoAnimation = useAnimatedStyle(() => {
return {
opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'),
}
})
const appAnimation = useAnimatedStyle(() => {
return {
transform: [
{
scale: interpolate(outroApp.get(), [0, 1], [1.1, 1], 'clamp'),
},
],
opacity: interpolate(
outroAppOpacity.get(),
[0, 0.1, 0.2, 1],
[0.02, 0.02, 1, 1], // first two values cant be 0 for the iOS blur/glass effects to work, the values obtained by trial and error
'clamp',
),
}
})
const onFinish = useCallback(() => setIsAnimationComplete(true), [])
const onLayout = useCallback(() => setIsLayoutReady(true), [])
const onLoadEnd = useCallback(() => setIsImageLoaded(true), [])
useEffect(() => {
if (isReady) {
SplashScreen.hideAsync()
.then(() => {
intro.set(
withTiming(
1,
{duration: 400, easing: Easing.out(Easing.cubic)},
() => {
'worklet'
// set these values to check animation at specific point
outroLogo.set(
withTiming(
1,
{duration: 1200, easing: Easing.in(Easing.cubic)},
() => {
scheduleOnRN(onFinish)
},
),
)
outroApp.set(
withTiming(1, {
duration: 1200,
easing: Easing.inOut(Easing.cubic),
}),
)
outroAppOpacity.set(
withTiming(1, {
duration: 1200,
easing: Easing.in(Easing.cubic),
}),
)
},
),
)
})
.catch(() => {})
}
}, [onFinish, intro, outroLogo, outroApp, outroAppOpacity, isReady])
useEffect(() => {
AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion)
}, [])
// special off-spec color for dark mode
const logoBg = isDarkMode ? '#0F1824' : '#fff'
return (
{!isAnimationComplete && (
)}
{isReady && (
<>
{props.children}
{!isAnimationComplete && (
)}
>
)}
)
}