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..44f91f15 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); @@ -98,6 +102,22 @@ export default function ScanScreen({ navigation }: Props) { loadStoredCardId(); }, []); + 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], + }); + useEffect(() => { if (!hasLoadedStoredCard) 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,9 +361,15 @@ 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',