ResultsScreen refinements
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -16,7 +16,7 @@
|
|||||||
"expo-status-bar": "^3.0.9",
|
"expo-status-bar": "^3.0.9",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5",
|
"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-nfc-manager": "^3.17.2",
|
||||||
"react-native-paper": "^5.14.5",
|
"react-native-paper": "^5.14.5",
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
@@ -11587,9 +11587,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-native-dt-theme": {
|
"node_modules/react-native-dt-theme": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.2",
|
||||||
"resolved": "file:react-native-dt-theme-0.2.0.tgz",
|
"resolved": "file:react-native-dt-theme-0.2.2.tgz",
|
||||||
"integrity": "sha512-hp+Pvezv7nzjW9guy50Dz1XFDU2/mFcxQPztBbZTFLwIJyqFspch9Czu/ACHrKw37ZDGop9mVnOTuJP+eP1aJw==",
|
"integrity": "sha512-NGBOguPGg/48t7h7xICngHiUzCElUN1sK6/CS2twB/mlkzO9p1xC7SEnElBShOZcfRsRd7p0vWqeJsPY2q4MXQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"expo-font": ">=11.0.0",
|
"expo-font": ">=11.0.0",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"expo-status-bar": "^3.0.9",
|
"expo-status-bar": "^3.0.9",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5",
|
"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-nfc-manager": "^3.17.2",
|
||||||
"react-native-paper": "^5.14.5",
|
"react-native-paper": "^5.14.5",
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/xnt/',
|
url: 'https://dangerousthings.com/product/xnt/',
|
||||||
canReceiveClone: true,
|
canReceiveClone: true,
|
||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
|
notes: "UID cannot be changed"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'xsiid',
|
id: 'xsiid',
|
||||||
@@ -115,6 +116,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/xsiid/',
|
url: 'https://dangerousthings.com/product/xsiid/',
|
||||||
canReceiveClone: true,
|
canReceiveClone: true,
|
||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
|
notes: "UID cannot be changed"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'xslx',
|
id: 'xslx',
|
||||||
@@ -131,6 +133,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/xslx/',
|
url: 'https://dangerousthings.com/product/xslx/',
|
||||||
canReceiveClone: true,
|
canReceiveClone: true,
|
||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
|
notes: "UID cannot be changed"
|
||||||
},
|
},
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -155,7 +158,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/next/',
|
url: 'https://dangerousthings.com/product/next/',
|
||||||
canReceiveClone: true,
|
canReceiveClone: true,
|
||||||
exactMatch: 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',
|
id: 'next-v2',
|
||||||
@@ -176,7 +179,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/next-v2/',
|
url: 'https://dangerousthings.com/product/next-v2/',
|
||||||
canReceiveClone: true,
|
canReceiveClone: true,
|
||||||
exactMatch: 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',
|
id: 'xmagic',
|
||||||
@@ -219,7 +222,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/xdf3/',
|
url: 'https://dangerousthings.com/product/xdf3/',
|
||||||
canReceiveClone: false,
|
canReceiveClone: false,
|
||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
notes: 'Cannot clone data due to cryptographic protection',
|
notes: 'UID cannot be changed. Cannot clone data due to cryptographic protection',
|
||||||
desfireEvLevel: 3,
|
desfireEvLevel: 3,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -262,7 +265,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/vivokey-spark/',
|
url: 'https://dangerousthings.com/product/vivokey-spark/',
|
||||||
canReceiveClone: false,
|
canReceiveClone: false,
|
||||||
exactMatch: true,
|
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/',
|
url: 'https://dangerousthings.com/product/dnext/',
|
||||||
canReceiveClone: true,
|
canReceiveClone: true,
|
||||||
exactMatch: 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',
|
id: 'dug4t',
|
||||||
@@ -332,6 +335,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/flexnt/',
|
url: 'https://dangerousthings.com/product/flexnt/',
|
||||||
canReceiveClone: true,
|
canReceiveClone: true,
|
||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
|
notes: "UID cannot be changed"
|
||||||
},
|
},
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -354,7 +358,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/flexdf2/',
|
url: 'https://dangerousthings.com/product/flexdf2/',
|
||||||
canReceiveClone: false,
|
canReceiveClone: false,
|
||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
notes: 'Cannot clone data due to cryptographic protection',
|
notes: 'UID cannot be changed. Cannot clone data due to cryptographic protection',
|
||||||
desfireEvLevel: 2,
|
desfireEvLevel: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -400,7 +404,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/apex-flex/',
|
url: 'https://dangerousthings.com/product/apex-flex/',
|
||||||
canReceiveClone: false,
|
canReceiveClone: false,
|
||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
notes: 'Cannot clone - programmable via Fidesmo app',
|
notes: 'UID cannot be changed. Cannot clone - programmable via Fidesmo app',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'flexsecure',
|
id: 'flexsecure',
|
||||||
@@ -422,7 +426,7 @@ export const PRODUCTS: Product[] = [
|
|||||||
url: 'https://dangerousthings.com/product/flexsecure/',
|
url: 'https://dangerousthings.com/product/flexsecure/',
|
||||||
canReceiveClone: false,
|
canReceiveClone: false,
|
||||||
exactMatch: true,
|
exactMatch: true,
|
||||||
notes: 'Developer-focused - requires technical knowledge',
|
notes: 'UID cannot be changed. Developer-focused - requires technical knowledge',
|
||||||
},
|
},
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|||||||
@@ -1,7 +1,43 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState, useEffect, useRef } from 'react';
|
||||||
import { StyleSheet, View, ScrollView, Linking, TouchableOpacity } from 'react-native';
|
import { StyleSheet, View, ScrollView, Linking, TouchableOpacity, Animated } from 'react-native';
|
||||||
import { Button, Text, Surface, Divider, Chip } from 'react-native-paper';
|
import { Button, Text, Surface, Divider, Chip } from 'react-native-paper';
|
||||||
import { DTCard, DTButton, DTColors, DTLabel, DTChip } from 'react-native-dt-theme';
|
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 (
|
||||||
|
<Animated.View style={[{ opacity, transform: [{ translateY }] }, style]}>
|
||||||
|
{children}
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
}
|
||||||
import type { ResultScreenProps } from '../types/navigation';
|
import type { ResultScreenProps } from '../types/navigation';
|
||||||
import { matchChipToProducts, getMatchSummary, getDesfireEvMismatchWarning, getMifareClassicCapacityWarning } from '../services/matching';
|
import { matchChipToProducts, getMatchSummary, getDesfireEvMismatchWarning, getMifareClassicCapacityWarning } from '../services/matching';
|
||||||
import { Product } from '../types/products';
|
import { Product } from '../types/products';
|
||||||
@@ -29,6 +65,36 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
|
|||||||
return getChipFamilyInfo(getChipFamily(transponder.type));
|
return getChipFamilyInfo(getChipFamily(transponder.type));
|
||||||
}, [transponder]);
|
}, [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
|
// Build URL with UTM tracking parameters for analytics
|
||||||
const buildTrackedUrl = (baseUrl: string, content?: string) => {
|
const buildTrackedUrl = (baseUrl: string, content?: string) => {
|
||||||
const url = new URL(baseUrl);
|
const url = new URL(baseUrl);
|
||||||
@@ -72,83 +138,87 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
|
|||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
{/* Chip Identification Card */}
|
{/* Chip Identification Card */}
|
||||||
{transponder && (
|
{transponder && (
|
||||||
<DTCard mode="success" title="CHIP IDENTIFIED" style={{ marginBottom: 20 }}>
|
<AnimatedSection delay={delays.chipIdentified}>
|
||||||
|
<DTCard mode="success" title="CHIP IDENTIFIED" style={{ marginBottom: 20 }}>
|
||||||
|
|
||||||
<Text variant="headlineMedium" style={styles.chipName}>
|
<Text variant="headlineMedium" style={styles.chipName}>
|
||||||
{transponder.chipName}
|
{transponder.chipName}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* Show implant name if detected from memory, or payment device */}
|
{/* Show implant name if detected from memory, or payment device */}
|
||||||
{transponder.implantName && (
|
{transponder.implantName && (
|
||||||
transponder.implantName.includes('Payment Card') ? (
|
transponder.implantName.includes('Payment Card') ? (
|
||||||
<View style={styles.paymentDeviceRow}>
|
<View style={styles.paymentDeviceRow}>
|
||||||
<Text variant="labelMedium" style={styles.paymentDeviceLabel}>
|
<Text variant="labelMedium" style={styles.paymentDeviceLabel}>
|
||||||
PAYMENT DEVICE DETECTED
|
PAYMENT DEVICE DETECTED
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="titleLarge" style={styles.paymentDeviceValue}>
|
<Text variant="titleLarge" style={styles.paymentDeviceValue}>
|
||||||
{transponder.implantName.replace(' Payment Card', '')}
|
{transponder.implantName.replace(' Payment Card', '')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.implantNameRow}>
|
<View style={styles.implantNameRow}>
|
||||||
<Text variant="labelMedium" style={styles.implantNameLabel}>
|
<Text variant="labelMedium" style={styles.implantNameLabel}>
|
||||||
IMPLANT DETECTED
|
IMPLANT DETECTED
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="titleLarge" style={styles.implantNameValue}>
|
<Text variant="titleLarge" style={styles.implantNameValue}>
|
||||||
{transponder.implantName}
|
{transponder.implantName}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View style={styles.chipMeta}>
|
<View style={styles.chipMeta}>
|
||||||
<Chip
|
<DTChip
|
||||||
style={[styles.familyChip, { borderColor: DTColors.modeNormal }]}
|
variant="normal">
|
||||||
textStyle={styles.familyChipText}>
|
{transponder.family}
|
||||||
{transponder.family}
|
</DTChip>
|
||||||
</Chip>
|
{getConfidenceLabel() && (
|
||||||
{getConfidenceLabel() && (
|
<DTChip
|
||||||
<Chip
|
variant='other'
|
||||||
style={[styles.confidenceChip, { borderColor: getConfidenceLabel()!.color }]}
|
// style={[styles.confidenceChip, { borderColor: getConfidenceLabel()!.color }]}
|
||||||
textStyle={[styles.confidenceChipText, { color: getConfidenceLabel()!.color }]}>
|
// textStyle={[styles.confidenceChipText, { color: getConfidenceLabel()!.color }]}>
|
||||||
{getConfidenceLabel()!.label}
|
>
|
||||||
</Chip>
|
{getConfidenceLabel()!.label}
|
||||||
|
</DTChip>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{transponder.memorySize && (
|
||||||
|
<View style={styles.detailRow}>
|
||||||
|
<Text variant="bodyMedium" style={styles.detailLabel}>
|
||||||
|
MEMORY
|
||||||
|
</Text>
|
||||||
|
<Text variant="bodyLarge" style={styles.detailValue}>
|
||||||
|
{transponder.memorySize} bytes
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
|
||||||
|
|
||||||
{transponder.memorySize && (
|
|
||||||
<View style={styles.detailRow}>
|
<View style={styles.detailRow}>
|
||||||
<Text variant="bodyMedium" style={styles.detailLabel}>
|
<Text variant="bodyMedium" style={styles.detailLabel}>
|
||||||
MEMORY
|
CLONEABLE
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="bodyLarge" style={styles.detailValue}>
|
<Text
|
||||||
{transponder.memorySize} bytes
|
variant="bodyLarge"
|
||||||
|
style={[styles.detailValue, { color: getCloneabilityColor() }]}>
|
||||||
|
{transponder.isCloneable ? 'YES' : 'NO'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
|
||||||
|
|
||||||
<View style={styles.detailRow}>
|
{transponder.cloneabilityNote && (
|
||||||
<Text variant="bodyMedium" style={styles.detailLabel}>
|
<Text variant="bodySmall" style={styles.cloneabilityNote}>
|
||||||
CLONEABLE
|
{transponder.cloneabilityNote}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
)}
|
||||||
variant="bodyLarge"
|
</DTCard>
|
||||||
style={[styles.detailValue, { color: getCloneabilityColor() }]}>
|
</AnimatedSection>
|
||||||
{transponder.isCloneable ? 'YES' : 'NO'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{transponder.cloneabilityNote && (
|
|
||||||
<Text variant="bodySmall" style={styles.cloneabilityNote}>
|
|
||||||
{transponder.cloneabilityNote}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</DTCard>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Educational Chip Info Card */}
|
{/* Educational Chip Info Card */}
|
||||||
{transponder && (chipInfo || chipFamilyInfo) && (
|
{transponder && (chipInfo || chipFamilyInfo) && (
|
||||||
<Surface style={styles.chipInfoCard} elevation={1}>
|
<AnimatedSection delay={delays.aboutChip}>
|
||||||
|
<Surface style={styles.chipInfoCard} elevation={1}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => setShowChipInfo(!showChipInfo)}
|
onPress={() => setShowChipInfo(!showChipInfo)}
|
||||||
style={styles.chipInfoHeader}
|
style={styles.chipInfoHeader}
|
||||||
@@ -245,28 +315,23 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Surface>
|
</Surface>
|
||||||
|
</AnimatedSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Product Matches Card - hide for payment devices */}
|
{/* Product Matches Card - hide for payment devices */}
|
||||||
{transponder && matchResult && (matchResult.exactMatches.length > 0 || matchResult.cloneTargets.length > 0) && !transponder.implantName?.includes('Payment Card') && (
|
{transponder && matchResult && (matchResult.exactMatches.length > 0 || matchResult.cloneTargets.length > 0) && !transponder.implantName?.includes('Payment Card') && (
|
||||||
// <Surface style={styles.productsCard} elevation={1}>
|
<AnimatedSection delay={delays.compatibleLabel}>
|
||||||
<>
|
<DTLabel mode='emphasis' primaryText="Compatible Implants" style={{ marginBottom: 25, }} size="large" animated={false} />
|
||||||
{/* <Text variant="labelLarge" style={styles.productsLabel}>
|
</AnimatedSection>
|
||||||
COMPATIBLE IMPLANTS
|
)}
|
||||||
</Text> */}
|
|
||||||
<DTLabel mode='emphasis' primaryText="Compatible Implants" />
|
|
||||||
{/* <Divider style={[styles.divider, { backgroundColor: DTColors.modeEmphasis }]} /> */}
|
|
||||||
|
|
||||||
{/* <Text variant="bodyMedium" style={styles.matchSummary}>
|
{/* Individual Product Cards - staggered */}
|
||||||
{getMatchSummary(matchResult, transponder.chipName)}
|
{transponder && matchResult && (matchResult.exactMatches.length > 0 || matchResult.cloneTargets.length > 0) && !transponder.implantName?.includes('Payment Card') && (
|
||||||
</Text> */}
|
[...matchResult.exactMatches, ...matchResult.cloneTargets.filter(
|
||||||
|
p => !matchResult.exactMatches.find(e => e.id === p.id)
|
||||||
{/* Exact Matches / Clone Targets */}
|
)].map((product, index) => (
|
||||||
{[...matchResult.exactMatches, ...matchResult.cloneTargets.filter(
|
<AnimatedSection key={product.id} delay={delays.productBase + (index * delays.productIncrement)}>
|
||||||
p => !matchResult.exactMatches.find(e => e.id === p.id)
|
<DTCard mode='emphasis' title={product.name} style={{ marginBottom: 25 }}>
|
||||||
)].map(product => (
|
|
||||||
// <Surface key={product.id} style={styles.productItem} elevation={0}>
|
|
||||||
<DTCard mode='emphasis' title={product.name} style={{ paddingBottom: 10 }}>
|
|
||||||
<View style={styles.productHeader}>
|
<View style={styles.productHeader}>
|
||||||
{/* <Text variant="titleMedium" style={styles.productName}>
|
{/* <Text variant="titleMedium" style={styles.productName}>
|
||||||
{product.name}
|
{product.name}
|
||||||
@@ -311,24 +376,20 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<DTButton
|
<DTButton
|
||||||
mode="outlined"
|
|
||||||
onPress={() => handleProductLink(product)}
|
onPress={() => handleProductLink(product)}
|
||||||
style={styles.productButton}
|
>
|
||||||
labelStyle={styles.productButtonLabel}
|
|
||||||
compact>
|
|
||||||
VIEW PRODUCT
|
VIEW PRODUCT
|
||||||
|
|
||||||
</DTButton>
|
</DTButton>
|
||||||
</DTCard>
|
</DTCard>
|
||||||
|
</AnimatedSection>
|
||||||
))}
|
))
|
||||||
{/* </Surface> */}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* No Match Card - show when chip detected but no products match */}
|
{/* No Match Card - show when chip detected but no products match */}
|
||||||
{transponder && matchResult && matchResult.exactMatches.length === 0 && matchResult.cloneTargets.length === 0 && (
|
{transponder && matchResult && matchResult.exactMatches.length === 0 && matchResult.cloneTargets.length === 0 && (
|
||||||
<Surface style={styles.noMatchCard} elevation={1}>
|
<AnimatedSection delay={delays.noMatch}>
|
||||||
|
<Surface style={styles.noMatchCard} elevation={1}>
|
||||||
<Text variant="labelLarge" style={styles.noMatchLabel}>
|
<Text variant="labelLarge" style={styles.noMatchLabel}>
|
||||||
NO DIRECT MATCH
|
NO DIRECT MATCH
|
||||||
</Text>
|
</Text>
|
||||||
@@ -351,11 +412,13 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Surface>
|
</Surface>
|
||||||
|
</AnimatedSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* SAK Swap Detection Card */}
|
{/* SAK Swap Detection Card */}
|
||||||
{transponder?.sakSwapInfo?.hasSakSwap && (
|
{transponder?.sakSwapInfo?.hasSakSwap && (
|
||||||
<Surface style={styles.sakSwapCard} elevation={1}>
|
<AnimatedSection delay={delays.sakSwap}>
|
||||||
|
<Surface style={styles.sakSwapCard} elevation={1}>
|
||||||
<Text variant="labelLarge" style={styles.sakSwapLabel}>
|
<Text variant="labelLarge" style={styles.sakSwapLabel}>
|
||||||
SAK SWAP DETECTED
|
SAK SWAP DETECTED
|
||||||
</Text>
|
</Text>
|
||||||
@@ -379,29 +442,34 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</Surface>
|
</Surface>
|
||||||
|
</AnimatedSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Raw Tag Data Card */}
|
{/* Raw Tag Data Card */}
|
||||||
<DTCard mode="normal" title="RAW TAG DATA" style={{ marginBottom: 20 }}>
|
<AnimatedSection delay={delays.rawData}>
|
||||||
|
<DTCard mode="normal" title="RAW TAG DATA" style={{ marginBottom: 20 }}>
|
||||||
|
|
||||||
{tagData ? (
|
{tagData ? (
|
||||||
<>
|
<>
|
||||||
<View style={styles.dataRow}>
|
<View style={styles.dataRow}>
|
||||||
<Text variant="bodyMedium" style={styles.dataLabel}>
|
<Text variant="bodyLarge" style={styles.dataLabel}>
|
||||||
UID
|
UID
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="bodyLarge" style={styles.dataValue}>
|
<Text variant="bodyMedium" style={styles.dataValue}>
|
||||||
{tagData.uid || 'N/A'}
|
{tagData.uid || 'N/A'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.dataRow}>
|
<View style={styles.dataRow}>
|
||||||
<Text variant="bodyMedium" style={styles.dataLabel}>
|
<Text variant="bodyLarge" style={styles.dataLabel}>
|
||||||
TECHNOLOGIES
|
TECHNOLOGIES
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="bodyLarge" style={styles.dataValue}>
|
{
|
||||||
{tagData.techTypes.join(', ') || 'N/A'}
|
tagData.techTypes.map((t, i) =>
|
||||||
</Text>
|
<Text key={i} variant="bodyMedium" style={styles.dataValue}>
|
||||||
|
{t}
|
||||||
|
</Text>)
|
||||||
|
}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{tagData.sak !== undefined && (
|
{tagData.sak !== undefined && (
|
||||||
@@ -443,10 +511,12 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</DTCard>
|
</DTCard>
|
||||||
|
</AnimatedSection>
|
||||||
|
|
||||||
{/* No Detection Card (fallback) */}
|
{/* No Detection Card (fallback) */}
|
||||||
{!transponder && (
|
{!transponder && (
|
||||||
<Surface style={styles.noDetectionCard} elevation={1}>
|
<AnimatedSection delay={0}>
|
||||||
|
<Surface style={styles.noDetectionCard} elevation={1}>
|
||||||
<Text variant="labelLarge" style={styles.noDetectionLabel}>
|
<Text variant="labelLarge" style={styles.noDetectionLabel}>
|
||||||
CHIP NOT IDENTIFIED
|
CHIP NOT IDENTIFIED
|
||||||
</Text>
|
</Text>
|
||||||
@@ -457,57 +527,60 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
|
|||||||
<Text variant="bodyMedium" style={styles.noDetectionText}>
|
<Text variant="bodyMedium" style={styles.noDetectionText}>
|
||||||
Need help? Ask on our forum for assistance.
|
Need help? Ask on our forum for assistance.
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<DTButton
|
||||||
mode="outlined"
|
|
||||||
onPress={() => Linking.openURL(buildTrackedUrl('https://dngr.us/forum', 'unknown_chip'))}
|
onPress={() => Linking.openURL(buildTrackedUrl('https://dngr.us/forum', 'unknown_chip'))}
|
||||||
style={styles.forumButton}
|
>
|
||||||
labelStyle={styles.forumButtonLabel}>
|
|
||||||
VISIT FORUM
|
VISIT FORUM
|
||||||
</Button>
|
</DTButton>
|
||||||
</Surface>
|
</Surface>
|
||||||
|
</AnimatedSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Conversion Service Card - show for payment devices, or when recommended (but NOT for unidentified chips) */}
|
{/* Conversion Service Card - show for payment devices, or when recommended (but NOT for unidentified chips) */}
|
||||||
{transponder && (transponder?.implantName?.includes('Payment Card') ||
|
{transponder && (transponder?.implantName?.includes('Payment Card') ||
|
||||||
(matchResult?.conversionRecommended && !transponder?.implantName)) && (
|
(matchResult?.conversionRecommended && !transponder?.implantName)) && (
|
||||||
<Surface style={styles.conversionCard} elevation={1}>
|
<AnimatedSection delay={delays.conversion}>
|
||||||
<Text variant="bodyMedium" style={styles.conversionText}>
|
<Surface style={styles.conversionCard} elevation={1}>
|
||||||
{transponder?.implantName?.includes('Payment Card')
|
<Text variant="bodyMedium" style={styles.conversionText}>
|
||||||
? "Payment cards can't be copied/cloned to implants. Our conversion service might be an option."
|
{transponder?.implantName?.includes('Payment Card')
|
||||||
: !transponder
|
? "Payment cards can't be copied/cloned to implants. Our conversion service might be an option."
|
||||||
? 'Unknown chip? Our conversion service can help.'
|
: !transponder
|
||||||
: matchResult?.isCloneable === false
|
? 'Unknown chip? Our conversion service can help.'
|
||||||
? "This chip uses cryptographic protection and can't be cloned. Our conversion service can help find alternatives."
|
: matchResult?.isCloneable === false
|
||||||
: "No direct product match found. Our conversion service can help."}
|
? "This chip uses cryptographic protection and can't be cloned. Our conversion service can help find alternatives."
|
||||||
</Text>
|
: "No direct product match found. Our conversion service can help."}
|
||||||
<Button
|
</Text>
|
||||||
mode="outlined"
|
<Button
|
||||||
onPress={handleConversionLink}
|
mode="outlined"
|
||||||
style={styles.conversionButton}
|
onPress={handleConversionLink}
|
||||||
labelStyle={styles.conversionButtonLabel}>
|
style={styles.conversionButton}
|
||||||
CONVERSION SERVICE
|
labelStyle={styles.conversionButtonLabel}>
|
||||||
</Button>
|
CONVERSION SERVICE
|
||||||
</Surface>
|
</Button>
|
||||||
|
</Surface>
|
||||||
|
</AnimatedSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
<View style={styles.actions}>
|
<AnimatedSection delay={delays.actions}>
|
||||||
<DTButton
|
<View style={styles.actions}>
|
||||||
variant="normal"
|
<DTButton
|
||||||
onPress={() => navigation.navigate('Scan')}
|
variant="normal"
|
||||||
style={{ width: '100%' }}>
|
onPress={() => navigation.navigate('Scan')}
|
||||||
SCAN ANOTHER
|
style={{ width: '100%' }}>
|
||||||
</DTButton>
|
SCAN ANOTHER
|
||||||
|
</DTButton>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
mode="text"
|
mode="text"
|
||||||
onPress={() => navigation.navigate('Home')}
|
onPress={() => navigation.navigate('Home')}
|
||||||
labelStyle={styles.homeLabel}>
|
labelStyle={styles.homeLabel}>
|
||||||
HOME
|
HOME
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
</AnimatedSection>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user