Splash: reduce motion + dark mode (#2448)
* Don't use mask for android at all * Handle reduced motion * Add dark splash * Add dark config * Fix android version code --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>zio/stable
parent
fcfebda469
commit
9eeff2cc5c
|
@ -66,6 +66,12 @@ module.exports = function () {
|
|||
'Used for profile pictures, posts, and other kinds of content',
|
||||
},
|
||||
associatedDomains: ['applinks:bsky.app', 'applinks:staging.bsky.app'],
|
||||
splash: {
|
||||
dark: {
|
||||
image: './assets/splash-dark.png',
|
||||
backgroundColor: '#001429',
|
||||
},
|
||||
},
|
||||
},
|
||||
androidStatusBar: {
|
||||
barStyle: 'dark-content',
|
||||
|
@ -95,6 +101,12 @@ module.exports = function () {
|
|||
category: ['BROWSABLE', 'DEFAULT'],
|
||||
},
|
||||
],
|
||||
splash: {
|
||||
dark: {
|
||||
image: './assets/splash-dark.png',
|
||||
backgroundColor: '#001429',
|
||||
},
|
||||
},
|
||||
},
|
||||
web: {
|
||||
favicon: './assets/favicon.png',
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 991 KiB |
164
src/Splash.tsx
164
src/Splash.tsx
|
@ -1,5 +1,11 @@
|
|||
import React, {useCallback, useEffect} from 'react'
|
||||
import {View, StyleSheet, Image as RNImage} from 'react-native'
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Image as RNImage,
|
||||
AccessibilityInfo,
|
||||
useColorScheme,
|
||||
} from 'react-native'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import {Image} from 'expo-image'
|
||||
import Animated, {
|
||||
|
@ -15,10 +21,17 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|||
import Svg, {Path, SvgProps} from 'react-native-svg'
|
||||
|
||||
import {isAndroid} from '#/platform/detection'
|
||||
import {useColorMode} from 'state/shell'
|
||||
import {colors} from '#/lib/styles'
|
||||
|
||||
// @ts-ignore
|
||||
import splashImagePointer from '../assets/splash.png'
|
||||
// @ts-ignore
|
||||
import darkSplashImagePointer from '../assets/splash-dark.png'
|
||||
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri
|
||||
const darkSplashImageUri = RNImage.resolveAssetSource(
|
||||
darkSplashImagePointer,
|
||||
).uri
|
||||
|
||||
export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) {
|
||||
const width = 1000
|
||||
|
@ -29,9 +42,9 @@ export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) {
|
|||
// @ts-ignore it's fiiiiine
|
||||
ref={ref}
|
||||
viewBox="0 0 64 66"
|
||||
style={{width, height}}>
|
||||
style={[{width, height}, props.style]}>
|
||||
<Path
|
||||
fill="#fff"
|
||||
fill={props.fill || '#fff'}
|
||||
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"
|
||||
/>
|
||||
</Svg>
|
||||
|
@ -53,7 +66,19 @@ export function Splash(props: React.PropsWithChildren<Props>) {
|
|||
const [isAnimationComplete, setIsAnimationComplete] = React.useState(false)
|
||||
const [isImageLoaded, setIsImageLoaded] = React.useState(false)
|
||||
const [isLayoutReady, setIsLayoutReady] = React.useState(false)
|
||||
const isReady = props.isReady && isImageLoaded && isLayoutReady
|
||||
const [reduceMotion, setReduceMotion] = React.useState<boolean | undefined>(
|
||||
false,
|
||||
)
|
||||
const isReady =
|
||||
props.isReady &&
|
||||
isImageLoaded &&
|
||||
isLayoutReady &&
|
||||
reduceMotion !== undefined
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const colorScheme = useColorScheme()
|
||||
const themeName = colorMode === 'system' ? colorScheme : colorMode
|
||||
const isDarkMode = themeName === 'dark'
|
||||
|
||||
const logoAnimation = useAnimatedStyle(() => {
|
||||
return {
|
||||
|
@ -73,6 +98,17 @@ export function Splash(props: React.PropsWithChildren<Props>) {
|
|||
opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'),
|
||||
}
|
||||
})
|
||||
const reducedLogoAnimation = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [
|
||||
{
|
||||
scale: interpolate(intro.value, [0, 1], [0.8, 1], 'clamp'),
|
||||
},
|
||||
],
|
||||
opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'),
|
||||
}
|
||||
})
|
||||
|
||||
const logoWrapperAnimation = useAnimatedStyle(() => {
|
||||
return {
|
||||
opacity: interpolate(
|
||||
|
@ -137,71 +173,85 @@ export function Splash(props: React.PropsWithChildren<Props>) {
|
|||
}
|
||||
}, [onFinish, intro, outroLogo, outroApp, outroAppOpacity, isReady])
|
||||
|
||||
useEffect(() => {
|
||||
AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion)
|
||||
}, [])
|
||||
|
||||
const logoAnimations =
|
||||
reduceMotion === true ? reducedLogoAnimation : logoAnimation
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}} onLayout={onLayout}>
|
||||
{!isAnimationComplete && (
|
||||
<Image
|
||||
accessibilityIgnoresInvertColors
|
||||
onLoadEnd={onLoadEnd}
|
||||
source={{uri: splashImageUri}}
|
||||
source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}}
|
||||
style={StyleSheet.absoluteFillObject}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isAndroid ? (
|
||||
// Use a simple fade on older versions of android (work around a bug)
|
||||
<>
|
||||
<Animated.View style={[{flex: 1}, appAnimation]}>
|
||||
{props.children}
|
||||
</Animated.View>
|
||||
{isReady &&
|
||||
(isAndroid || reduceMotion === true ? (
|
||||
// Use a simple fade on older versions of android (work around a bug)
|
||||
<>
|
||||
<Animated.View style={[{flex: 1}, appAnimation]}>
|
||||
{props.children}
|
||||
</Animated.View>
|
||||
|
||||
{!isAnimationComplete && (
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFillObject,
|
||||
logoWrapperAnimation,
|
||||
{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px
|
||||
},
|
||||
]}>
|
||||
<AnimatedLogo style={[{opacity: 0}, logoAnimation]} />
|
||||
{!isAnimationComplete && (
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFillObject,
|
||||
logoWrapperAnimation,
|
||||
{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px
|
||||
},
|
||||
]}>
|
||||
<AnimatedLogo
|
||||
fill={isDarkMode ? colors.blue3 : '#fff'}
|
||||
style={[{opacity: 0}, logoAnimations]}
|
||||
/>
|
||||
</Animated.View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<MaskedView
|
||||
style={[StyleSheet.absoluteFillObject]}
|
||||
maskElement={
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
// Transparent background because mask is based off alpha channel.
|
||||
backgroundColor: 'transparent',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px
|
||||
},
|
||||
]}>
|
||||
<AnimatedLogo
|
||||
fill={isDarkMode ? colors.blue3 : '#fff'}
|
||||
style={[logoAnimations]}
|
||||
/>
|
||||
</Animated.View>
|
||||
}>
|
||||
{!isAnimationComplete && (
|
||||
<View
|
||||
style={[
|
||||
StyleSheet.absoluteFillObject,
|
||||
{backgroundColor: isDarkMode ? colors.blue3 : '#fff'},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Animated.View style={[{flex: 1}, appAnimation]}>
|
||||
{props.children}
|
||||
</Animated.View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<MaskedView
|
||||
style={[StyleSheet.absoluteFillObject]}
|
||||
maskElement={
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
// Transparent background because mask is based off alpha channel.
|
||||
backgroundColor: 'transparent',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px
|
||||
},
|
||||
]}>
|
||||
<AnimatedLogo style={[logoAnimation]} />
|
||||
</Animated.View>
|
||||
}>
|
||||
{!isAnimationComplete && (
|
||||
<View
|
||||
style={[
|
||||
StyleSheet.absoluteFillObject,
|
||||
{backgroundColor: 'white'},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Animated.View style={[{flex: 1}, appAnimation]}>
|
||||
{props.children}
|
||||
</Animated.View>
|
||||
</MaskedView>
|
||||
)}
|
||||
</MaskedView>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue