From 5a3094d1630c2793d0d1f561008bee18c793544b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:39:08 +0000 Subject: [PATCH] [jules] ux: Complete skeleton loading for HomeScreen groups Co-authored-by: Devasy <110348311+Devasy@users.noreply.github.com> --- .Jules/changelog.md | 7 ++ .Jules/knowledge.md | 19 +++++ .Jules/todo.md | 12 ++-- mobile/components/GroupSkeleton.js | 110 +++++++++++++++++++++++++++++ mobile/screens/HomeScreen.js | 6 +- 5 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 mobile/components/GroupSkeleton.js diff --git a/.Jules/changelog.md b/.Jules/changelog.md index f6a41244..95f0c792 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -29,6 +29,13 @@ - Toast notification system (`ToastContext`, `Toast` component) for providing non-blocking user feedback. - Keyboard navigation support for Groups page, enabling accessibility for power users. +- **Mobile App:** Replaced generic loading spinner on Home Screen with a custom `GroupSkeleton` component. + - **Features:** + - Mimics actual list layout (Avatar + Title + Subtitle). + - Pulsing opacity animation using `Animated` API. + - Adapts to theme colors using `react-native-paper`'s `useTheme` (`surfaceVariant`). + - **Technical:** Created `mobile/components/GroupSkeleton.js`, integrated into `mobile/screens/HomeScreen.js`. + ### Changed - **Web App:** Refactored `GroupDetails` destructive actions (Delete Group, Delete Expense, Leave Group, Remove Member) to use the new `ConfirmDialog` instead of `window.confirm`. - **Accessibility:** Updated `Modal` component to include proper ARIA roles and labels, fixing a long-standing accessibility gap. diff --git a/.Jules/knowledge.md b/.Jules/knowledge.md index 3361c5da..bc858985 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -299,6 +299,25 @@ Commonly used components: - `` and `` for overlays - `` for loading states +### Skeleton Loading Pattern (Mobile) + +**Date:** 2026-01-22 +**Context:** Creating loading states for lists + +Use `Animated` API combined with `react-native-paper` theme colors: + +```javascript +const theme = useTheme(); +const opacity = useRef(new Animated.Value(0.3)).current; +const color = theme.colors.surfaceVariant; // Adapt to theme + +Animated.loop(...).start(); + +return ( + +); +``` + ### Safe Area Pattern **Date:** 2026-01-01 diff --git a/.Jules/todo.md b/.Jules/todo.md index 3c53efd3..2a427769 100644 --- a/.Jules/todo.md +++ b/.Jules/todo.md @@ -57,12 +57,12 @@ - Size: ~45 lines - Added: 2026-01-01 -- [ ] **[ux]** Complete skeleton loading for HomeScreen groups - - File: `mobile/screens/HomeScreen.js` - - Context: Replace ActivityIndicator with skeleton group cards - - Impact: Better loading experience, less jarring - - Size: ~40 lines - - Added: 2026-01-01 +- [x] **[ux]** Complete skeleton loading for HomeScreen groups + - Completed: 2026-01-22 + - File: `mobile/screens/HomeScreen.js`, `mobile/components/GroupSkeleton.js` + - Context: Replace ActivityIndicator with skeleton group cards using Animated API and Theme colors + - Impact: Better loading experience, less jarring, theme aware + - Size: ~80 lines - [ ] **[a11y]** Complete accessibility labels for all screens - Files: All screens in `mobile/screens/` diff --git a/mobile/components/GroupSkeleton.js b/mobile/components/GroupSkeleton.js new file mode 100644 index 00000000..7c482d71 --- /dev/null +++ b/mobile/components/GroupSkeleton.js @@ -0,0 +1,110 @@ +import React, { useEffect, useRef } from "react"; +import { View, StyleSheet, Animated } from "react-native"; +import { Card, useTheme } from "react-native-paper"; + +const SkeletonItem = () => { + const theme = useTheme(); + const opacity = useRef(new Animated.Value(0.3)).current; + + // Use surfaceVariant for a neutral placeholder color that adapts to dark/light mode + const skeletonColor = theme.colors.surfaceVariant || "#E0E0E0"; + + useEffect(() => { + Animated.loop( + Animated.sequence([ + Animated.timing(opacity, { + toValue: 0.7, + duration: 800, + useNativeDriver: true, + }), + Animated.timing(opacity, { + toValue: 0.3, + duration: 800, + useNativeDriver: true, + }), + ]) + ).start(); + }, [opacity]); + + return ( + + + + {/* Avatar Skeleton */} + + + + {/* Title Skeleton */} + + {/* Subtitle/Status Skeleton */} + + + + + + ); +}; + +const GroupSkeleton = () => { + return ( + + {[1, 2, 3, 4, 5].map((key) => ( + + ))} + + ); +}; + +const styles = StyleSheet.create({ + container: { + padding: 16, + }, + card: { + marginBottom: 16, + }, + content: { + paddingVertical: 8, + }, + row: { + flexDirection: "row", + alignItems: "center", + }, + avatar: { + width: 40, + height: 40, + borderRadius: 20, + marginRight: 16, + }, + textContainer: { + flex: 1, + justifyContent: "center", + }, + title: { + width: "60%", + height: 16, + borderRadius: 4, + marginBottom: 8, + }, + subtitle: { + width: "40%", + height: 12, + borderRadius: 4, + }, +}); + +export default GroupSkeleton; diff --git a/mobile/screens/HomeScreen.js b/mobile/screens/HomeScreen.js index dfb0eadd..234ff6e4 100644 --- a/mobile/screens/HomeScreen.js +++ b/mobile/screens/HomeScreen.js @@ -1,7 +1,6 @@ import { useContext, useEffect, useState } from "react"; import { Alert, FlatList, StyleSheet, View } from "react-native"; import { - ActivityIndicator, Appbar, Avatar, Button, @@ -14,6 +13,7 @@ import { import { createGroup, getGroups, getOptimizedSettlements } from "../api/groups"; import { AuthContext } from "../context/AuthContext"; import { formatCurrency, getCurrencySymbol } from "../utils/currency"; +import GroupSkeleton from "../components/GroupSkeleton"; const HomeScreen = ({ navigation }) => { const { token, logout, user } = useContext(AuthContext); @@ -232,9 +232,7 @@ const HomeScreen = ({ navigation }) => { {isLoading ? ( - - - + ) : (