diff --git a/.Jules/changelog.md b/.Jules/changelog.md index 11fc864..b7205fd 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -7,6 +7,14 @@ ## [Unreleased] ### Added +- **Mobile Skeleton Loading:** Implemented a skeleton loading state for the Home screen groups list. + - **Features:** + - Created a reusable `Skeleton` component using `Animated` for pulsing opacity. + - Created `GroupListSkeleton` to mimic the exact layout of group cards. + - Replaced the generic `ActivityIndicator` spinner with the skeleton loader for a smoother UX. + - Matches `react-native-paper` theme colors (uses `surfaceVariant`). + - **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/knowledge.md b/.Jules/knowledge.md index 43a9ab0..0b8c5bc 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -299,6 +299,27 @@ Commonly used components: - `` and `` for overlays - `` for loading states +### Skeleton Loading Pattern + +**Date:** 2026-02-12 +**Context:** Implemented in `HomeScreen` for better perceived performance. + +Use `mobile/components/ui/Skeleton.js` for building loading states: +1. **Base Component:** `` +2. **Animation:** Uses `Animated.loop` for pulsing opacity (0.3 <-> 1.0). +3. **Theming:** Automatically uses `theme.colors.surfaceVariant`. +4. **Layout:** Combine multiple `Skeleton` components inside a container (e.g., `GroupListSkeleton`) that mimics the actual content layout (e.g., inside a `Card`). + +**Example:** +```jsx + + } + left={(props) => } + /> + +``` + ### Safe Area Pattern **Date:** 2026-01-01 diff --git a/.Jules/todo.md b/.Jules/todo.md index ebb0c7a..d194d55 100644 --- a/.Jules/todo.md +++ b/.Jules/todo.md @@ -57,11 +57,12 @@ - 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-12 + - Files: `mobile/screens/HomeScreen.js`, `mobile/components/ui/Skeleton.js`, `mobile/components/skeletons/GroupListSkeleton.js` + - Context: Created reusable Skeleton component and GroupListSkeleton to replace spinner - Impact: Better loading experience, less jarring - - Size: ~40 lines + - Size: ~80 lines - Added: 2026-01-01 - [x] **[a11y]** Complete accessibility labels for all screens diff --git a/mobile/components/skeletons/GroupListSkeleton.js b/mobile/components/skeletons/GroupListSkeleton.js new file mode 100644 index 0000000..8d81120 --- /dev/null +++ b/mobile/components/skeletons/GroupListSkeleton.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { View } from 'react-native'; +import { Card } from 'react-native-paper'; +import Skeleton from '../ui/Skeleton'; + +const GroupListSkeleton = () => { + // Render 4 skeleton items to fill the screen + return ( + + {[1, 2, 3, 4].map((i) => ( + + + + + + + + + + ))} + + ); +}; + +export default GroupListSkeleton; diff --git a/mobile/components/ui/Skeleton.js b/mobile/components/ui/Skeleton.js new file mode 100644 index 0000000..0e8f94a --- /dev/null +++ b/mobile/components/ui/Skeleton.js @@ -0,0 +1,45 @@ +import React, { useEffect, useRef } from 'react'; +import { Animated, View } 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: 1, + 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..e127f43 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 ? ( - - - + ) : (