Files
CBS-Mobile-App/src/modules/auth/screens/SplashScreen.tsx
T
Abin ToppoandClaude Opus 5.5 f960a119e8 Add Positive Pay, statement guide v1.0, onboarding, and switch every feature on
- Positive Pay on the 12 Oct /pps contract (account number, not account ID):
  OTP enrollment, cheque submit, saved cheques, detail and stop payment.
  Full flow passed on the phone against mobile-banking-backend.lincpay.
- Statement & Mini Statement guide v1.0 (14 Oct): mini statement JSON on
  Home and the Statement screen; dated PDF export with the password rule.
- Onboarding slides and splash screen, Inter font, shared Icon and
  TypewriterText components, nearby branches via OpenStreetMap.
- Every feature switched on; the 14 Oct switch set is kept in
  featureConfig.stage-2026-10-14.ts. Loans run on demo data until the
  backend has /loans routes.
- npm run android wraps run-android for shells spawned from VS Code.
- OpenProject work packages updated to 14-10-2026 status, with screenshots
  of Positive Pay, statement, loans and cards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:18:40 +05:30

156 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { memo, useMemo } from 'react';
import { StatusBar, StyleSheet, Text, useWindowDimensions, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import Svg, { G, Path, Rect } from 'react-native-svg';
import Animated, { FadeIn, FadeInUp } from 'react-native-reanimated';
import { useTheme } from '../../../theme';
import { BrandLogo } from '../components/BrandLogo';
/**
* The skyline, on a 400 × 220 canvas anchored to the bottom edge.
*
* Two layers of towers: a far row drawn in a translucent white, so it reads as
* haze against whatever navy the theme supplies, and a near row in the wash's
* darkest stop, with lit windows. Fixed geometry rather than random, so the
* splash is the same picture on every launch.
*/
const FAR: ReadonlyArray<[x: number, w: number, h: number]> = [
[0, 34, 92], [30, 26, 120], [52, 40, 84], [88, 22, 140], [106, 36, 104],
[140, 30, 156], [196, 24, 128], [224, 42, 96], [262, 28, 150], [286, 38, 110],
[320, 24, 132], [340, 36, 88], [372, 28, 118],
];
const NEAR: ReadonlyArray<[x: number, w: number, h: number]> = [
[-4, 40, 64], [32, 30, 96], [60, 46, 72], [104, 28, 112], [150, 32, 128],
[210, 30, 104], [238, 44, 70], [280, 32, 118], [310, 40, 80], [348, 30, 100],
[376, 30, 66],
];
const CANVAS_W = 400;
const CANVAS_H = 220;
/** Lit windows for one near tower — a deterministic sprinkle, not a grid. */
const windowsFor = (x: number, w: number, h: number, seed: number) => {
const lit: Array<{ x: number; y: number }> = [];
const columns = Math.max(1, Math.floor((w - 8) / 8));
for (let row = 0; row * 10 + 14 < h - 6; row += 1) {
for (let col = 0; col < columns; col += 1) {
if ((row * 5 + col * 3 + seed) % 4 === 0) {
lit.push({ x: x + 6 + col * 8, y: CANVAS_H - h + 10 + row * 10 });
}
}
}
return lit;
};
const Skyline = memo(({ width }: { width: number }) => {
const theme = useTheme();
const nearColor = theme.colors.brandGradient[2];
const windows = useMemo(
() => NEAR.flatMap(([x, w, h], i) => windowsFor(x, w, h, i)),
[],
);
return (
<Svg
width={width}
height={(width * CANVAS_H) / CANVAS_W}
viewBox={`0 0 ${CANVAS_W} ${CANVAS_H}`}
preserveAspectRatio="xMidYMax slice"
>
{/* far row */}
<G fill={theme.colors.onBrandSurface}>
{FAR.map(([x, w, h]) => (
<Rect key={`f${x}`} x={x} y={CANVAS_H - h} width={w} height={h} />
))}
{/* the landmark tower, with its spire */}
<Rect x={170} y={CANVAS_H - 188} width={26} height={188} />
<Path d={`M176 ${CANVAS_H - 188} L183 ${CANVAS_H - 214} L190 ${CANVAS_H - 188} Z`} />
</G>
{/* near row */}
<G fill={nearColor}>
{NEAR.map(([x, w, h]) => (
<Rect key={`n${x}`} x={x} y={CANVAS_H - h} width={w} height={h} />
))}
</G>
{/* windows */}
<G fill={theme.colors.accentFill} opacity={0.75}>
{windows.map((pane) => (
<Rect key={`${pane.x}-${pane.y}`} x={pane.x} y={pane.y} width={3} height={4} />
))}
</G>
</Svg>
);
});
/**
* The launch screen, shown while the app restores its session and resolves
* its feature flags.
*
* The navy wash with the bank's logo on its white plate, a one-line promise,
* and the skyline along the foot. Light status-bar icons in both themes, since
* the wash is dark in both.
*/
const SplashScreenComponent = () => {
const theme = useTheme();
const { width } = useWindowDimensions();
return (
<View style={styles.flex}>
<StatusBar barStyle="light-content" backgroundColor="transparent" translucent />
<LinearGradient
colors={theme.colors.brandGradient}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
style={StyleSheet.absoluteFill}
/>
<View style={styles.skyline} pointerEvents="none">
<Skyline width={width} />
</View>
<View style={styles.centre}>
<Animated.View entering={FadeIn.duration(500)}>
<BrandLogo width={210} />
</Animated.View>
<Animated.View entering={FadeInUp.delay(250).duration(500)}>
<Text style={[theme.text.body, styles.tagline, { color: theme.colors.onBrandSecondary }]}>
Banking for a brighter tomorrow
</Text>
</Animated.View>
</View>
</View>
);
};
const styles = StyleSheet.create({
flex: {
flex: 1,
},
centre: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: 18,
// Sits the lock-up a little above centre, clear of the skyline.
paddingBottom: '30%',
},
tagline: {
textAlign: 'center',
},
skyline: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
},
});
export const SplashScreen = memo(SplashScreenComponent);