diff --git a/.gitea/workflows/build-apk.yml b/.gitea/workflows/build-apk.yml
index 96e51e8..6aa972b 100644
--- a/.gitea/workflows/build-apk.yml
+++ b/.gitea/workflows/build-apk.yml
@@ -44,20 +44,8 @@ jobs:
working-directory: ./apk/android
run: |
./gradlew assembleRelease
- - name: 📦 Zip APK
- working-directory: .
- run: |
- mkdir -p dist
- cp apk/android/app/build/outputs/apk/release/app-release.apk dist/
- zip -j dist/build.zip dist/app-release.apk
-
- - name: Create Release
- uses: https://gitea.com/actions/gitea-release-action@v1
- working-directory: dist
+ - name: 📤 Upload APK Artifact
+ uses: actions/upload-artifact@v3
with:
- tag_name: latest
- name: Latest Build
- files: |
- - build.zip
- env:
- GITEA_TOKEN: ${{ secrets.GITEA }}
\ No newline at end of file
+ name: app-release-apk
+ path: apk/android/app/build/outputs/apk/release/app-release.apk
\ No newline at end of file
diff --git a/apk/app/(auth)/login.tsx b/apk/app/(auth)/login.tsx
index 2b44310..589c9b0 100644
--- a/apk/app/(auth)/login.tsx
+++ b/apk/app/(auth)/login.tsx
@@ -1,6 +1,7 @@
import React, { useState } from "react";
import { View, Text, TextInput, TouchableOpacity, StyleSheet } from "react-native";
import { useAuth } from "@/context/auth-context";
+import { router } from "expo-router";
export default function AuthScreen() {
const [isLogin, setIsLogin] = useState(true);
@@ -17,9 +18,12 @@ function LoginScreen({ onSwitch }) {
const [password, setPassword] = useState("");
const { login } = useAuth();
- const handleLogin = () => {
- console.log("Login ->", { email, password });
- login(email, password);
+ const handleLogin = async () => {
+ const loginResult = await login(email, password);
+ if (loginResult) {
+ console.log("Login successful");
+ router.replace("/(tabs)");
+ }
};
return (
diff --git a/apk/app/(tabs)/_layout.tsx b/apk/app/(tabs)/_layout.tsx
index 3d54133..f0bd0af 100644
--- a/apk/app/(tabs)/_layout.tsx
+++ b/apk/app/(tabs)/_layout.tsx
@@ -1,5 +1,25 @@
import { Tabs } from "expo-router";
+import { Ionicons } from "@expo/vector-icons";
export default function TabsLayout() {
- return ;
-}
+ return (
+
+ (
+
+ ),
+ }} />
+ (
+
+ ),
+ }} />
+
+ );
+}
\ No newline at end of file
diff --git a/apk/app/(tabs)/index.tsx b/apk/app/(tabs)/index.tsx
index 417f39e..5819aeb 100644
--- a/apk/app/(tabs)/index.tsx
+++ b/apk/app/(tabs)/index.tsx
@@ -14,7 +14,11 @@ export default function HomeScreen() {
Upcoming Birthdays
router.push("/add")}
+ onPress={() => {
+ router.push("/add");
+ }
+ }
+
style={styles.addButton}
>
+
diff --git a/apk/app/(tabs)/settings.tsx b/apk/app/(tabs)/settings.tsx
new file mode 100644
index 0000000..12f06be
--- /dev/null
+++ b/apk/app/(tabs)/settings.tsx
@@ -0,0 +1,148 @@
+import React, { useState } from "react";
+import { StyleSheet, Text, View, TouchableOpacity, Alert } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+
+import { useRouter } from "expo-router";
+import { useAuth } from "@/context/auth-context";
+
+export default function SettingsScreen() {
+ const router = useRouter()
+ const { logout } = useAuth();
+ const [theme, setTheme] = useState("light"); // light | dark | system
+
+ const handleLogout = () => {
+ Alert.alert("Logout", "Are you sure you want to logout?", [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Logout",
+ style: "destructive",
+ onPress: () => {
+ // TODO: clear auth state here
+ router.replace("/login");
+ logout();
+ },
+ },
+ ]);
+ };
+
+ const toggleTheme = (value) => {
+ setTheme(value);
+ // TODO: persist theme (AsyncStorage / context / zustand)
+ };
+
+ return (
+
+
+
+ Settings
+
+ {/* Theme Selector */}
+
+ Theme
+
+
+ toggleTheme("light")}
+ />
+ toggleTheme("dark")}
+ />
+ toggleTheme("system")}
+ />
+
+
+
+ {/* Logout */}
+
+ Account
+
+
+ Log Out
+
+
+
+
+
+ );
+}
+
+function ThemeButton({ label, active, onPress }) {
+ return (
+
+
+ {label}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ backgroundColor: "#fff",
+ },
+ content: {
+ flex: 1,
+ paddingHorizontal: 16,
+ paddingTop: 12,
+ },
+ title: {
+ fontSize: 24,
+ fontWeight: "bold",
+ marginBottom: 20,
+ },
+
+ section: {
+ marginBottom: 24,
+ },
+ sectionTitle: {
+ fontSize: 16,
+ fontWeight: "600",
+ marginBottom: 10,
+ },
+
+ row: {
+ flexDirection: "row",
+ gap: 10,
+ },
+
+ themeButton: {
+ paddingVertical: 8,
+ paddingHorizontal: 12,
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: "#ccc",
+ },
+ themeButtonActive: {
+ backgroundColor: "#111",
+ borderColor: "#111",
+ },
+ themeText: {
+ color: "#333",
+ paddingHorizontal: 5,
+ },
+ themeTextActive: {
+ color: "#fff",
+ },
+
+ logoutButton: {
+ padding: 14,
+ borderRadius: 10,
+ backgroundColor: "#ff3b30",
+ alignItems: "center",
+ },
+ logoutText: {
+ color: "#fff",
+ fontWeight: "600",
+ },
+});
\ No newline at end of file
diff --git a/apk/app/(tabs)/add.tsx b/apk/app/add.tsx
similarity index 100%
rename from apk/app/(tabs)/add.tsx
rename to apk/app/add.tsx
diff --git a/apk/components/views/loading-screen.tsx b/apk/components/views/loading-screen.tsx
new file mode 100644
index 0000000..3a134f9
--- /dev/null
+++ b/apk/components/views/loading-screen.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { View, Text, ActivityIndicator, StyleSheet } from "react-native";
+
+export default function LoadingScreen({ message = "Loading..." }) {
+ return (
+
+
+ {message}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ text: {
+ marginTop: 10,
+ fontSize: 16,
+ },
+});
\ No newline at end of file
diff --git a/apk/components/views/root-navigation.tsx b/apk/components/views/root-navigation.tsx
index 3ccd1a1..5951315 100644
--- a/apk/components/views/root-navigation.tsx
+++ b/apk/components/views/root-navigation.tsx
@@ -1,29 +1,32 @@
import { BirthdaysProvider } from "@/context/birthdays-context";
import { useAuth } from "@/context/auth-context";
import { Stack, router, useRootNavigationState, useSegments } from "expo-router";
-import { useEffect } from "react";
+import React, { useEffect } from "react";
+import LoadingScreen from "./loading-screen";
export default function RootLayout() {
const { user, isHydrated } = useAuth();
const navigationState = useRootNavigationState();
const segments = useSegments();
+ const [loaded, setLoaded] = React.useState(false);
useEffect(() => {
- if (!isHydrated || !navigationState?.key) {
- return;
- }
+ if (!isHydrated || !navigationState?.key) return;
- const inAuthGroup = segments[0] === "(auth)";
+ const inAuthGroup = segments.length === 0;
- if (!user && !inAuthGroup) {
- router.replace("/(auth)/login");
- return;
- }
+ if (!user) {
+ router.replace("/(auth)/login");
+ } else if (inAuthGroup) {
+ router.replace("/(tabs)");
+ }
- if (user && inAuthGroup) {
- router.replace("/(tabs)");
- }
- }, [isHydrated, navigationState?.key, segments, user]);
+ setLoaded(true); // 👈 ALWAYS run this
+}, [isHydrated, navigationState?.key, segments, user]);
+
+ if(!loaded) {
+ return (); // or a loading spinner
+ }
return (
diff --git a/apk/components/views/tabs.tsx b/apk/components/views/tabs.tsx
deleted file mode 100644
index efab7b9..0000000
--- a/apk/components/views/tabs.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import {
- DarkTheme,
- DefaultTheme,
- ThemeProvider,
-} from "@react-navigation/native";
-import { Stack } from "expo-router";
-import { StatusBar } from "expo-status-bar";
-import "react-native-reanimated";
-
-import { BirthdaysProvider } from "@/context/birthdays-context";
-import { useColorScheme } from "@/hooks/use-color-scheme";
-
-export const unstable_settings = {
- anchor: "(tabs)",
-};
-
-export default function RootLayout() {
- const colorScheme = useColorScheme();
-
- return (
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apk/context/auth-context.tsx b/apk/context/auth-context.tsx
index 565ed0c..a86e0bc 100644
--- a/apk/context/auth-context.tsx
+++ b/apk/context/auth-context.tsx
@@ -8,7 +8,7 @@ interface User {
interface AuthContextValue {
user: User | null;
isHydrated: boolean;
- login: (email: string, password: string) => Promise;
+ login: (email: string, password: string) => Promise;
logout: () => Promise;
}
@@ -41,6 +41,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(fakeUser));
setUser(fakeUser);
+ console.log(fakeUser)
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ return true;
}
const logout = async () => {