240 lines
6.8 KiB
TypeScript
240 lines
6.8 KiB
TypeScript
import { Ionicons } from '@expo/vector-icons';
|
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
import { Pressable, StyleSheet, View } from 'react-native';
|
|
import { colors } from '../theme/colors';
|
|
import { DashboardScreen } from '../screens/DashboardScreen';
|
|
import { HistoryScreen } from '../screens/HistoryScreen';
|
|
import { AddTransactionScreen } from '../screens/AddTransactionScreen';
|
|
import { EditTransactionScreen } from '../screens/EditTransactionScreen';
|
|
import { RecurringFormScreen } from '../screens/RecurringFormScreen';
|
|
import { TargetFormScreen } from '../screens/TargetFormScreen';
|
|
import { CategoryFormScreen } from '../screens/CategoryFormScreen';
|
|
import { OnboardingScreen } from '../screens/OnboardingScreen';
|
|
import { SavingsScreen } from '../screens/SavingsScreen';
|
|
import { SettingsScreen } from '../screens/SettingsScreen';
|
|
import type { TransactionType } from '../types';
|
|
|
|
export type RootTabParamList = {
|
|
Beranda: undefined;
|
|
Riwayat: undefined;
|
|
Catat: { type?: TransactionType } | undefined;
|
|
Tabungan: undefined;
|
|
Pengaturan: undefined;
|
|
};
|
|
|
|
export type RootStackParamList = {
|
|
Main: undefined;
|
|
Onboarding: undefined;
|
|
EditTransaksi: { transactionId: number };
|
|
RecurringForm: undefined;
|
|
TargetForm: undefined;
|
|
CategoryForm:
|
|
| {
|
|
categoryId?: number;
|
|
initialName?: string;
|
|
initialIcon?: string;
|
|
defaultType?: TransactionType;
|
|
}
|
|
| undefined;
|
|
};
|
|
|
|
const Tab = createBottomTabNavigator<RootTabParamList>();
|
|
const Stack = createNativeStackNavigator<RootStackParamList>();
|
|
|
|
type IoniconName = React.ComponentProps<typeof Ionicons>['name'];
|
|
|
|
const tabIcon =
|
|
(focusedName: IoniconName, unfocusedName: IoniconName) =>
|
|
({ color, size, focused }: { color: string; size: number; focused: boolean }) =>
|
|
(
|
|
<Ionicons
|
|
name={focused ? focusedName : unfocusedName}
|
|
size={size}
|
|
color={color}
|
|
/>
|
|
);
|
|
|
|
/** Tombol "+" melingkar di tengah tab bar (FAB) */
|
|
function FabButton({
|
|
onPress,
|
|
accessibilityState,
|
|
}: {
|
|
onPress?: (...args: any[]) => void;
|
|
accessibilityState?: { selected?: boolean };
|
|
}) {
|
|
const focused = accessibilityState?.selected;
|
|
return (
|
|
<Pressable
|
|
onPress={onPress}
|
|
style={({ pressed }) => [styles.fabWrapper, pressed && styles.fabPressed]}
|
|
hitSlop={8}
|
|
>
|
|
<View style={[styles.fab, focused && styles.fabActive]}>
|
|
<Ionicons name="add" size={32} color={colors.white} />
|
|
</View>
|
|
</Pressable>
|
|
);
|
|
}
|
|
|
|
function MainTabs() {
|
|
return (
|
|
<Tab.Navigator
|
|
screenOptions={{
|
|
tabBarActiveTintColor: colors.primary,
|
|
tabBarInactiveTintColor: colors.textMuted,
|
|
tabBarLabelStyle: styles.tabLabel,
|
|
tabBarStyle: styles.tabBar,
|
|
headerTitleStyle: { fontWeight: '700', color: colors.text },
|
|
headerStyle: { backgroundColor: colors.background },
|
|
headerShadowVisible: false,
|
|
}}
|
|
>
|
|
<Tab.Screen
|
|
name="Beranda"
|
|
component={DashboardScreen}
|
|
options={{
|
|
headerShown: false,
|
|
tabBarIcon: tabIcon('home', 'home-outline'),
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Riwayat"
|
|
component={HistoryScreen}
|
|
options={{
|
|
title: 'Riwayat',
|
|
tabBarIcon: tabIcon('list', 'list-outline'),
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Catat"
|
|
component={AddTransactionScreen}
|
|
options={{
|
|
title: 'Catat',
|
|
tabBarIcon: () => null,
|
|
tabBarLabel: () => null,
|
|
tabBarButton: (props) => <FabButton {...props} />,
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Tabungan"
|
|
component={SavingsScreen}
|
|
options={{
|
|
tabBarIcon: tabIcon('wallet', 'wallet-outline'),
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Pengaturan"
|
|
component={SettingsScreen}
|
|
options={{
|
|
tabBarIcon: tabIcon('settings', 'settings-outline'),
|
|
}}
|
|
/>
|
|
</Tab.Navigator>
|
|
);
|
|
}
|
|
|
|
export function RootNavigator({
|
|
initialRoute = 'Main',
|
|
}: {
|
|
initialRoute?: 'Main' | 'Onboarding';
|
|
}) {
|
|
return (
|
|
<Stack.Navigator
|
|
initialRouteName={initialRoute}
|
|
screenOptions={{ headerShown: false }}
|
|
>
|
|
<Stack.Screen name="Main" component={MainTabs} />
|
|
<Stack.Screen name="Onboarding" component={OnboardingScreen} />
|
|
<Stack.Screen
|
|
name="EditTransaksi"
|
|
component={EditTransactionScreen}
|
|
options={{
|
|
presentation: 'modal',
|
|
headerShown: true,
|
|
title: 'Edit Transaksi',
|
|
headerTitleStyle: { fontWeight: '700', color: colors.text },
|
|
headerStyle: { backgroundColor: colors.background },
|
|
headerShadowVisible: false,
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="RecurringForm"
|
|
component={RecurringFormScreen}
|
|
options={{
|
|
presentation: 'modal',
|
|
headerShown: true,
|
|
title: 'Transaksi Berulang',
|
|
headerTitleStyle: { fontWeight: '700', color: colors.text },
|
|
headerStyle: { backgroundColor: colors.background },
|
|
headerShadowVisible: false,
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="TargetForm"
|
|
component={TargetFormScreen}
|
|
options={{
|
|
presentation: 'modal',
|
|
headerShown: true,
|
|
title: 'Target Tabungan',
|
|
headerTitleStyle: { fontWeight: '700', color: colors.text },
|
|
headerStyle: { backgroundColor: colors.background },
|
|
headerShadowVisible: false,
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="CategoryForm"
|
|
component={CategoryFormScreen}
|
|
options={({ route }) => ({
|
|
presentation: 'modal',
|
|
headerShown: true,
|
|
title: route.params?.categoryId != null ? 'Edit Kategori' : 'Kategori Baru',
|
|
headerTitleStyle: { fontWeight: '700', color: colors.text },
|
|
headerStyle: { backgroundColor: colors.background },
|
|
headerShadowVisible: false,
|
|
})}
|
|
/>
|
|
</Stack.Navigator>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
tabBar: {
|
|
backgroundColor: colors.surface,
|
|
borderTopColor: colors.border,
|
|
borderTopWidth: 1,
|
|
height: 62,
|
|
paddingTop: 4,
|
|
paddingBottom: 6,
|
|
},
|
|
tabLabel: {
|
|
fontSize: 10,
|
|
fontWeight: '600',
|
|
},
|
|
fabWrapper: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
top: -24,
|
|
},
|
|
fabPressed: { opacity: 0.85 },
|
|
fab: {
|
|
width: 58,
|
|
height: 58,
|
|
borderRadius: 29,
|
|
backgroundColor: colors.primary,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderWidth: 4,
|
|
borderColor: colors.surface,
|
|
shadowColor: colors.primaryDark,
|
|
shadowOpacity: 0.35,
|
|
shadowRadius: 10,
|
|
shadowOffset: { width: 0, height: 5 },
|
|
elevation: 8,
|
|
},
|
|
fabActive: {
|
|
backgroundColor: colors.primaryDark,
|
|
},
|
|
});
|