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/components/TaskListSkeleton.tsx | 119 ++++++++++++++++++++++++++++
 1 file changed, 119 insertions(+)
 create mode 100644 apps/mobile/components/TaskListSkeleton.tsx

(limited to 'apps/mobile/components/TaskListSkeleton.tsx')

diff --git a/apps/mobile/components/TaskListSkeleton.tsx b/apps/mobile/components/TaskListSkeleton.tsx
new file mode 100644
index 0000000..60e747d
--- /dev/null
+++ b/apps/mobile/components/TaskListSkeleton.tsx
@@ -0,0 +1,119 @@
+import React, { useEffect, useRef } from 'react';
+import {
+  View,
+  StyleSheet,
+  Animated,
+  useColorScheme,
+} from 'react-native';
+import { Colors } from '../constants/Colors';
+
+interface SkeletonRowProps {
+  delay?: number;
+}
+
+function SkeletonRow({ delay = 0 }: SkeletonRowProps) {
+  const colorScheme = useColorScheme() ?? 'light';
+  const colors = Colors[colorScheme];
+  const opacity = useRef(new Animated.Value(0.3)).current;
+
+  useEffect(() => {
+    const animation = Animated.loop(
+      Animated.sequence([
+        Animated.timing(opacity, {
+          toValue: 0.7,
+          duration: 800,
+          useNativeDriver: true,
+          delay,
+        }),
+        Animated.timing(opacity, {
+          toValue: 0.3,
+          duration: 800,
+          useNativeDriver: true,
+        }),
+      ])
+    );
+    animation.start();
+    return () => animation.stop();
+  }, [opacity, delay]);
+
+  const bgColor = colorScheme === 'dark' ? '#374151' : '#e5e7eb';
+
+  return (
+    <View
+      style={[
+        styles.row,
+        {
+          backgroundColor: colors.card,
+          borderColor: colors.border,
+        },
+      ]}
+    >
+      <Animated.View
+        style={[
+          styles.dot,
+          { backgroundColor: bgColor, opacity },
+        ]}
+      />
+      <View style={styles.content}>
+        <Animated.View
+          style={[
+            styles.titleBar,
+            { backgroundColor: bgColor, opacity },
+          ]}
+        />
+        <Animated.View
+          style={[
+            styles.subtitleBar,
+            { backgroundColor: bgColor, opacity },
+          ]}
+        />
+      </View>
+    </View>
+  );
+}
+
+export function TaskListSkeleton() {
+  return (
+    <View style={styles.container}>
+      <SkeletonRow delay={0} />
+      <SkeletonRow delay={100} />
+      <SkeletonRow delay={200} />
+      <SkeletonRow delay={300} />
+      <SkeletonRow delay={400} />
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+  },
+  row: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 16,
+    paddingHorizontal: 16,
+    borderBottomWidth: StyleSheet.hairlineWidth,
+    minHeight: 64,
+  },
+  dot: {
+    width: 10,
+    height: 10,
+    borderRadius: 5,
+    marginRight: 12,
+  },
+  content: {
+    flex: 1,
+    gap: 8,
+  },
+  titleBar: {
+    height: 16,
+    width: '70%',
+    borderRadius: 4,
+  },
+  subtitleBar: {
+    height: 12,
+    width: '40%',
+    borderRadius: 4,
+  },
+});
-- 
cgit v1.2.3

