From c01aeea99b66baed5375eaee59c96a9510b48ee5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 19:49:21 +0000 Subject: [PATCH] [jules] enhance: Complete skeleton loading for HomeScreen groups Replaced the default ActivityIndicator on the mobile HomeScreen with a proper Skeleton loading state that mimics the list content layout, providing a smoother transition and better perceived performance. Added reusable Skeleton and GroupListSkeleton components with accessibility support. Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com> --- .Jules/changelog.md | 7 +++ .Jules/todo.md | 5 +- .../components/skeletons/GroupListSkeleton.js | 47 +++++++++++++++++ mobile/components/ui/Skeleton.js | 51 +++++++++++++++++++ mobile/screens/HomeScreen.js | 5 +- 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 mobile/components/skeletons/GroupListSkeleton.js create mode 100644 mobile/components/ui/Skeleton.js diff --git a/.Jules/changelog.md b/.Jules/changelog.md index 11fc864..29838df 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -55,6 +55,13 @@ - Captures errors in `AppRoutes` and displays a user-friendly message instead of a white screen. - **Technical:** Created `web/components/ErrorBoundary.tsx` using a hybrid Class+Functional approach to support Hooks in the fallback UI. Integrated into `web/App.tsx`. +- **Mobile Skeleton Loading:** Replaced generic ActivityIndicator with `GroupListSkeleton` for smoother and more professional loading state. + - **Features:** + - Custom animated `Skeleton` primitive utilizing `Animated.loop` to mimic content layout. + - Added comprehensive skeleton list container component mirroring actual data appearance. + - Ensured screen reader compatibility with appropriate progressbar ARIA equivalents (`accessible=true`, `accessibilityRole=progressbar`, `accessibilityLabel="Loading groups"`). + - **Technical:** Created `mobile/components/ui/Skeleton.js` and `mobile/components/skeletons/GroupListSkeleton.js`. Integrated into `mobile/screens/HomeScreen.js`. + - Inline form validation in Auth page with real-time feedback and proper ARIA accessibility support (`aria-invalid`, `aria-describedby`, `role="alert"`). - Dashboard skeleton loading state (`DashboardSkeleton`) to improve perceived performance during data fetch. - Comprehensive `EmptyState` component for Groups and Friends pages to better guide new users. diff --git a/.Jules/todo.md b/.Jules/todo.md index ebb0c7a..5f12324 100644 --- a/.Jules/todo.md +++ b/.Jules/todo.md @@ -57,8 +57,9 @@ - Impact: Native feel, users can easily refresh data - Size: ~150 lines -- [ ] **[ux]** Complete skeleton loading for HomeScreen groups - - File: `mobile/screens/HomeScreen.js` +- [x] **[ux]** Complete skeleton loading for HomeScreen groups + - Completed: 2026-02-14 + - Files: `mobile/screens/HomeScreen.js`, `mobile/components/ui/Skeleton.js`, `mobile/components/skeletons/GroupListSkeleton.js` - Context: Replace ActivityIndicator with skeleton group cards - Impact: Better loading experience, less jarring - Size: ~40 lines diff --git a/mobile/components/skeletons/GroupListSkeleton.js b/mobile/components/skeletons/GroupListSkeleton.js new file mode 100644 index 0000000..c63c849 --- /dev/null +++ b/mobile/components/skeletons/GroupListSkeleton.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { Card } from 'react-native-paper'; +import Skeleton from '../ui/Skeleton'; + +const GroupListSkeletonItem = () => { + return ( + + } + left={(props) => } + /> + + + + + ); +}; + +const GroupListSkeleton = ({ count = 3 }) => { + return ( + + {Array.from({ length: count }).map((_, index) => ( + + ))} + + ); +}; + +const styles = StyleSheet.create({ + container: { + padding: 16, + }, + card: { + marginBottom: 16, + }, + subtitle: { + marginTop: 4, + }, +}); + +export default GroupListSkeleton; diff --git a/mobile/components/ui/Skeleton.js b/mobile/components/ui/Skeleton.js new file mode 100644 index 0000000..0f2b642 --- /dev/null +++ b/mobile/components/ui/Skeleton.js @@ -0,0 +1,51 @@ +import React, { useEffect, useRef } from 'react'; +import { Animated, View, StyleSheet } from 'react-native'; +import { useTheme } from 'react-native-paper'; + +const Skeleton = ({ width, height, borderRadius = 4, style }) => { + const theme = useTheme(); + const opacity = useRef(new Animated.Value(0.3)).current; + + useEffect(() => { + const loop = Animated.loop( + Animated.sequence([ + Animated.timing(opacity, { + toValue: 0.7, + duration: 800, + useNativeDriver: true, + }), + Animated.timing(opacity, { + toValue: 0.3, + duration: 800, + useNativeDriver: true, + }), + ]) + ); + loop.start(); + return () => loop.stop(); + }, [opacity]); + + return ( + + ); +}; + +const styles = StyleSheet.create({ + skeleton: { + overflow: 'hidden', + }, +}); + +export default Skeleton; diff --git a/mobile/screens/HomeScreen.js b/mobile/screens/HomeScreen.js index d2f3c38..e8c2320 100644 --- a/mobile/screens/HomeScreen.js +++ b/mobile/screens/HomeScreen.js @@ -13,6 +13,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 +258,7 @@ const HomeScreen = ({ navigation }) => { {isLoading ? ( - - - + ) : (