Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
## [Unreleased]

### Added
- **Mobile Skeleton Loading:** Implemented skeleton loading state for the Home screen groups list.
- **Features:**
- Created reusable `Skeleton` component with pulsing opacity animation.
- Created `GroupListSkeleton` component matching `HapticCard` layout (Avatar, Title, Status).
- Replaced generic `ActivityIndicator` with skeleton list for better perceived performance.
- Dual-theme support using `react-native-paper`'s surface variant colors.
- **Technical:** Created `mobile/components/ui/Skeleton.js` and `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).
Expand Down
15 changes: 15 additions & 0 deletions .Jules/knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,21 @@ Commonly used components:
- `<Portal>` and `<Modal>` for overlays
- `<ActivityIndicator>` for loading states

### Skeleton Loading Pattern

**Date:** 2026-02-14
**Context:** Implementing skeleton loading for Card components

To perfectly match `Card` layout in skeletons, you can pass `Skeleton` components directly to `Card.Title` props:

```javascript
<Card.Title
title={<Skeleton width="60%" height={20} />}
left={(props) => <Skeleton width={props.size} height={props.size} borderRadius={props.size/2} />}
/>
```
This ensures the skeleton aligns exactly with the content state.

### Safe Area Pattern

**Date:** 2026-01-01
Expand Down
11 changes: 6 additions & 5 deletions .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- Impact: Better loading experience, less jarring
- Size: ~40 lines
- [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`
- Context: Created reusable Skeleton component and GroupListSkeleton. Replaced ActivityIndicator.
- Impact: Professional loading experience that mimics actual content layout
- Size: ~70 lines
- Added: 2026-01-01

- [x] **[a11y]** Complete accessibility labels for all screens
Expand Down
54 changes: 54 additions & 0 deletions mobile/components/skeletons/GroupListSkeleton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Card } from 'react-native-paper';
import Skeleton from '../ui/Skeleton';

const GroupListSkeleton = () => {
return (
<View
style={styles.container}
accessible={true}
accessibilityLabel="Loading groups"
accessibilityRole="progressbar"
>
{[...Array(6)].map((_, index) => (
<Card key={index} style={styles.card}>
<Card.Title
title={
<Skeleton width="60%" height={20} borderRadius={4} />
}
left={(props) => (
<Skeleton
width={props.size}
height={props.size}
borderRadius={props.size / 2}
/>
)}
/>
<Card.Content>
<Skeleton
width="80%"
height={16}
borderRadius={4}
style={styles.status}
/>
</Card.Content>
</Card>
))}
</View>
);
};

const styles = StyleSheet.create({
container: {
padding: 16,
},
card: {
marginBottom: 16,
},
status: {
marginTop: 4,
},
});

export default GroupListSkeleton;
45 changes: 45 additions & 0 deletions mobile/components/ui/Skeleton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useEffect, useRef } from 'react';
import { Animated } 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 animation = Animated.loop(
Animated.sequence([
Animated.timing(opacity, {
toValue: 0.7,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 0.3,
duration: 800,
useNativeDriver: true,
}),
])
);
animation.start();

return () => animation.stop();
}, [opacity]);

return (
<Animated.View
style={[
{
width,
height,
borderRadius,
backgroundColor: theme.colors.surfaceVariant,
opacity,
},
style,
]}
/>
);
};

export default Skeleton;
11 changes: 2 additions & 9 deletions mobile/screens/HomeScreen.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useContext, useEffect, useState } from "react";
import { Alert, FlatList, RefreshControl, StyleSheet, View } from "react-native";
import {
ActivityIndicator,
Appbar,
Avatar,
Modal,
Expand All @@ -10,6 +9,7 @@ import {
TextInput,
useTheme,
} from "react-native-paper";
import GroupListSkeleton from '../components/skeletons/GroupListSkeleton';
import HapticButton from '../components/ui/HapticButton';
import HapticCard from '../components/ui/HapticCard';
import { HapticAppbarAction } from '../components/ui/HapticAppbar';
Expand Down Expand Up @@ -257,9 +257,7 @@ const HomeScreen = ({ navigation }) => {
</Appbar.Header>

{isLoading ? (
<View style={styles.loaderContainer}>
<ActivityIndicator size="large" />
</View>
<GroupListSkeleton />
) : (
<FlatList
data={groups}
Expand Down Expand Up @@ -289,11 +287,6 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
loaderContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
list: {
padding: 16,
},
Expand Down
Loading