Kantongku: onboarding personalisasi (nama + gender avatar 3D) + splash mewah + greeting dinamis
This commit is contained in:
@@ -1,15 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ActivityIndicator, StatusBar, StyleSheet, Text, View } from 'react-native';
|
import { ActivityIndicator, StatusBar, StyleSheet, Text, View } from 'react-native';
|
||||||
|
import * as SplashScreen from 'expo-splash-screen';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { initDatabase } from './src/db/database';
|
import { initDatabase } from './src/db/database';
|
||||||
import { checkRecurringDue } from './src/db/repositories';
|
import { checkRecurringDue, getSetting } from './src/db/repositories';
|
||||||
import { RootNavigator } from './src/navigation';
|
import { RootNavigator } from './src/navigation';
|
||||||
import { colors } from './src/theme/colors';
|
import { colors } from './src/theme/colors';
|
||||||
|
|
||||||
|
SplashScreen.preventAutoHideAsync().catch(() => {});
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [initialRoute, setInitialRoute] = useState<'Main' | 'Onboarding'>('Onboarding');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initDatabase()
|
initDatabase()
|
||||||
@@ -20,7 +24,10 @@ export default function App() {
|
|||||||
} catch {
|
} catch {
|
||||||
// jangan sampai mengganggu startup
|
// jangan sampai mengganggu startup
|
||||||
}
|
}
|
||||||
|
const onboarded = await getSetting('onboarded');
|
||||||
|
setInitialRoute(onboarded === '1' ? 'Main' : 'Onboarding');
|
||||||
setReady(true);
|
setReady(true);
|
||||||
|
SplashScreen.hideAsync().catch(() => {});
|
||||||
})
|
})
|
||||||
.catch((e) => setError(String(e)));
|
.catch((e) => setError(String(e)));
|
||||||
}, []);
|
}, []);
|
||||||
@@ -46,7 +53,7 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<NavigationContainer>
|
<NavigationContainer>
|
||||||
<RootNavigator />
|
<RootNavigator initialRoute={initialRoute} />
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
barStyle="light-content"
|
barStyle="light-content"
|
||||||
|
|||||||
@@ -24,7 +24,16 @@
|
|||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"expo-sqlite",
|
"expo-sqlite",
|
||||||
"expo-sharing"
|
"expo-sharing",
|
||||||
|
[
|
||||||
|
"expo-splash-screen",
|
||||||
|
{
|
||||||
|
"backgroundColor": "#0D9488",
|
||||||
|
"image": "./assets/splash-logo.png",
|
||||||
|
"imageWidth": 220,
|
||||||
|
"resizeMode": "contain"
|
||||||
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
Generated
+15
@@ -18,6 +18,7 @@
|
|||||||
"expo-file-system": "~57.0.4",
|
"expo-file-system": "~57.0.4",
|
||||||
"expo-linear-gradient": "~57.0.1",
|
"expo-linear-gradient": "~57.0.1",
|
||||||
"expo-sharing": "~57.0.12",
|
"expo-sharing": "~57.0.12",
|
||||||
|
"expo-splash-screen": "~57.0.6",
|
||||||
"expo-sqlite": "~57.0.1",
|
"expo-sqlite": "~57.0.1",
|
||||||
"expo-status-bar": "~57.0.1",
|
"expo-status-bar": "~57.0.1",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
@@ -3064,6 +3065,20 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-splash-screen": {
|
||||||
|
"version": "57.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-57.0.6.tgz",
|
||||||
|
"integrity": "sha512-FY0E7hMyXVAsNh18yGzIjdooPXdjafalEb6mk2EcqNPjXspHuu+pVEhUscEunw4wF8F/w6MOpT7UfOwHVWez9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@expo/config-plugins": "~57.0.7",
|
||||||
|
"@expo/image-utils": "^0.11.4",
|
||||||
|
"xml2js": "0.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-sqlite": {
|
"node_modules/expo-sqlite": {
|
||||||
"version": "57.0.1",
|
"version": "57.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-57.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-57.0.1.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"expo-file-system": "~57.0.4",
|
"expo-file-system": "~57.0.4",
|
||||||
"expo-linear-gradient": "~57.0.1",
|
"expo-linear-gradient": "~57.0.1",
|
||||||
"expo-sharing": "~57.0.12",
|
"expo-sharing": "~57.0.12",
|
||||||
|
"expo-splash-screen": "~57.0.6",
|
||||||
"expo-sqlite": "~57.0.1",
|
"expo-sqlite": "~57.0.1",
|
||||||
"expo-status-bar": "~57.0.1",
|
"expo-status-bar": "~57.0.1",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { EditTransactionScreen } from '../screens/EditTransactionScreen';
|
|||||||
import { RecurringFormScreen } from '../screens/RecurringFormScreen';
|
import { RecurringFormScreen } from '../screens/RecurringFormScreen';
|
||||||
import { TargetFormScreen } from '../screens/TargetFormScreen';
|
import { TargetFormScreen } from '../screens/TargetFormScreen';
|
||||||
import { CategoryFormScreen } from '../screens/CategoryFormScreen';
|
import { CategoryFormScreen } from '../screens/CategoryFormScreen';
|
||||||
|
import { OnboardingScreen } from '../screens/OnboardingScreen';
|
||||||
import { SavingsScreen } from '../screens/SavingsScreen';
|
import { SavingsScreen } from '../screens/SavingsScreen';
|
||||||
import { SettingsScreen } from '../screens/SettingsScreen';
|
import { SettingsScreen } from '../screens/SettingsScreen';
|
||||||
import type { TransactionType } from '../types';
|
import type { TransactionType } from '../types';
|
||||||
@@ -24,6 +25,7 @@ export type RootTabParamList = {
|
|||||||
|
|
||||||
export type RootStackParamList = {
|
export type RootStackParamList = {
|
||||||
Main: undefined;
|
Main: undefined;
|
||||||
|
Onboarding: undefined;
|
||||||
EditTransaksi: { transactionId: number };
|
EditTransaksi: { transactionId: number };
|
||||||
RecurringForm: undefined;
|
RecurringForm: undefined;
|
||||||
TargetForm: undefined;
|
TargetForm: undefined;
|
||||||
@@ -132,10 +134,18 @@ function MainTabs() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RootNavigator() {
|
export function RootNavigator({
|
||||||
|
initialRoute = 'Main',
|
||||||
|
}: {
|
||||||
|
initialRoute?: 'Main' | 'Onboarding';
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
<Stack.Navigator
|
||||||
|
initialRouteName={initialRoute}
|
||||||
|
screenOptions={{ headerShown: false }}
|
||||||
|
>
|
||||||
<Stack.Screen name="Main" component={MainTabs} />
|
<Stack.Screen name="Main" component={MainTabs} />
|
||||||
|
<Stack.Screen name="Onboarding" component={OnboardingScreen} />
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="EditTransaksi"
|
name="EditTransaksi"
|
||||||
component={EditTransactionScreen}
|
component={EditTransactionScreen}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
getSavingsMonthComparison,
|
getSavingsMonthComparison,
|
||||||
getSavingsStreak,
|
getSavingsStreak,
|
||||||
getSavingsTotal,
|
getSavingsTotal,
|
||||||
|
getSetting,
|
||||||
getTodaySummary,
|
getTodaySummary,
|
||||||
getTopExpenseCategory,
|
getTopExpenseCategory,
|
||||||
getTransactions,
|
getTransactions,
|
||||||
@@ -47,6 +48,8 @@ export function DashboardScreen() {
|
|||||||
const [recent, setRecent] = useState<TransactionWithCategory[]>([]);
|
const [recent, setRecent] = useState<TransactionWithCategory[]>([]);
|
||||||
const [compare, setCompare] = useState({ current: 0, previous: 0 });
|
const [compare, setCompare] = useState({ current: 0, previous: 0 });
|
||||||
const [hidden, setHidden] = useState(true);
|
const [hidden, setHidden] = useState(true);
|
||||||
|
const [userName, setUserName] = useState('');
|
||||||
|
const [userGender, setUserGender] = useState('');
|
||||||
const heartScale = useRef(new Animated.Value(1)).current;
|
const heartScale = useRef(new Animated.Value(1)).current;
|
||||||
const hpAnim = useRef(new Animated.Value(0)).current;
|
const hpAnim = useRef(new Animated.Value(0)).current;
|
||||||
const shimmerX = useRef(new Animated.Value(-140)).current;
|
const shimmerX = useRef(new Animated.Value(-140)).current;
|
||||||
@@ -85,7 +88,7 @@ export function DashboardScreen() {
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
const [b, s, st, t, r, c, m, top, topLast] = await Promise.all([
|
const [b, s, st, t, r, c, m, top, topLast, un, ug] = await Promise.all([
|
||||||
getBalance(),
|
getBalance(),
|
||||||
getSavingsTotal(),
|
getSavingsTotal(),
|
||||||
getSavingsStreak(),
|
getSavingsStreak(),
|
||||||
@@ -95,6 +98,8 @@ export function DashboardScreen() {
|
|||||||
getMonthSummary(),
|
getMonthSummary(),
|
||||||
getTopExpenseCategory(),
|
getTopExpenseCategory(),
|
||||||
getTopExpenseCategory('-1 month'),
|
getTopExpenseCategory('-1 month'),
|
||||||
|
getSetting('user_name'),
|
||||||
|
getSetting('user_gender'),
|
||||||
]);
|
]);
|
||||||
setBalance(b);
|
setBalance(b);
|
||||||
setSavings(s);
|
setSavings(s);
|
||||||
@@ -106,6 +111,8 @@ export function DashboardScreen() {
|
|||||||
setMonthIncome(m.income);
|
setMonthIncome(m.income);
|
||||||
setTopCategory(top);
|
setTopCategory(top);
|
||||||
setTopCategoryLast(topLast);
|
setTopCategoryLast(topLast);
|
||||||
|
setUserName(un ?? '');
|
||||||
|
setUserGender(ug ?? '');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
@@ -267,12 +274,22 @@ export function DashboardScreen() {
|
|||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
/>
|
/>
|
||||||
<View style={styles.headerText}>
|
<View style={styles.headerText}>
|
||||||
<Text style={styles.greeting}>Halo, Mas 👋</Text>
|
<Text style={styles.greeting}>Halo, {userName || 'Mas'} 👋</Text>
|
||||||
<Text style={styles.date}>{todayLabel()}</Text>
|
<Text style={styles.date}>{todayLabel()}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.avatarCircle}>
|
{userGender === 'female' ? (
|
||||||
<Text style={styles.avatarText}>N</Text>
|
<Image
|
||||||
</View>
|
source={require('../../assets/avatar-female.png')}
|
||||||
|
style={styles.avatarImg}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Image
|
||||||
|
source={require('../../assets/avatar-male.png')}
|
||||||
|
style={styles.avatarImg}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.quoteBubble}>
|
<View style={styles.quoteBubble}>
|
||||||
<Ionicons name="leaf" size={14} color={colors.primaryDark} />
|
<Ionicons name="leaf" size={14} color={colors.primaryDark} />
|
||||||
@@ -701,15 +718,10 @@ const styles = StyleSheet.create({
|
|||||||
headerText: { flex: 1 },
|
headerText: { flex: 1 },
|
||||||
greeting: { fontSize: 22, fontWeight: '800', color: colors.text },
|
greeting: { fontSize: 22, fontWeight: '800', color: colors.text },
|
||||||
date: { fontSize: 13, color: colors.textMuted, marginTop: 2 },
|
date: { fontSize: 13, color: colors.textMuted, marginTop: 2 },
|
||||||
avatarCircle: {
|
avatarImg: {
|
||||||
width: 40,
|
width: 46,
|
||||||
height: 40,
|
height: 52,
|
||||||
borderRadius: 20,
|
|
||||||
backgroundColor: colors.primary,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
},
|
||||||
avatarText: { color: colors.white, fontWeight: '800', fontSize: 16 },
|
|
||||||
quoteBubble: {
|
quoteBubble: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -0,0 +1,313 @@
|
|||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Image,
|
||||||
|
KeyboardAvoidingView,
|
||||||
|
Platform,
|
||||||
|
Pressable,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
import { setSetting } from '../db/repositories';
|
||||||
|
import type { RootStackParamList } from '../navigation';
|
||||||
|
|
||||||
|
export function OnboardingScreen() {
|
||||||
|
const navigation =
|
||||||
|
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||||
|
const [step, setStep] = useState<1 | 2>(1);
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [gender, setGender] = useState<'male' | 'female' | null>(null);
|
||||||
|
|
||||||
|
const handleFinish = async () => {
|
||||||
|
await setSetting('user_name', name.trim());
|
||||||
|
await setSetting('user_gender', gender ?? 'male');
|
||||||
|
await setSetting('onboarded', '1');
|
||||||
|
navigation.replace('Main');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.safe}>
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
style={styles.flex}
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||||
|
>
|
||||||
|
<ScrollView contentContainerStyle={styles.content}>
|
||||||
|
{/* Indikator langkah */}
|
||||||
|
<View style={styles.steps}>
|
||||||
|
<View style={[styles.stepDot, step === 1 && styles.stepDotActive]} />
|
||||||
|
<View style={[styles.stepDot, step === 2 && styles.stepDotActive]} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{step === 1 ? (
|
||||||
|
<>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[colors.primary, colors.primaryDark]}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
style={styles.hero}
|
||||||
|
>
|
||||||
|
<View style={styles.ornCircleLarge} />
|
||||||
|
<View style={styles.ornCircleSmall} />
|
||||||
|
<Image
|
||||||
|
source={require('../../assets/wallet-3d-teal-logo.png')}
|
||||||
|
style={styles.logo}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
<Text style={styles.heroTitle}>Halo kak! 👋</Text>
|
||||||
|
<Text style={styles.heroSub}>
|
||||||
|
Selamat datang di Kantongku — asisten keuangan pribadimu.
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
<Text style={styles.question}>Panggilan Kakak siapa?</Text>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.card, styles.input]}
|
||||||
|
value={name}
|
||||||
|
onChangeText={setName}
|
||||||
|
placeholder="Ketik nama panggilan..."
|
||||||
|
placeholderTextColor={colors.textMuted}
|
||||||
|
autoFocus
|
||||||
|
returnKeyType="done"
|
||||||
|
onSubmitEditing={() => name.trim() && setStep(2)}
|
||||||
|
/>
|
||||||
|
<Pressable
|
||||||
|
style={({ pressed }) => [
|
||||||
|
styles.nextBtn,
|
||||||
|
!name.trim() && styles.nextBtnDisabled,
|
||||||
|
pressed && styles.pressed,
|
||||||
|
]}
|
||||||
|
disabled={!name.trim()}
|
||||||
|
onPress={() => setStep(2)}
|
||||||
|
>
|
||||||
|
<Text style={styles.nextBtnText}>Lanjut</Text>
|
||||||
|
<Ionicons name="arrow-forward" size={18} color={colors.white} />
|
||||||
|
</Pressable>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[colors.primary, colors.primaryDark]}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
style={styles.hero}
|
||||||
|
>
|
||||||
|
<View style={styles.ornCircleLarge} />
|
||||||
|
<View style={styles.ornCircleSmall} />
|
||||||
|
<Text style={styles.heroTitle2}>
|
||||||
|
Kak {name.trim()}, Cowok atau Cewek nih? 🤔
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.heroSub}>
|
||||||
|
Pilih avatar yang paling mirip kamu — biar Kantongku makin
|
||||||
|
personal.
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
<View style={styles.genderRow}>
|
||||||
|
<Pressable
|
||||||
|
style={[
|
||||||
|
styles.genderCard,
|
||||||
|
gender === 'male' && styles.genderCardActive,
|
||||||
|
]}
|
||||||
|
onPress={() => setGender('male')}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
source={require('../../assets/avatar-male.png')}
|
||||||
|
style={styles.avatar}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
<Text style={styles.genderLabel}>Cowok</Text>
|
||||||
|
{gender === 'male' && (
|
||||||
|
<View style={styles.checkBadge}>
|
||||||
|
<Ionicons name="checkmark" size={14} color={colors.white} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
style={[
|
||||||
|
styles.genderCard,
|
||||||
|
gender === 'female' && styles.genderCardActive,
|
||||||
|
]}
|
||||||
|
onPress={() => setGender('female')}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
source={require('../../assets/avatar-female.png')}
|
||||||
|
style={styles.avatar}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
<Text style={styles.genderLabel}>Cewek</Text>
|
||||||
|
{gender === 'female' && (
|
||||||
|
<View style={styles.checkBadge}>
|
||||||
|
<Ionicons name="checkmark" size={14} color={colors.white} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
style={({ pressed }) => [
|
||||||
|
styles.nextBtn,
|
||||||
|
!gender && styles.nextBtnDisabled,
|
||||||
|
pressed && styles.pressed,
|
||||||
|
]}
|
||||||
|
disabled={!gender}
|
||||||
|
onPress={handleFinish}
|
||||||
|
>
|
||||||
|
<Text style={styles.nextBtnText}>🚀 Mulai Pakai Kantongku</Text>
|
||||||
|
</Pressable>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
safe: { flex: 1, backgroundColor: colors.background },
|
||||||
|
flex: { flex: 1 },
|
||||||
|
content: { padding: 16, paddingBottom: 32, flexGrow: 1 },
|
||||||
|
steps: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
marginVertical: 16,
|
||||||
|
},
|
||||||
|
stepDot: {
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: colors.border,
|
||||||
|
},
|
||||||
|
stepDotActive: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
width: 24,
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 24,
|
||||||
|
overflow: 'hidden',
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: colors.primaryDark,
|
||||||
|
shadowOpacity: 0.25,
|
||||||
|
shadowRadius: 12,
|
||||||
|
shadowOffset: { width: 0, height: 6 },
|
||||||
|
elevation: 6,
|
||||||
|
},
|
||||||
|
ornCircleLarge: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: -40,
|
||||||
|
top: -50,
|
||||||
|
width: 160,
|
||||||
|
height: 160,
|
||||||
|
borderRadius: 80,
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.07)',
|
||||||
|
},
|
||||||
|
ornCircleSmall: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: -30,
|
||||||
|
bottom: -40,
|
||||||
|
width: 110,
|
||||||
|
height: 110,
|
||||||
|
borderRadius: 55,
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.06)',
|
||||||
|
},
|
||||||
|
logo: { width: 76, height: 76, marginBottom: 10 },
|
||||||
|
heroTitle: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: colors.white,
|
||||||
|
},
|
||||||
|
heroTitle2: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: colors.white,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 27,
|
||||||
|
},
|
||||||
|
heroSub: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: 'rgba(255,255,255,0.9)',
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 19,
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
question: {
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: colors.text,
|
||||||
|
marginTop: 24,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
paddingVertical: 14,
|
||||||
|
fontSize: 16,
|
||||||
|
color: colors.text,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
nextBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 6,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderRadius: 14,
|
||||||
|
paddingVertical: 15,
|
||||||
|
marginTop: 8,
|
||||||
|
shadowColor: colors.primaryDark,
|
||||||
|
shadowOpacity: 0.25,
|
||||||
|
shadowRadius: 8,
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
nextBtnDisabled: { opacity: 0.4, elevation: 0, shadowOpacity: 0 },
|
||||||
|
nextBtnText: { color: colors.white, fontWeight: '800', fontSize: 16 },
|
||||||
|
genderRow: { flexDirection: 'row', gap: 12, marginTop: 24, marginBottom: 20 },
|
||||||
|
genderCard: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: colors.border,
|
||||||
|
borderRadius: 20,
|
||||||
|
paddingVertical: 20,
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
genderCardActive: {
|
||||||
|
borderColor: colors.primary,
|
||||||
|
backgroundColor: colors.primarySoft,
|
||||||
|
},
|
||||||
|
avatar: { width: 110, height: 130 },
|
||||||
|
genderLabel: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: colors.text,
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
checkBadge: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
borderRadius: 11,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
pressed: { opacity: 0.85 },
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user