-
Notifications
You must be signed in to change notification settings - Fork 23
Add mobile skeleton loading for Home Screen groups #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 GroupListSkeleton = () => { | ||
| return ( | ||
| <View | ||
| style={styles.container} | ||
| accessible={true} | ||
| accessibilityLabel="Loading groups" | ||
| accessibilityRole="progressbar" | ||
| > | ||
| {[...Array(5)].map((_, index) => ( | ||
| <Card key={index} style={styles.card}> | ||
| <Card.Title | ||
| title={<Skeleton width={120} height={20} />} | ||
| left={(props) => ( | ||
| <Skeleton | ||
| width={props.size} | ||
| height={props.size} | ||
| borderRadius={props.size / 2} | ||
| /> | ||
| )} | ||
| /> | ||
| <Card.Content> | ||
| <Skeleton width={200} height={16} style={styles.subtitle} /> | ||
| </Card.Content> | ||
| </Card> | ||
| ))} | ||
| </View> | ||
| ); | ||
| }; | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| padding: 16, | ||
| }, | ||
| card: { | ||
| marginBottom: 16, | ||
| }, | ||
| subtitle: { | ||
| marginTop: 4, | ||
| }, | ||
| }); | ||
|
|
||
| export default GroupListSkeleton; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,44 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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.5)).current; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Animated.loop( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Animated.sequence([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Animated.timing(opacity, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toValue: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| duration: 1000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useNativeDriver: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Animated.timing(opacity, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toValue: 0.5, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| duration: 1000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useNativeDriver: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ).start(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [opacity]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing animation cleanup on unmount. The looping animation is never stopped when the component unmounts, which can cause a memory leak and "update on unmounted component" warnings. Store the return value of 🛡️ Proposed fix useEffect(() => {
- Animated.loop(
+ const animation = Animated.loop(
Animated.sequence([
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 0.5,
duration: 1000,
useNativeDriver: true,
}),
])
- ).start();
+ );
+ animation.start();
+ return () => animation.stop();
}, [opacity]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Animated.View | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={[ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderRadius, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor: theme.colors.surfaceVariant, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| opacity, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accessibilityRole="progressbar" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accessibilityLabel="Loading..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default Skeleton; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider varying skeleton widths for a more natural look.
All five cards render identical skeleton dimensions (title: 120, subtitle: 200). Slightly randomizing or alternating widths (e.g.,
[120, 150, 100, 130, 110]) would produce a more realistic shimmer effect and avoid a rigid, repetitive appearance.🤖 Prompt for AI Agents