From 869f21ee2efaefed6a5aa4fbd417c25df8dec02a Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sun, 18 Jan 2026 17:44:50 +0000
Subject: Add React Native mobile app for Makima (#3)

* [WIP] Heartbeat checkpoint - 2026-01-18 02:58:27 UTC

* feat(mobile): complete mobile app integration and verification

- Add ThemeColors type export to Colors.ts for type safety
- Export SUPABASE_URL from supabase.ts and use environment variables
- Update .env.example with correct default URLs
- Add comprehensive README.md with setup instructions

Verified:
- TypeScript compiles without errors
- App exports successfully for iOS and Android
- All screens accessible (login, dashboard, tasks, settings, task detail)
- Auth flow working with Zustand store and Supabase

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Task completion checkpoint

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
---
 apps/mobile/app/_layout.tsx | 111 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 111 insertions(+)
 create mode 100644 apps/mobile/app/_layout.tsx

(limited to 'apps/mobile/app/_layout.tsx')

diff --git a/apps/mobile/app/_layout.tsx b/apps/mobile/app/_layout.tsx
new file mode 100644
index 0000000..2c030b6
--- /dev/null
+++ b/apps/mobile/app/_layout.tsx
@@ -0,0 +1,111 @@
+import React, { useEffect } from 'react';
+import { Stack, useRouter, useSegments } from 'expo-router';
+import { StatusBar } from 'expo-status-bar';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { useColorScheme } from 'react-native';
+import { Colors } from '../constants/Colors';
+import { useAuthStore, setupAuthListener } from '../stores/authStore';
+import { LoadingScreen } from '../components/LoadingScreen';
+
+// Create a client
+const queryClient = new QueryClient({
+  defaultOptions: {
+    queries: {
+      staleTime: 5000, // Data is fresh for 5 seconds
+      retry: 2, // Retry failed requests twice
+      refetchOnWindowFocus: false, // Don't refetch on app focus (mobile)
+    },
+    mutations: {
+      retry: 1,
+    },
+  },
+});
+
+/**
+ * Auth state handler component
+ * Redirects users based on authentication status
+ */
+function AuthStateHandler({ children }: { children: React.ReactNode }) {
+  const router = useRouter();
+  const segments = useSegments();
+  const session = useAuthStore((state) => state.session);
+  const isInitialized = useAuthStore((state) => state.isInitialized);
+  const initialize = useAuthStore((state) => state.initialize);
+
+  // Initialize auth state on mount
+  useEffect(() => {
+    initialize();
+  }, [initialize]);
+
+  // Setup auth listener on mount
+  useEffect(() => {
+    const unsubscribe = setupAuthListener();
+    return unsubscribe;
+  }, []);
+
+  // Handle auth-based routing
+  useEffect(() => {
+    if (!isInitialized) return;
+
+    const inAuthGroup = segments[0] === '(auth)';
+    const isAuthenticated = session !== null;
+
+    if (!isAuthenticated && !inAuthGroup) {
+      // Redirect to login if not authenticated and not already on auth screens
+      router.replace('/(auth)/login');
+    } else if (isAuthenticated && inAuthGroup) {
+      // Redirect to main app if authenticated and on auth screens
+      router.replace('/(tabs)');
+    }
+  }, [session, segments, isInitialized, router]);
+
+  // Show loading screen while initializing
+  if (!isInitialized) {
+    return <LoadingScreen message="Checking authentication..." />;
+  }
+
+  return <>{children}</>;
+}
+
+export default function RootLayout() {
+  const colorScheme = useColorScheme() ?? 'light';
+  const colors = Colors[colorScheme];
+
+  return (
+    <QueryClientProvider client={queryClient}>
+      <StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
+      <AuthStateHandler>
+        <Stack
+          screenOptions={{
+            headerStyle: {
+              backgroundColor: colors.background,
+            },
+            headerTintColor: colors.text,
+            headerTitleStyle: {
+              fontWeight: '600',
+            },
+            contentStyle: {
+              backgroundColor: colors.background,
+            },
+          }}
+        >
+          <Stack.Screen
+            name="(auth)"
+            options={{ headerShown: false }}
+          />
+          <Stack.Screen
+            name="(tabs)"
+            options={{ headerShown: false }}
+          />
+          <Stack.Screen
+            name="task/[id]"
+            options={{
+              presentation: 'card',
+              headerBackTitle: 'Back',
+            }}
+          />
+        </Stack>
+      </AuthStateHandler>
+    </QueryClientProvider>
+  );
+}
-- 
cgit v1.2.3

