From 8d99ec8dc899a646b4020647c4371cafc927d438 Mon Sep 17 00:00:00 2001 From: kavin553 Date: Mon, 8 Jun 2026 16:16:02 +0530 Subject: [PATCH] Add animated qr scanning effect to scanscreen --- apps/mobile/src/components/Skeleton.tsx | 3 +- apps/mobile/src/screens/ScanScreen.tsx | 121 ++++++++++++++++++------ 2 files changed, 94 insertions(+), 30 deletions(-) diff --git a/apps/mobile/src/components/Skeleton.tsx b/apps/mobile/src/components/Skeleton.tsx index 4c65e855..07d1ba61 100644 --- a/apps/mobile/src/components/Skeleton.tsx +++ b/apps/mobile/src/components/Skeleton.tsx @@ -13,7 +13,8 @@ export const Skeleton: React.FC = ({ width, height, borderRadius = 4, - style, + style = {} as ViewStyle, + }) => { const opacity = useRef(new Animated.Value(0.3)).current; diff --git a/apps/mobile/src/screens/ScanScreen.tsx b/apps/mobile/src/screens/ScanScreen.tsx index b89d70bd..deaddc35 100644 --- a/apps/mobile/src/screens/ScanScreen.tsx +++ b/apps/mobile/src/screens/ScanScreen.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; + import { View, Text, @@ -7,6 +8,8 @@ import { TextInput, StatusBar, Alert, + Animated, + Easing, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useFocusEffect } from '@react-navigation/native'; @@ -32,6 +35,7 @@ const LAST_SELECTED_CARD_KEY = 'devcard.lastSelectedCardId'; export default function ScanScreen({ navigation }: Props) { const { token, user } = useAuth(); const [manualUrl, setManualUrl] = useState(''); + const scanAnim = useRef(new Animated.Value(0)).current; const [cards, setCards] = useState([]); const [selectedCardId, setSelectedCardId] = useState(null); const [storedCardId, setStoredCardId] = useState(null); @@ -84,27 +88,43 @@ export default function ScanScreen({ navigation }: Props) { ); useEffect(() => { - const loadStoredCardId = async () => { - try { - const value = await AsyncStorage.getItem(LAST_SELECTED_CARD_KEY); - setStoredCardId(value); - } catch { - setStoredCardId(null); - } finally { - setHasLoadedStoredCard(true); - } - }; + const loadStoredCardId = async () => { + try { + const value = await AsyncStorage.getItem(LAST_SELECTED_CARD_KEY); + setStoredCardId(value); + } catch { + setStoredCardId(null); + } finally { + setHasLoadedStoredCard(true); + } + }; - loadStoredCardId(); - }, []); + loadStoredCardId(); +}, []); - useEffect(() => { - if (!hasLoadedStoredCard) return; +useEffect(() => { + Animated.loop( + Animated.timing(scanAnim, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + useNativeDriver: true, +}) + ).start(); +}, []); - if (!cards.length) { - setSelectedCardId(null); - return; - } +const translateY = scanAnim.interpolate({ + inputRange: [0, 1], + outputRange: [0, 220], +}); + +useEffect(() => { + if (!hasLoadedStoredCard) return; + + if (!cards.length) { + setSelectedCardId(null); + return; + } const currentValid = selectedCardId && cards.some(card => card.id === selectedCardId); if (currentValid && hasUserSelected) return; @@ -185,15 +205,31 @@ export default function ScanScreen({ navigation }: Props) { {loadingCards ? ( - + ) : qrUrl ? ( - + + + + + ) : ( + 📷 Camera QR Scanner @@ -269,6 +313,19 @@ const styles = StyleSheet.create({ marginBottom: SPACING.lg, gap: SPACING.md, }, + scanLine: { + position: 'absolute', + top: 0, + left: 20, + right: 20, + height: 3, + backgroundColor: '#00ff99', + shadowColor: '#00ff99', + shadowOpacity: 0.8, + shadowRadius: 10, + elevation: 8, + borderRadius: 10, +}, shareHeader: { flexDirection: 'row', alignItems: 'center', @@ -304,10 +361,16 @@ const styles = StyleSheet.create({ marginTop: SPACING.md, }, cameraArea: { - flex: 1, maxHeight: 350, - backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.lg, - overflow: 'hidden', marginBottom: SPACING.lg, position: 'relative', - }, + flex: 1, + maxHeight: 350, + width: '100%', + alignSelf: 'center', + backgroundColor: COLORS.bgCard, + borderRadius: BORDER_RADIUS.lg, + overflow: 'hidden', + marginBottom: SPACING.lg, + position: 'relative', +}, cameraPlaceholder: { flex: 1, alignItems: 'center', justifyContent: 'center', },