fix(app-expo): use brand splash icon on logout replay overlay

Replace Expo template logo and blue gradient with splash-icon.png and

Co-authored-by: Cursor <cursoragent@cursor.com>
#E6F4FE so post-logout splash matches the native cold-start screen.
This commit is contained in:
Kevin
2026-05-22 14:19:53 +08:00
parent 53e0065e3e
commit 8f66f94a1a
3 changed files with 40 additions and 177 deletions

View File

@@ -1,6 +0,0 @@
.expoLogoBackground {
background-image: linear-gradient(180deg, #3c9ffe, #0274df);
border-radius: 40px;
width: 128px;
height: 128px;
}

View File

@@ -8,9 +8,11 @@ import { completeSplashReplay } from '@/core/splash-replay';
const INITIAL_SCALE_FACTOR = Dimensions.get('screen').height / 90; const INITIAL_SCALE_FACTOR = Dimensions.get('screen').height / 90;
const DURATION = 600; const DURATION = 600;
/** Matches expo-splash-screen `imageWidth` in app.config.ts */
const SPLASH_LOGO_SIZE = 200;
/** Brand gate background (matches native splash / overlay). */ /** Brand gate background matches native splash / adaptiveIcon (#E6F4FE). */
export const BRAND_BOOTSTRAP_BG = '#208AEF'; export const BRAND_BOOTSTRAP_BG = '#E6F4FE';
export function AnimatedSplashOverlay() { export function AnimatedSplashOverlay() {
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
@@ -61,100 +63,41 @@ export function BrandBootstrapLoading() {
); );
} }
const keyframe = new Keyframe({
0: {
transform: [{ scale: INITIAL_SCALE_FACTOR }],
},
100: {
transform: [{ scale: 1 }],
easing: Easing.elastic(0.7),
},
});
const logoKeyframe = new Keyframe({ const logoKeyframe = new Keyframe({
0: { 0: {
transform: [{ scale: 1.3 }], transform: [{ scale: 0.92 }],
opacity: 0, opacity: 0,
}, },
40: {
transform: [{ scale: 1.3 }],
opacity: 0,
easing: Easing.elastic(0.7),
},
100: { 100: {
opacity: 1, opacity: 1,
transform: [{ scale: 1 }], transform: [{ scale: 1 }],
easing: Easing.elastic(0.7), easing: Easing.out(Easing.cubic),
},
});
const glowKeyframe = new Keyframe({
0: {
transform: [{ rotateZ: '0deg' }],
},
100: {
transform: [{ rotateZ: '7200deg' }],
}, },
}); });
export function AnimatedIcon() { export function AnimatedIcon() {
return ( return (
<View style={styles.iconContainer}> <Animated.View
<Animated.View style={styles.logoContainer}
entering={glowKeyframe.duration(60 * 1000 * 4)} entering={logoKeyframe.duration(DURATION)}
style={styles.glow} >
> <Image
<Image style={styles.splashLogo}
style={styles.glow} source={require('@/assets/images/splash-icon.png')}
source={require('@/assets/images/logo-glow.png')} contentFit="contain"
/>
</Animated.View>
<Animated.View
entering={keyframe.duration(DURATION)}
style={styles.background}
/> />
<Animated.View </Animated.View>
style={styles.imageContainer}
entering={logoKeyframe.duration(DURATION)}
>
<Image
style={styles.image}
source={require('@/assets/images/expo-logo.png')}
/>
</Animated.View>
</View>
); );
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
imageContainer: { logoContainer: {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
glow: { splashLogo: {
width: 201, width: SPLASH_LOGO_SIZE,
height: 201, height: SPLASH_LOGO_SIZE,
position: 'absolute',
},
iconContainer: {
justifyContent: 'center',
alignItems: 'center',
width: 128,
height: 128,
zIndex: 100,
},
image: {
position: 'absolute',
width: 76,
height: 71,
},
background: {
borderRadius: 40,
experimental_backgroundImage: `linear-gradient(180deg, #3C9FFE, #0274DF)`,
width: 128,
height: 128,
position: 'absolute',
}, },
backgroundSolidColor: { backgroundSolidColor: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,

View File

@@ -1,12 +1,13 @@
import { Image } from 'expo-image'; import { Image } from 'expo-image';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import Animated, { Keyframe, Easing } from 'react-native-reanimated'; import Animated, { Easing, Keyframe } from 'react-native-reanimated';
import classes from './animated-icon.module.css'; /** Matches expo-splash-screen `imageWidth` in app.config.ts */
const SPLASH_LOGO_SIZE = 200;
const DURATION = 300; const DURATION = 300;
/** Brand gate background (matches native splash / overlay). */ /** Brand gate background matches native splash / adaptiveIcon (#E6F4FE). */
export const BRAND_BOOTSTRAP_BG = '#208AEF'; export const BRAND_BOOTSTRAP_BG = '#E6F4FE';
export function AnimatedSplashOverlay() { export function AnimatedSplashOverlay() {
return null; return null;
@@ -21,81 +22,30 @@ export function BrandBootstrapLoading() {
); );
} }
const keyframe = new Keyframe({
0: {
transform: [{ scale: 0 }],
},
60: {
transform: [{ scale: 1.2 }],
easing: Easing.elastic(1.2),
},
100: {
transform: [{ scale: 1 }],
easing: Easing.elastic(1.2),
},
});
const logoKeyframe = new Keyframe({ const logoKeyframe = new Keyframe({
0: { 0: {
transform: [{ scale: 0.92 }],
opacity: 0, opacity: 0,
}, },
60: {
transform: [{ scale: 1.2 }],
opacity: 0,
easing: Easing.elastic(1.2),
},
100: { 100: {
transform: [{ scale: 1 }], transform: [{ scale: 1 }],
opacity: 1, opacity: 1,
easing: Easing.elastic(1.2), easing: Easing.out(Easing.cubic),
},
});
const glowKeyframe = new Keyframe({
0: {
transform: [{ rotateZ: '-180deg' }, { scale: 0.8 }],
opacity: 0,
},
[DURATION / 1000]: {
transform: [{ rotateZ: '0deg' }, { scale: 1 }],
opacity: 1,
easing: Easing.elastic(0.7),
},
100: {
transform: [{ rotateZ: '7200deg' }],
}, },
}); });
export function AnimatedIcon() { export function AnimatedIcon() {
return ( return (
<View style={styles.iconContainer}> <Animated.View
<Animated.View style={styles.logoContainer}
entering={glowKeyframe.duration(60 * 1000 * 4)} entering={logoKeyframe.duration(DURATION)}
style={styles.glow} >
> <Image
<Image style={styles.splashLogo}
style={styles.glow} source={require('@/assets/images/splash-icon.png')}
source={require('@/assets/images/logo-glow.png')} contentFit="contain"
/> />
</Animated.View> </Animated.View>
<Animated.View
style={styles.background}
entering={keyframe.duration(DURATION)}
>
<div className={classes.expoLogoBackground} />
</Animated.View>
<Animated.View
style={styles.imageContainer}
entering={logoKeyframe.duration(DURATION)}
>
<Image
style={styles.image}
source={require('@/assets/images/expo-logo.png')}
/>
</Animated.View>
</View>
); );
} }
@@ -106,36 +56,12 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
container: { logoContainer: {
alignItems: 'center',
width: '100%',
zIndex: 1000,
position: 'absolute',
top: 128 / 2 + 138,
},
imageContainer: {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
glow: { splashLogo: {
width: 201, width: SPLASH_LOGO_SIZE,
height: 201, height: SPLASH_LOGO_SIZE,
position: 'absolute',
},
iconContainer: {
justifyContent: 'center',
alignItems: 'center',
width: 128,
height: 128,
},
image: {
position: 'absolute',
width: 76,
height: 71,
},
background: {
width: 128,
height: 128,
position: 'absolute',
}, },
}); });