From 8d99ec8dc899a646b4020647c4371cafc927d438 Mon Sep 17 00:00:00 2001 From: kavin553 Date: Mon, 8 Jun 2026 16:16:02 +0530 Subject: [PATCH 1/2] 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', }, From 438008b53ad5cf2e3cbf564efcbd2b763ecdb33d Mon Sep 17 00:00:00 2001 From: kavin553 Date: Mon, 15 Jun 2026 14:35:59 +0530 Subject: [PATCH 2/2] fix error --- apps/mobile/src/screens/ScanScreen.tsx | 124 ++++++++++++------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/apps/mobile/src/screens/ScanScreen.tsx b/apps/mobile/src/screens/ScanScreen.tsx index deaddc35..44f91f15 100644 --- a/apps/mobile/src/screens/ScanScreen.tsx +++ b/apps/mobile/src/screens/ScanScreen.tsx @@ -88,43 +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(() => { - Animated.loop( - Animated.timing(scanAnim, { - toValue: 1, - duration: 2000, - easing: Easing.linear, - useNativeDriver: true, -}) - ).start(); -}, []); + useEffect(() => { + Animated.loop( + Animated.timing(scanAnim, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + useNativeDriver: true, + }) + ).start(); + }, [scanAnim]); -const translateY = scanAnim.interpolate({ - inputRange: [0, 1], - outputRange: [0, 220], -}); + const translateY = scanAnim.interpolate({ + inputRange: [0, 1], + outputRange: [0, 220], + }); -useEffect(() => { - if (!hasLoadedStoredCard) return; + useEffect(() => { + if (!hasLoadedStoredCard) return; - if (!cards.length) { - setSelectedCardId(null); - return; - } + if (!cards.length) { + setSelectedCardId(null); + return; + } const currentValid = selectedCardId && cards.some(card => card.id === selectedCardId); if (currentValid && hasUserSelected) return; @@ -245,14 +245,14 @@ useEffect(() => { {/* Camera Placeholder */} - + 📷 Camera QR Scanner @@ -314,18 +314,18 @@ const styles = StyleSheet.create({ 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, -}, + 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', @@ -361,16 +361,16 @@ const styles = StyleSheet.create({ marginTop: SPACING.md, }, cameraArea: { - flex: 1, - maxHeight: 350, - width: '100%', - alignSelf: 'center', - 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', },