diff --git a/.Jules/changelog.md b/.Jules/changelog.md
index 11fc864..5db98de 100644
--- a/.Jules/changelog.md
+++ b/.Jules/changelog.md
@@ -7,6 +7,14 @@
## [Unreleased]
### Added
+- **Mobile Skeleton Loading:** Implemented skeleton loading state for HomeScreen.
+ - **Features:**
+ - Replaced `ActivityIndicator` with `GroupListSkeleton` component.
+ - Mimics exact layout of group cards (Avatar, Title, Status).
+ - Uses pulsing animation for smooth transition.
+ - Uses theme colors (`surfaceVariant`) for dark/light mode support.
+ - **Technical:** Created `mobile/components/ui/Skeleton.js`, `mobile/components/skeletons/GroupListSkeleton.js`. Updated `mobile/screens/HomeScreen.js`.
+
- **Password Strength Meter:** Added a visual password strength indicator to the signup form.
- **Features:**
- Real-time strength calculation (Length, Uppercase, Lowercase, Number, Symbol).
diff --git a/.Jules/todo.md b/.Jules/todo.md
index ebb0c7a..a104ff1 100644
--- a/.Jules/todo.md
+++ b/.Jules/todo.md
@@ -57,12 +57,10 @@
- Impact: Native feel, users can easily refresh data
- Size: ~150 lines
-- [ ] **[ux]** Complete skeleton loading for HomeScreen groups
- - File: `mobile/screens/HomeScreen.js`
- - Context: Replace ActivityIndicator with skeleton group cards
+- [x] **[ux]** Complete skeleton loading for HomeScreen groups
+ - Completed: 2026-02-14
+ - Files modified: `mobile/screens/HomeScreen.js`, `mobile/components/skeletons/GroupListSkeleton.js`, `mobile/components/ui/Skeleton.js`
- Impact: Better loading experience, less jarring
- - Size: ~40 lines
- - Added: 2026-01-01
- [x] **[a11y]** Complete accessibility labels for all screens
- Completed: 2026-01-29
@@ -168,3 +166,7 @@
- Completed: 2026-02-08
- Files modified: `web/components/ui/PasswordStrength.tsx`, `web/pages/Auth.tsx`
- Impact: Provides visual feedback on password complexity during signup
+- [x] **[ux]** Complete skeleton loading for HomeScreen groups
+ - Completed: 2026-02-14
+ - Files modified: `mobile/screens/HomeScreen.js`, `mobile/components/skeletons/GroupListSkeleton.js`, `mobile/components/ui/Skeleton.js`
+ - Impact: Better loading experience, less jarring
diff --git a/mobile/components/skeletons/GroupListSkeleton.js b/mobile/components/skeletons/GroupListSkeleton.js
new file mode 100644
index 0000000..08af1f7
--- /dev/null
+++ b/mobile/components/skeletons/GroupListSkeleton.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import { View, StyleSheet, FlatList } from 'react-native';
+import { Card } from 'react-native-paper';
+import Skeleton from '../ui/Skeleton';
+
+const GroupListSkeleton = () => {
+ // Create 6 dummy items to fill the screen
+ const dummyData = Array(6).fill(null);
+
+ const renderItem = () => (
+
+ {/* Header Area (Avatar + Title) */}
+
+
+
+
+
+
+
+ {/* Content Area (Status Text) */}
+
+
+
+
+ );
+
+ return (
+
+ index.toString()}
+ contentContainerStyle={styles.list}
+ scrollEnabled={false}
+ />
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ list: {
+ padding: 16,
+ },
+ card: {
+ marginBottom: 16,
+ // Card has default elevation and background color from theme
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 16, // Match Card.Title padding
+ },
+ titleContainer: {
+ marginLeft: 16,
+ flex: 1,
+ },
+ content: {
+ paddingHorizontal: 16,
+ paddingBottom: 16, // Match Card.Content padding
+ },
+});
+
+export default GroupListSkeleton;
diff --git a/mobile/components/ui/Skeleton.js b/mobile/components/ui/Skeleton.js
new file mode 100644
index 0000000..95bef85
--- /dev/null
+++ b/mobile/components/ui/Skeleton.js
@@ -0,0 +1,48 @@
+import React, { useEffect, useRef } from 'react';
+import { Animated, StyleSheet } from 'react-native';
+import { useTheme } from 'react-native-paper';
+
+const Skeleton = ({ width, height, borderRadius, style }) => {
+ const theme = useTheme();
+ const opacity = useRef(new Animated.Value(0.3)).current;
+
+ useEffect(() => {
+ const animation = Animated.loop(
+ Animated.sequence([
+ Animated.timing(opacity, {
+ toValue: 0.7, // Don't go to fully opaque so it looks like a placeholder
+ duration: 800,
+ useNativeDriver: true,
+ }),
+ Animated.timing(opacity, {
+ toValue: 0.3,
+ duration: 800,
+ useNativeDriver: true,
+ }),
+ ])
+ );
+
+ animation.start();
+
+ return () => animation.stop();
+ }, [opacity]);
+
+ return (
+
+ );
+};
+
+export default Skeleton;
diff --git a/mobile/screens/HomeScreen.js b/mobile/screens/HomeScreen.js
index d2f3c38..3cbc2b9 100644
--- a/mobile/screens/HomeScreen.js
+++ b/mobile/screens/HomeScreen.js
@@ -1,7 +1,6 @@
import { useContext, useEffect, useState } from "react";
import { Alert, FlatList, RefreshControl, StyleSheet, View } from "react-native";
import {
- ActivityIndicator,
Appbar,
Avatar,
Modal,
@@ -13,6 +12,7 @@ import {
import HapticButton from '../components/ui/HapticButton';
import HapticCard from '../components/ui/HapticCard';
import { HapticAppbarAction } from '../components/ui/HapticAppbar';
+import GroupListSkeleton from '../components/skeletons/GroupListSkeleton';
import * as Haptics from "expo-haptics";
import { createGroup, getGroups, getOptimizedSettlements } from "../api/groups";
import { AuthContext } from "../context/AuthContext";
@@ -257,9 +257,7 @@ const HomeScreen = ({ navigation }) => {
{isLoading ? (
-
-
-
+
) : (