Files

114 lines
3.6 KiB
TypeScript

import { Component, useEffect, useState } from 'react';
import type { ReactNode } from 'react';
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 { NavigationContainer } from '@react-navigation/native';
import { initDatabase } from './src/db/database';
import { checkRecurringDue, getSetting } from './src/db/repositories';
import { scheduleDailyReminder } from './src/utils/notifications';
import { RootNavigator } from './src/navigation';
import { colors } from './src/theme/colors';
SplashScreen.preventAutoHideAsync().catch(() => {});
/** Tangkap error render agar tidak force-close: tampilkan layar aman. */
class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean; message: string }> {
state = { hasError: false, message: '' };
static getDerivedStateFromError(e: unknown) {
return { hasError: true, message: e instanceof Error ? e.message : String(e) };
}
render() {
if (this.state.hasError) {
return (
<View style={styles.center}>
<Text style={styles.errorTitle}>Terjadi kesalahan 😢</Text>
<Text style={styles.errorText}>{this.state.message}</Text>
</View>
);
}
return this.props.children;
}
}
export default function App() {
const [ready, setReady] = useState(false);
const [error, setError] = useState<string | null>(null);
const [initialRoute, setInitialRoute] = useState<'Main' | 'Onboarding'>('Onboarding');
useEffect(() => {
initDatabase()
.then(async () => {
try {
// Buat transaksi berulang yang jatuh tempo hari ini
await checkRecurringDue();
} catch {
// jangan sampai mengganggu startup
}
const onboarded = await getSetting('onboarded');
const userName = await getSetting('user_name');
setInitialRoute(onboarded === '1' && userName ? 'Main' : 'Onboarding');
// Reschedule pengingat harian kalau aktif
try {
if ((await getSetting('reminder_enabled')) === '1') {
const h = Number(await getSetting('reminder_hour'));
await scheduleDailyReminder(Number.isFinite(h) ? h : 20, 0);
}
} catch {
// abaikan
}
setReady(true);
SplashScreen.hideAsync().catch(() => {});
})
.catch((e) => setError(String(e)));
}, []);
if (error) {
return (
<View style={styles.center}>
<Text style={styles.errorTitle}>Terjadi kesalahan 😢</Text>
<Text style={styles.errorText}>{error}</Text>
</View>
);
}
if (!ready) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" color={colors.primary} />
<Text style={styles.loadingText}>Menyiapkan Kantongku...</Text>
</View>
);
}
return (
<ErrorBoundary>
<SafeAreaProvider>
<NavigationContainer>
<RootNavigator initialRoute={initialRoute} />
</NavigationContainer>
<StatusBar
barStyle="light-content"
translucent={false}
backgroundColor={colors.primary}
/>
</SafeAreaProvider>
</ErrorBoundary>
);
}
const styles = StyleSheet.create({
center: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.background,
padding: 24,
},
loadingText: { marginTop: 12, color: colors.textMuted, fontSize: 14 },
errorTitle: { fontSize: 16, fontWeight: '700', color: colors.text, marginBottom: 8 },
errorText: { color: colors.expense, fontSize: 13, textAlign: 'center' },
});