diff --git a/package-lock.json b/package-lock.json
index 506aa3d..84490cb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"expo-status-bar": "^3.0.9",
"react": "19.1.0",
"react-native": "0.81.5",
- "react-native-dt-theme": "file:react-native-dt-theme-0.2.0.tgz",
+ "react-native-dt-theme": "file:react-native-dt-theme-0.2.2.tgz",
"react-native-nfc-manager": "^3.17.2",
"react-native-paper": "^5.14.5",
"react-native-safe-area-context": "~5.6.0",
@@ -11587,9 +11587,9 @@
}
},
"node_modules/react-native-dt-theme": {
- "version": "0.2.0",
- "resolved": "file:react-native-dt-theme-0.2.0.tgz",
- "integrity": "sha512-hp+Pvezv7nzjW9guy50Dz1XFDU2/mFcxQPztBbZTFLwIJyqFspch9Czu/ACHrKw37ZDGop9mVnOTuJP+eP1aJw==",
+ "version": "0.2.2",
+ "resolved": "file:react-native-dt-theme-0.2.2.tgz",
+ "integrity": "sha512-NGBOguPGg/48t7h7xICngHiUzCElUN1sK6/CS2twB/mlkzO9p1xC7SEnElBShOZcfRsRd7p0vWqeJsPY2q4MXQ==",
"license": "MIT",
"peerDependencies": {
"expo-font": ">=11.0.0",
diff --git a/package.json b/package.json
index 1450d48..2a2663e 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"expo-status-bar": "^3.0.9",
"react": "19.1.0",
"react-native": "0.81.5",
- "react-native-dt-theme": "file:react-native-dt-theme-0.2.0.tgz",
+ "react-native-dt-theme": "file:react-native-dt-theme-0.2.2.tgz",
"react-native-nfc-manager": "^3.17.2",
"react-native-paper": "^5.14.5",
"react-native-safe-area-context": "~5.6.0",
diff --git a/src/data/products.ts b/src/data/products.ts
index 781ff81..a1a9ea4 100644
--- a/src/data/products.ts
+++ b/src/data/products.ts
@@ -97,6 +97,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/xnt/',
canReceiveClone: true,
exactMatch: true,
+ notes: "UID cannot be changed"
},
{
id: 'xsiid',
@@ -115,6 +116,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/xsiid/',
canReceiveClone: true,
exactMatch: true,
+ notes: "UID cannot be changed"
},
{
id: 'xslx',
@@ -131,6 +133,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/xslx/',
canReceiveClone: true,
exactMatch: true,
+ notes: "UID cannot be changed"
},
// ==========================================================================
@@ -155,7 +158,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/next/',
canReceiveClone: true,
exactMatch: true,
- notes: 'Also includes T5577 for 125kHz access systems',
+ notes: 'HF UID cannot be changed. Also includes T5577 for 125kHz access systems',
},
{
id: 'next-v2',
@@ -176,7 +179,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/next-v2/',
canReceiveClone: true,
exactMatch: true,
- notes: 'Also includes T5577 for 125kHz access systems',
+ notes: 'HF UID cannot be changed. Also includes T5577 for 125kHz access systems',
},
{
id: 'xmagic',
@@ -219,7 +222,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/xdf3/',
canReceiveClone: false,
exactMatch: true,
- notes: 'Cannot clone data due to cryptographic protection',
+ notes: 'UID cannot be changed. Cannot clone data due to cryptographic protection',
desfireEvLevel: 3,
},
@@ -262,7 +265,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/vivokey-spark/',
canReceiveClone: false,
exactMatch: true,
- notes: 'Uses cryptographic authentication - cannot clone',
+ notes: 'UID cannot be changed. Uses cryptographic authentication - cannot clone',
},
// ==========================================================================
@@ -287,7 +290,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/dnext/',
canReceiveClone: true,
exactMatch: true,
- notes: 'Larger size provides improved performance over x-series',
+ notes: 'HF UID cannot be changed. Larger size provides improved performance over x-series',
},
{
id: 'dug4t',
@@ -332,6 +335,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/flexnt/',
canReceiveClone: true,
exactMatch: true,
+ notes: "UID cannot be changed"
},
// ==========================================================================
@@ -354,7 +358,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/flexdf2/',
canReceiveClone: false,
exactMatch: true,
- notes: 'Cannot clone data due to cryptographic protection',
+ notes: 'UID cannot be changed. Cannot clone data due to cryptographic protection',
desfireEvLevel: 2,
},
@@ -400,7 +404,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/apex-flex/',
canReceiveClone: false,
exactMatch: true,
- notes: 'Cannot clone - programmable via Fidesmo app',
+ notes: 'UID cannot be changed. Cannot clone - programmable via Fidesmo app',
},
{
id: 'flexsecure',
@@ -422,7 +426,7 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/flexsecure/',
canReceiveClone: false,
exactMatch: true,
- notes: 'Developer-focused - requires technical knowledge',
+ notes: 'UID cannot be changed. Developer-focused - requires technical knowledge',
},
// ==========================================================================
diff --git a/src/screens/ResultScreen.tsx b/src/screens/ResultScreen.tsx
index 4297669..44d4c3f 100644
--- a/src/screens/ResultScreen.tsx
+++ b/src/screens/ResultScreen.tsx
@@ -1,7 +1,43 @@
-import React, { useMemo, useState } from 'react';
-import { StyleSheet, View, ScrollView, Linking, TouchableOpacity } from 'react-native';
+import React, { useMemo, useState, useEffect, useRef } from 'react';
+import { StyleSheet, View, ScrollView, Linking, TouchableOpacity, Animated } from 'react-native';
import { Button, Text, Surface, Divider, Chip } from 'react-native-paper';
import { DTCard, DTButton, DTColors, DTLabel, DTChip } from 'react-native-dt-theme';
+
+// Animated section wrapper for staggered entry
+interface AnimatedSectionProps {
+ children: React.ReactNode;
+ delay: number;
+ duration?: number;
+ style?: any;
+}
+
+function AnimatedSection({ children, delay, duration = 400, style }: AnimatedSectionProps) {
+ const opacity = useRef(new Animated.Value(0)).current;
+ const translateY = useRef(new Animated.Value(20)).current;
+
+ useEffect(() => {
+ Animated.parallel([
+ Animated.timing(opacity, {
+ toValue: 1,
+ duration,
+ delay,
+ useNativeDriver: true,
+ }),
+ Animated.timing(translateY, {
+ toValue: 0,
+ duration,
+ delay,
+ useNativeDriver: true,
+ }),
+ ]).start();
+ }, [delay, duration, opacity, translateY]);
+
+ return (
+
+ {children}
+
+ );
+}
import type { ResultScreenProps } from '../types/navigation';
import { matchChipToProducts, getMatchSummary, getDesfireEvMismatchWarning, getMifareClassicCapacityWarning } from '../services/matching';
import { Product } from '../types/products';
@@ -29,6 +65,36 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
return getChipFamilyInfo(getChipFamily(transponder.type));
}, [transponder]);
+ // Calculate product count for animation timing
+ const productCount = useMemo(() => {
+ if (!matchResult) return 0;
+ const exactCount = matchResult.exactMatches.length;
+ const cloneCount = matchResult.cloneTargets.filter(
+ p => !matchResult.exactMatches.find(e => e.id === p.id)
+ ).length;
+ return exactCount + cloneCount;
+ }, [matchResult]);
+
+ // Animation delays - sequential flow
+ const delays = useMemo(() => {
+ const base = {
+ chipIdentified: 0,
+ aboutChip: 150,
+ compatibleLabel: 300,
+ productBase: 450,
+ productIncrement: 150,
+ };
+ const afterProducts = base.productBase + (productCount * base.productIncrement);
+ return {
+ ...base,
+ noMatch: base.compatibleLabel, // Shows in place of products
+ sakSwap: base.aboutChip, // Shows after chip info
+ rawData: productCount > 0 ? afterProducts : 300,
+ actions: productCount > 0 ? afterProducts + 150 : 450,
+ conversion: productCount > 0 ? afterProducts + 75 : 375, // Between raw data and actions
+ };
+ }, [productCount]);
+
// Build URL with UTM tracking parameters for analytics
const buildTrackedUrl = (baseUrl: string, content?: string) => {
const url = new URL(baseUrl);
@@ -72,83 +138,87 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
{/* Chip Identification Card */}
{transponder && (
-
+
+
-
- {transponder.chipName}
-
+
+ {transponder.chipName}
+
- {/* Show implant name if detected from memory, or payment device */}
- {transponder.implantName && (
- transponder.implantName.includes('Payment Card') ? (
-
-
- PAYMENT DEVICE DETECTED
-
-
- {transponder.implantName.replace(' Payment Card', '')}
-
-
- ) : (
-
-
- IMPLANT DETECTED
-
-
- {transponder.implantName}
-
-
- )
- )}
-
-
-
- {transponder.family}
-
- {getConfidenceLabel() && (
-
- {getConfidenceLabel()!.label}
-
+ {/* Show implant name if detected from memory, or payment device */}
+ {transponder.implantName && (
+ transponder.implantName.includes('Payment Card') ? (
+
+
+ PAYMENT DEVICE DETECTED
+
+
+ {transponder.implantName.replace(' Payment Card', '')}
+
+
+ ) : (
+
+
+ IMPLANT DETECTED
+
+
+ {transponder.implantName}
+
+
+ )
+ )}
+
+
+
+ {transponder.family}
+
+ {getConfidenceLabel() && (
+
+ >
+ {getConfidenceLabel()!.label}
+
+ )}
+
+
+ {transponder.memorySize && (
+
+
+ MEMORY
+
+
+ {transponder.memorySize} bytes
+
+
)}
-
- {transponder.memorySize && (
- MEMORY
+ CLONEABLE
-
- {transponder.memorySize} bytes
+
+ {transponder.isCloneable ? 'YES' : 'NO'}
- )}
-
-
- CLONEABLE
-
-
- {transponder.isCloneable ? 'YES' : 'NO'}
-
-
-
- {transponder.cloneabilityNote && (
-
- {transponder.cloneabilityNote}
-
- )}
-
+ {transponder.cloneabilityNote && (
+
+ {transponder.cloneabilityNote}
+
+ )}
+
+
)}
{/* Educational Chip Info Card */}
{transponder && (chipInfo || chipFamilyInfo) && (
-
+
+
setShowChipInfo(!showChipInfo)}
style={styles.chipInfoHeader}
@@ -245,28 +315,23 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
>
)}
+
)}
{/* Product Matches Card - hide for payment devices */}
{transponder && matchResult && (matchResult.exactMatches.length > 0 || matchResult.cloneTargets.length > 0) && !transponder.implantName?.includes('Payment Card') && (
- //
- <>
- {/*
- COMPATIBLE IMPLANTS
- */}
-
- {/* */}
+
+
+
+ )}
- {/*
- {getMatchSummary(matchResult, transponder.chipName)}
- */}
-
- {/* Exact Matches / Clone Targets */}
- {[...matchResult.exactMatches, ...matchResult.cloneTargets.filter(
- p => !matchResult.exactMatches.find(e => e.id === p.id)
- )].map(product => (
- //
-
+ {/* Individual Product Cards - staggered */}
+ {transponder && matchResult && (matchResult.exactMatches.length > 0 || matchResult.cloneTargets.length > 0) && !transponder.implantName?.includes('Payment Card') && (
+ [...matchResult.exactMatches, ...matchResult.cloneTargets.filter(
+ p => !matchResult.exactMatches.find(e => e.id === p.id)
+ )].map((product, index) => (
+
+
{/*
{product.name}
@@ -311,24 +376,20 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
handleProductLink(product)}
- style={styles.productButton}
- labelStyle={styles.productButtonLabel}
- compact>
+ >
VIEW PRODUCT
-
- ))}
- {/* */}
- >
+
+ ))
)}
{/* No Match Card - show when chip detected but no products match */}
{transponder && matchResult && matchResult.exactMatches.length === 0 && matchResult.cloneTargets.length === 0 && (
-
+
+
NO DIRECT MATCH
@@ -351,11 +412,13 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
>
)}
+
)}
{/* SAK Swap Detection Card */}
{transponder?.sakSwapInfo?.hasSakSwap && (
-
+
+
SAK SWAP DETECTED
@@ -379,29 +442,34 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
)}
+
)}
{/* Raw Tag Data Card */}
-
+
+
{tagData ? (
<>
-
+
UID
-
+
{tagData.uid || 'N/A'}
-
+
TECHNOLOGIES
-
- {tagData.techTypes.join(', ') || 'N/A'}
-
+ {
+ tagData.techTypes.map((t, i) =>
+
+ {t}
+ )
+ }
{tagData.sak !== undefined && (
@@ -443,10 +511,12 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
)}
+
{/* No Detection Card (fallback) */}
{!transponder && (
-
+
+
CHIP NOT IDENTIFIED
@@ -457,57 +527,60 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
Need help? Ask on our forum for assistance.
-
+
+
)}
{/* Conversion Service Card - show for payment devices, or when recommended (but NOT for unidentified chips) */}
{transponder && (transponder?.implantName?.includes('Payment Card') ||
(matchResult?.conversionRecommended && !transponder?.implantName)) && (
-
-
- {transponder?.implantName?.includes('Payment Card')
- ? "Payment cards can't be copied/cloned to implants. Our conversion service might be an option."
- : !transponder
- ? 'Unknown chip? Our conversion service can help.'
- : matchResult?.isCloneable === false
- ? "This chip uses cryptographic protection and can't be cloned. Our conversion service can help find alternatives."
- : "No direct product match found. Our conversion service can help."}
-
-
-
+
+
+
+ {transponder?.implantName?.includes('Payment Card')
+ ? "Payment cards can't be copied/cloned to implants. Our conversion service might be an option."
+ : !transponder
+ ? 'Unknown chip? Our conversion service can help.'
+ : matchResult?.isCloneable === false
+ ? "This chip uses cryptographic protection and can't be cloned. Our conversion service can help find alternatives."
+ : "No direct product match found. Our conversion service can help."}
+
+
+
+
)}
{/* Action Buttons */}
-
- navigation.navigate('Scan')}
- style={{ width: '100%' }}>
- SCAN ANOTHER
-
+
+
+ navigation.navigate('Scan')}
+ style={{ width: '100%' }}>
+ SCAN ANOTHER
+
-
-
+
+
+
-
+
);
}