diff options
| author | Mohamed Bassem <me@mbassem.com> | 2026-02-09 00:25:45 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-09 00:25:45 +0000 |
| commit | b41b5647aa10d22ca83cfd3ba97146681e9f28a3 (patch) | |
| tree | 2fff51df620ce7ccbf9934db9b91ca7444ce9b08 /apps/mobile/components/sharing/LoadingAnimation.tsx | |
| parent | 07f4d7d6eff4306605725de911f90e03d6db1fe4 (diff) | |
| download | karakeep-b41b5647aa10d22ca83cfd3ba97146681e9f28a3.tar.zst | |
feat(mobile): Add animated UI feedback to sharing modal (#2427)
* feat(mobile): Add animated UI feedback to sharing modal
---------
Co-authored-by: Claude <noreply@anthropic.com>
Diffstat (limited to '')
| -rw-r--r-- | apps/mobile/components/sharing/LoadingAnimation.tsx | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/apps/mobile/components/sharing/LoadingAnimation.tsx b/apps/mobile/components/sharing/LoadingAnimation.tsx new file mode 100644 index 00000000..a8838915 --- /dev/null +++ b/apps/mobile/components/sharing/LoadingAnimation.tsx @@ -0,0 +1,120 @@ +import { useEffect } from "react"; +import { View } from "react-native"; +import Animated, { + Easing, + FadeIn, + useAnimatedStyle, + useSharedValue, + withDelay, + withRepeat, + withSequence, + withTiming, +} from "react-native-reanimated"; +import { Text } from "@/components/ui/Text"; +import { Archive } from "lucide-react-native"; + +export default function LoadingAnimation() { + const scale = useSharedValue(1); + const rotation = useSharedValue(0); + const opacity = useSharedValue(0.6); + const dotOpacity1 = useSharedValue(0); + const dotOpacity2 = useSharedValue(0); + const dotOpacity3 = useSharedValue(0); + + useEffect(() => { + scale.value = withRepeat( + withSequence( + withTiming(1.1, { duration: 800, easing: Easing.inOut(Easing.ease) }), + withTiming(1, { duration: 800, easing: Easing.inOut(Easing.ease) }), + ), + -1, + false, + ); + + rotation.value = withRepeat( + withSequence( + withTiming(-5, { duration: 400, easing: Easing.inOut(Easing.ease) }), + withTiming(5, { duration: 800, easing: Easing.inOut(Easing.ease) }), + withTiming(0, { duration: 400, easing: Easing.inOut(Easing.ease) }), + ), + -1, + false, + ); + + opacity.value = withRepeat( + withSequence( + withTiming(1, { duration: 800 }), + withTiming(0.6, { duration: 800 }), + ), + -1, + false, + ); + + dotOpacity1.value = withRepeat( + withSequence( + withTiming(1, { duration: 300 }), + withDelay(900, withTiming(0, { duration: 0 })), + ), + -1, + ); + dotOpacity2.value = withDelay( + 300, + withRepeat( + withSequence( + withTiming(1, { duration: 300 }), + withDelay(600, withTiming(0, { duration: 0 })), + ), + -1, + ), + ); + dotOpacity3.value = withDelay( + 600, + withRepeat( + withSequence( + withTiming(1, { duration: 300 }), + withDelay(300, withTiming(0, { duration: 0 })), + ), + -1, + ), + ); + }, []); + + const iconStyle = useAnimatedStyle(() => ({ + transform: [{ scale: scale.value }, { rotate: `${rotation.value}deg` }], + opacity: opacity.value, + })); + + const dot1Style = useAnimatedStyle(() => ({ opacity: dotOpacity1.value })); + const dot2Style = useAnimatedStyle(() => ({ opacity: dotOpacity2.value })); + const dot3Style = useAnimatedStyle(() => ({ opacity: dotOpacity3.value })); + + return ( + <Animated.View + entering={FadeIn.duration(300)} + className="items-center gap-6" + > + <Animated.View + style={iconStyle} + className="h-24 w-24 items-center justify-center rounded-full bg-primary/10" + > + <Archive size={48} className="text-primary" strokeWidth={1.5} /> + </Animated.View> + <View className="flex-row items-baseline"> + <Text variant="title1" className="font-semibold text-foreground"> + Hoarding + </Text> + <View className="w-8 flex-row"> + <Animated.Text style={dot1Style} className="text-xl text-foreground"> + . + </Animated.Text> + <Animated.Text style={dot2Style} className="text-xl text-foreground"> + . + </Animated.Text> + <Animated.Text style={dot3Style} className="text-xl text-foreground"> + . + </Animated.Text> + </View> + </View> + </Animated.View> + ); +} |
