aboutsummaryrefslogtreecommitdiff
path: root/src/app/(app)
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/(app)')
-rw-r--r--src/app/(app)/(tabs)/_layout.tsx169
-rw-r--r--src/app/(app)/(tabs)/expenses.tsx2
-rw-r--r--src/app/(app)/(tabs)/index.tsx2
-rw-r--r--src/app/(app)/(tabs)/recurring.tsx2
-rw-r--r--src/app/(app)/(tabs)/salary.tsx2
-rw-r--r--src/app/(app)/_layout.tsx21
6 files changed, 198 insertions, 0 deletions
diff --git a/src/app/(app)/(tabs)/_layout.tsx b/src/app/(app)/(tabs)/_layout.tsx
new file mode 100644
index 0000000..5033232
--- /dev/null
+++ b/src/app/(app)/(tabs)/_layout.tsx
@@ -0,0 +1,169 @@
+import React from 'react';
+import { Platform, StyleSheet, TouchableOpacity, View } from 'react-native';
+import { Tabs } from 'expo-router';
+import { BlurView } from 'expo-blur';
+import { Feather } from '@expo/vector-icons';
+import Animated, {
+ useAnimatedStyle,
+ withSpring,
+ withTiming,
+} from 'react-native-reanimated';
+import * as Haptics from 'expo-haptics';
+import { Colors, Spacing } from '@/constants/theme';
+
+function TabBarIcon({
+ name,
+ isFocused,
+}: {
+ name: keyof typeof Feather.glyphMap;
+ isFocused: boolean;
+}) {
+ const animatedIconStyle = useAnimatedStyle(() => {
+ return {
+ transform: [
+ { scale: withSpring(isFocused ? 1.15 : 1, { damping: 12, stiffness: 300 }) },
+ ],
+ };
+ });
+
+ const animatedIndicatorStyle = useAnimatedStyle(() => {
+ return {
+ opacity: withTiming(isFocused ? 1 : 0, { duration: 200 }),
+ transform: [
+ { scale: withSpring(isFocused ? 1 : 0.5, { damping: 15, stiffness: 300 }) },
+ ],
+ };
+ });
+
+ return (
+ <View style={styles.iconContainer}>
+ <Animated.View style={animatedIconStyle}>
+ <Feather
+ name={name}
+ size={22}
+ color={isFocused ? Colors.primaryLight : Colors.textMuted}
+ />
+ </Animated.View>
+ <Animated.View style={[styles.indicator, animatedIndicatorStyle]} />
+ </View>
+ );
+}
+
+function CustomTabBar({ state, descriptors, navigation }: any) {
+ return (
+ <View style={styles.tabBarContainer}>
+ <BlurView
+ intensity={Platform.OS === 'ios' ? 80 : 100}
+ tint="dark"
+ style={styles.blurView}
+ >
+ {state.routes.map((route: any, index: number) => {
+ const { options } = descriptors[route.key];
+ const isFocused = state.index === index;
+
+ const onPress = () => {
+ const event = navigation.emit({
+ type: 'tabPress',
+ target: route.key,
+ canPreventDefault: true,
+ });
+
+ if (!isFocused && !event.defaultPrevented) {
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
+ navigation.navigate(route.name);
+ }
+ };
+
+ let iconName: keyof typeof Feather.glyphMap = 'home';
+ if (route.name === 'index') iconName = 'pie-chart';
+ else if (route.name === 'salary') iconName = 'dollar-sign';
+ else if (route.name === 'recurring') iconName = 'repeat';
+ else if (route.name === 'expenses') iconName = 'plus-square';
+
+ return (
+ <TouchableOpacity
+ key={route.key}
+ accessibilityRole="button"
+ accessibilityState={isFocused ? { selected: true } : {}}
+ accessibilityLabel={options.tabBarAccessibilityLabel}
+ onPress={onPress}
+ style={styles.tabItem}
+ activeOpacity={1}
+ >
+ <TabBarIcon name={iconName} isFocused={isFocused} />
+ </TouchableOpacity>
+ );
+ })}
+ </BlurView>
+ </View>
+ );
+}
+
+export default function TabsLayout() {
+ return (
+ <Tabs
+ tabBar={(props) => <CustomTabBar {...props} />}
+ screenOptions={{
+ headerShown: false,
+ }}
+ >
+ <Tabs.Screen name="index" options={{ title: 'Dashboard' }} />
+ <Tabs.Screen name="salary" options={{ title: 'Salário' }} />
+ <Tabs.Screen name="recurring" options={{ title: 'Gastos Fixos' }} />
+ <Tabs.Screen name="expenses" options={{ title: 'Gastos do Mês' }} />
+ </Tabs>
+ );
+}
+
+const styles = StyleSheet.create({
+ tabBarContainer: {
+ position: 'absolute',
+ bottom: Platform.OS === 'ios' ? 30 : 20,
+ left: 24,
+ right: 24,
+ borderRadius: 32,
+ ...Platform.select({
+ ios: {
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 8 },
+ shadowOpacity: 0.5,
+ shadowRadius: 16,
+ },
+ android: {
+ elevation: 10,
+ },
+ }),
+ },
+ blurView: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ borderRadius: 32,
+ backgroundColor: 'rgba(22, 27, 34, 0.75)',
+ borderWidth: 1,
+ borderColor: 'rgba(255, 255, 255, 0.08)',
+ overflow: 'hidden',
+ height: 64,
+ paddingHorizontal: Spacing.sm,
+ },
+ tabItem: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ },
+ iconContainer: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: 44,
+ height: 44,
+ },
+ indicator: {
+ position: 'absolute',
+ bottom: -2,
+ width: 4,
+ height: 4,
+ borderRadius: 2,
+ backgroundColor: Colors.primaryLight,
+ },
+});
diff --git a/src/app/(app)/(tabs)/expenses.tsx b/src/app/(app)/(tabs)/expenses.tsx
new file mode 100644
index 0000000..c313dc6
--- /dev/null
+++ b/src/app/(app)/(tabs)/expenses.tsx
@@ -0,0 +1,2 @@
+import OneTimeExpensesScreen from '@/screens/one-time-expenses-screen';
+export default OneTimeExpensesScreen;
diff --git a/src/app/(app)/(tabs)/index.tsx b/src/app/(app)/(tabs)/index.tsx
new file mode 100644
index 0000000..73bede0
--- /dev/null
+++ b/src/app/(app)/(tabs)/index.tsx
@@ -0,0 +1,2 @@
+import DashboardScreen from '@/screens/dashboard-screen';
+export default DashboardScreen;
diff --git a/src/app/(app)/(tabs)/recurring.tsx b/src/app/(app)/(tabs)/recurring.tsx
new file mode 100644
index 0000000..c5340ed
--- /dev/null
+++ b/src/app/(app)/(tabs)/recurring.tsx
@@ -0,0 +1,2 @@
+import RecurringExpensesScreen from '@/screens/recurring-expenses-screen';
+export default RecurringExpensesScreen;
diff --git a/src/app/(app)/(tabs)/salary.tsx b/src/app/(app)/(tabs)/salary.tsx
new file mode 100644
index 0000000..1d30bb1
--- /dev/null
+++ b/src/app/(app)/(tabs)/salary.tsx
@@ -0,0 +1,2 @@
+import SalaryScreen from '@/screens/salary-screen';
+export default SalaryScreen;
diff --git a/src/app/(app)/_layout.tsx b/src/app/(app)/_layout.tsx
new file mode 100644
index 0000000..6099274
--- /dev/null
+++ b/src/app/(app)/_layout.tsx
@@ -0,0 +1,21 @@
+import { Colors } from "@/constants/theme";
+import { useAuth } from "@/contexts/AuthContext";
+import { Redirect, Slot } from "expo-router";
+import { ActivityIndicator, View } from "react-native";
+
+export default function AppLayout() {
+ const { user, isLoading } = useAuth();
+
+ if (isLoading) {
+ return (
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: Colors.background }}>
+ <ActivityIndicator size="large" color={Colors.primary} />
+ </View>
+ );
+ }
+
+ if (!user)
+ return <Redirect href="/login" />;
+
+ return <Slot />;
+} \ No newline at end of file