@hexar/biometric-identity-sdk-react-native 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/dist/components/BiometricIdentityFlow.d.ts +17 -0
- package/dist/components/BiometricIdentityFlow.d.ts.map +1 -0
- package/dist/components/BiometricIdentityFlow.js +366 -0
- package/dist/components/CameraCapture.d.ts +15 -0
- package/dist/components/CameraCapture.d.ts.map +1 -0
- package/dist/components/CameraCapture.js +238 -0
- package/dist/components/ErrorScreen.d.ts +15 -0
- package/dist/components/ErrorScreen.d.ts.map +1 -0
- package/dist/components/ErrorScreen.js +142 -0
- package/dist/components/InstructionsScreen.d.ts +14 -0
- package/dist/components/InstructionsScreen.d.ts.map +1 -0
- package/dist/components/InstructionsScreen.js +181 -0
- package/dist/components/ResultScreen.d.ts +15 -0
- package/dist/components/ResultScreen.d.ts.map +1 -0
- package/dist/components/ResultScreen.js +182 -0
- package/dist/components/ValidationProgress.d.ts +14 -0
- package/dist/components/ValidationProgress.d.ts.map +1 -0
- package/dist/components/ValidationProgress.js +143 -0
- package/dist/components/VideoRecorder.d.ts +43 -0
- package/dist/components/VideoRecorder.d.ts.map +1 -0
- package/dist/components/VideoRecorder.js +631 -0
- package/dist/hooks/useBiometricSDK.d.ts +25 -0
- package/dist/hooks/useBiometricSDK.d.ts.map +1 -0
- package/dist/hooks/useBiometricSDK.js +173 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/package.json +27 -0
- package/src/components/BiometricIdentityFlow.tsx +557 -0
- package/src/components/CameraCapture.tsx +262 -0
- package/src/components/ErrorScreen.tsx +201 -0
- package/src/components/InstructionsScreen.tsx +269 -0
- package/src/components/ResultScreen.tsx +301 -0
- package/src/components/ValidationProgress.tsx +223 -0
- package/src/components/VideoRecorder.tsx +794 -0
- package/src/hooks/useBiometricSDK.ts +230 -0
- package/src/index.ts +24 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instructions Screen Component
|
|
3
|
+
* Shows initial instructions before starting verification
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
Text,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
TouchableOpacity,
|
|
12
|
+
ScrollView,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import { ThemeConfig, SupportedLanguage, getStrings } from '@hexar/biometric-identity-sdk-core';
|
|
15
|
+
|
|
16
|
+
export interface InstructionsScreenProps {
|
|
17
|
+
theme?: ThemeConfig;
|
|
18
|
+
language?: SupportedLanguage;
|
|
19
|
+
onStart: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const InstructionsScreen: React.FC<InstructionsScreenProps> = ({
|
|
23
|
+
theme,
|
|
24
|
+
language = 'en',
|
|
25
|
+
onStart,
|
|
26
|
+
}) => {
|
|
27
|
+
const content = language === 'es' ? spanishContent : englishContent;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.container}>
|
|
31
|
+
<ScrollView contentContainerStyle={styles.content}>
|
|
32
|
+
<Text style={[styles.title, { color: theme?.textColor || '#000000' }]}>
|
|
33
|
+
{content.title}
|
|
34
|
+
</Text>
|
|
35
|
+
|
|
36
|
+
<Text style={[styles.subtitle, { color: theme?.secondaryTextColor || '#6B7280' }]}>
|
|
37
|
+
{content.subtitle}
|
|
38
|
+
</Text>
|
|
39
|
+
|
|
40
|
+
{/* Steps */}
|
|
41
|
+
<View style={styles.stepsContainer}>
|
|
42
|
+
<InstructionStep
|
|
43
|
+
number={1}
|
|
44
|
+
title={content.step1Title}
|
|
45
|
+
description={content.step1Description}
|
|
46
|
+
theme={theme}
|
|
47
|
+
/>
|
|
48
|
+
<InstructionStep
|
|
49
|
+
number={2}
|
|
50
|
+
title={content.step2Title}
|
|
51
|
+
description={content.step2Description}
|
|
52
|
+
theme={theme}
|
|
53
|
+
/>
|
|
54
|
+
<InstructionStep
|
|
55
|
+
number={3}
|
|
56
|
+
title={content.step3Title}
|
|
57
|
+
description={content.step3Description}
|
|
58
|
+
theme={theme}
|
|
59
|
+
/>
|
|
60
|
+
</View>
|
|
61
|
+
|
|
62
|
+
{/* Tips */}
|
|
63
|
+
<View style={styles.tipsContainer}>
|
|
64
|
+
<Text style={[styles.tipsTitle, { color: theme?.textColor || '#000000' }]}>
|
|
65
|
+
{content.tipsTitle}
|
|
66
|
+
</Text>
|
|
67
|
+
{content.tips.map((tip, index) => (
|
|
68
|
+
<Text key={index} style={styles.tipText}>
|
|
69
|
+
• {tip}
|
|
70
|
+
</Text>
|
|
71
|
+
))}
|
|
72
|
+
</View>
|
|
73
|
+
|
|
74
|
+
{/* Privacy Notice */}
|
|
75
|
+
<View style={styles.privacyContainer}>
|
|
76
|
+
<Text style={styles.privacyText}>{content.privacy}</Text>
|
|
77
|
+
</View>
|
|
78
|
+
</ScrollView>
|
|
79
|
+
|
|
80
|
+
{/* Start Button */}
|
|
81
|
+
<View style={styles.footer}>
|
|
82
|
+
<TouchableOpacity
|
|
83
|
+
style={[
|
|
84
|
+
styles.button,
|
|
85
|
+
{ backgroundColor: theme?.primaryColor || '#6366F1' },
|
|
86
|
+
]}
|
|
87
|
+
onPress={onStart}
|
|
88
|
+
>
|
|
89
|
+
<Text style={styles.buttonText}>{content.startButton}</Text>
|
|
90
|
+
</TouchableOpacity>
|
|
91
|
+
</View>
|
|
92
|
+
</View>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
interface InstructionStepProps {
|
|
97
|
+
number: number;
|
|
98
|
+
title: string;
|
|
99
|
+
description: string;
|
|
100
|
+
theme?: ThemeConfig;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const InstructionStep: React.FC<InstructionStepProps> = ({
|
|
104
|
+
number,
|
|
105
|
+
title,
|
|
106
|
+
description,
|
|
107
|
+
theme,
|
|
108
|
+
}) => (
|
|
109
|
+
<View style={stepStyles.container}>
|
|
110
|
+
<View
|
|
111
|
+
style={[
|
|
112
|
+
stepStyles.numberContainer,
|
|
113
|
+
{ backgroundColor: theme?.primaryColor || '#6366F1' },
|
|
114
|
+
]}
|
|
115
|
+
>
|
|
116
|
+
<Text style={stepStyles.number}>{number}</Text>
|
|
117
|
+
</View>
|
|
118
|
+
<View style={stepStyles.content}>
|
|
119
|
+
<Text style={[stepStyles.title, { color: theme?.textColor || '#000000' }]}>
|
|
120
|
+
{title}
|
|
121
|
+
</Text>
|
|
122
|
+
<Text style={[stepStyles.description, { color: theme?.secondaryTextColor || '#6B7280' }]}>
|
|
123
|
+
{description}
|
|
124
|
+
</Text>
|
|
125
|
+
</View>
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const stepStyles = StyleSheet.create({
|
|
130
|
+
container: {
|
|
131
|
+
flexDirection: 'row',
|
|
132
|
+
marginBottom: 24,
|
|
133
|
+
},
|
|
134
|
+
numberContainer: {
|
|
135
|
+
width: 40,
|
|
136
|
+
height: 40,
|
|
137
|
+
borderRadius: 20,
|
|
138
|
+
justifyContent: 'center',
|
|
139
|
+
alignItems: 'center',
|
|
140
|
+
marginRight: 16,
|
|
141
|
+
},
|
|
142
|
+
number: {
|
|
143
|
+
fontSize: 18,
|
|
144
|
+
fontWeight: 'bold',
|
|
145
|
+
color: '#FFFFFF',
|
|
146
|
+
},
|
|
147
|
+
content: {
|
|
148
|
+
flex: 1,
|
|
149
|
+
},
|
|
150
|
+
title: {
|
|
151
|
+
fontSize: 16,
|
|
152
|
+
fontWeight: '600',
|
|
153
|
+
marginBottom: 4,
|
|
154
|
+
},
|
|
155
|
+
description: {
|
|
156
|
+
fontSize: 14,
|
|
157
|
+
lineHeight: 20,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const styles = StyleSheet.create({
|
|
162
|
+
container: {
|
|
163
|
+
flex: 1,
|
|
164
|
+
backgroundColor: '#FFFFFF',
|
|
165
|
+
},
|
|
166
|
+
content: {
|
|
167
|
+
padding: 24,
|
|
168
|
+
},
|
|
169
|
+
title: {
|
|
170
|
+
fontSize: 28,
|
|
171
|
+
fontWeight: 'bold',
|
|
172
|
+
marginBottom: 8,
|
|
173
|
+
},
|
|
174
|
+
subtitle: {
|
|
175
|
+
fontSize: 16,
|
|
176
|
+
marginBottom: 32,
|
|
177
|
+
lineHeight: 24,
|
|
178
|
+
},
|
|
179
|
+
stepsContainer: {
|
|
180
|
+
marginBottom: 32,
|
|
181
|
+
},
|
|
182
|
+
tipsContainer: {
|
|
183
|
+
backgroundColor: '#F9FAFB',
|
|
184
|
+
padding: 16,
|
|
185
|
+
borderRadius: 8,
|
|
186
|
+
marginBottom: 24,
|
|
187
|
+
},
|
|
188
|
+
tipsTitle: {
|
|
189
|
+
fontSize: 16,
|
|
190
|
+
fontWeight: '600',
|
|
191
|
+
marginBottom: 12,
|
|
192
|
+
},
|
|
193
|
+
tipText: {
|
|
194
|
+
fontSize: 14,
|
|
195
|
+
color: '#6B7280',
|
|
196
|
+
marginVertical: 4,
|
|
197
|
+
lineHeight: 20,
|
|
198
|
+
},
|
|
199
|
+
privacyContainer: {
|
|
200
|
+
backgroundColor: '#EEF2FF',
|
|
201
|
+
padding: 16,
|
|
202
|
+
borderRadius: 8,
|
|
203
|
+
},
|
|
204
|
+
privacyText: {
|
|
205
|
+
fontSize: 12,
|
|
206
|
+
color: '#4F46E5',
|
|
207
|
+
lineHeight: 18,
|
|
208
|
+
},
|
|
209
|
+
footer: {
|
|
210
|
+
padding: 24,
|
|
211
|
+
borderTopWidth: 1,
|
|
212
|
+
borderTopColor: '#E5E7EB',
|
|
213
|
+
},
|
|
214
|
+
button: {
|
|
215
|
+
paddingVertical: 16,
|
|
216
|
+
paddingHorizontal: 32,
|
|
217
|
+
borderRadius: 8,
|
|
218
|
+
alignItems: 'center',
|
|
219
|
+
},
|
|
220
|
+
buttonText: {
|
|
221
|
+
color: '#FFFFFF',
|
|
222
|
+
fontSize: 16,
|
|
223
|
+
fontWeight: '600',
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const englishContent = {
|
|
228
|
+
title: 'Identity Verification',
|
|
229
|
+
subtitle: 'We need to verify your identity to continue. This process takes about 2 minutes.',
|
|
230
|
+
step1Title: 'Capture ID Front',
|
|
231
|
+
step1Description: 'Take a clear photo of the front of your ID document.',
|
|
232
|
+
step2Title: 'Capture ID Back',
|
|
233
|
+
step2Description: 'Take a clear photo of the back of your ID document.',
|
|
234
|
+
step3Title: 'Record Face Video',
|
|
235
|
+
step3Description: 'Record a short video of your face while following on-screen instructions.',
|
|
236
|
+
tipsTitle: 'Tips for Success',
|
|
237
|
+
tips: [
|
|
238
|
+
'Ensure good lighting without glare or shadows',
|
|
239
|
+
'Keep your ID flat and all edges visible',
|
|
240
|
+
'Remove glasses and face coverings',
|
|
241
|
+
'Find a quiet place without distractions',
|
|
242
|
+
],
|
|
243
|
+
privacy: '🔒 Your data is encrypted and processed securely. We do not store your biometric data locally.',
|
|
244
|
+
startButton: 'Start Verification',
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const spanishContent = {
|
|
248
|
+
title: 'Verificación de Identidad',
|
|
249
|
+
subtitle: 'Necesitamos verificar tu identidad para continuar. Este proceso toma aproximadamente 2 minutos.',
|
|
250
|
+
step1Title: 'Captura Frente del Documento',
|
|
251
|
+
step1Description: 'Toma una foto clara del frente de tu documento de identidad.',
|
|
252
|
+
step2Title: 'Captura Reverso del Documento',
|
|
253
|
+
step2Description: 'Toma una foto clara del reverso de tu documento de identidad.',
|
|
254
|
+
step3Title: 'Graba Video Facial',
|
|
255
|
+
step3Description: 'Graba un video corto de tu rostro siguiendo las instrucciones en pantalla.',
|
|
256
|
+
tipsTitle: 'Consejos para el Éxito',
|
|
257
|
+
tips: [
|
|
258
|
+
'Asegura buena iluminación sin brillos o sombras',
|
|
259
|
+
'Mantén tu documento plano y todos los bordes visibles',
|
|
260
|
+
'Quítate los lentes y cubiertas faciales',
|
|
261
|
+
'Encuentra un lugar tranquilo sin distracciones',
|
|
262
|
+
],
|
|
263
|
+
privacy: '🔒 Tus datos están encriptados y procesados de forma segura. No almacenamos tus datos biométricos localmente.',
|
|
264
|
+
startButton: 'Iniciar Verificación',
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export default InstructionsScreen;
|
|
268
|
+
|
|
269
|
+
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result Screen Component
|
|
3
|
+
* Shows validation result (pass/fail)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
Text,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
TouchableOpacity,
|
|
12
|
+
ScrollView,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import { ValidationResult, ThemeConfig, SupportedLanguage } from '@hexar/biometric-identity-sdk-core';
|
|
15
|
+
|
|
16
|
+
export interface ResultScreenProps {
|
|
17
|
+
result: ValidationResult;
|
|
18
|
+
theme?: ThemeConfig;
|
|
19
|
+
language?: SupportedLanguage;
|
|
20
|
+
onClose: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ResultScreen: React.FC<ResultScreenProps> = ({
|
|
24
|
+
result,
|
|
25
|
+
theme,
|
|
26
|
+
language = 'en',
|
|
27
|
+
onClose,
|
|
28
|
+
}) => {
|
|
29
|
+
const isSuccess = result.isValidFaceMatch && result.isDocumentAuthentic;
|
|
30
|
+
const successColor = theme?.successColor || '#10B981';
|
|
31
|
+
const errorColor = theme?.errorColor || '#EF4444';
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View style={styles.container}>
|
|
35
|
+
<ScrollView contentContainerStyle={styles.content}>
|
|
36
|
+
{/* Result Icon */}
|
|
37
|
+
<View
|
|
38
|
+
style={[
|
|
39
|
+
styles.iconContainer,
|
|
40
|
+
{ backgroundColor: isSuccess ? successColor : errorColor },
|
|
41
|
+
]}
|
|
42
|
+
>
|
|
43
|
+
<Text style={styles.icon}>{isSuccess ? '✓' : '✗'}</Text>
|
|
44
|
+
</View>
|
|
45
|
+
|
|
46
|
+
{/* Result Title */}
|
|
47
|
+
<Text style={[styles.title, { color: theme?.textColor || '#000000' }]}>
|
|
48
|
+
{isSuccess
|
|
49
|
+
? (language === 'es' ? '¡Verificación Exitosa!' : 'Verification Successful!')
|
|
50
|
+
: (language === 'es' ? 'Verificación Fallida' : 'Verification Failed')}
|
|
51
|
+
</Text>
|
|
52
|
+
|
|
53
|
+
<Text style={[styles.subtitle, { color: theme?.secondaryTextColor || '#6B7280' }]}>
|
|
54
|
+
{isSuccess
|
|
55
|
+
? (language === 'es'
|
|
56
|
+
? 'Tu identidad ha sido verificada correctamente'
|
|
57
|
+
: 'Your identity has been verified successfully')
|
|
58
|
+
: (language === 'es'
|
|
59
|
+
? 'No pudimos verificar tu identidad'
|
|
60
|
+
: 'We were unable to verify your identity')}
|
|
61
|
+
</Text>
|
|
62
|
+
|
|
63
|
+
{/* Scores */}
|
|
64
|
+
<View style={styles.scoresContainer}>
|
|
65
|
+
<ScoreCard
|
|
66
|
+
label={language === 'es' ? 'Coincidencia Facial' : 'Face Match'}
|
|
67
|
+
score={result.matchScore}
|
|
68
|
+
passed={result.isValidFaceMatch}
|
|
69
|
+
theme={theme}
|
|
70
|
+
/>
|
|
71
|
+
<ScoreCard
|
|
72
|
+
label={language === 'es' ? 'Presencia Vital' : 'Liveness'}
|
|
73
|
+
score={result.livenessScore}
|
|
74
|
+
passed={result.livenessScore >= 80}
|
|
75
|
+
theme={theme}
|
|
76
|
+
/>
|
|
77
|
+
</View>
|
|
78
|
+
|
|
79
|
+
{/* Document Data */}
|
|
80
|
+
{result.extractedDocumentData && (
|
|
81
|
+
<View style={styles.dataContainer}>
|
|
82
|
+
<Text style={[styles.sectionTitle, { color: theme?.textColor || '#000000' }]}>
|
|
83
|
+
{language === 'es' ? 'Datos Extraídos' : 'Extracted Data'}
|
|
84
|
+
</Text>
|
|
85
|
+
<DataRow
|
|
86
|
+
label={language === 'es' ? 'Nombre' : 'Name'}
|
|
87
|
+
value={`${result.extractedDocumentData.firstName} ${result.extractedDocumentData.lastName}`}
|
|
88
|
+
theme={theme}
|
|
89
|
+
/>
|
|
90
|
+
<DataRow
|
|
91
|
+
label={language === 'es' ? 'Documento' : 'Document Number'}
|
|
92
|
+
value={result.extractedDocumentData.documentNumber}
|
|
93
|
+
theme={theme}
|
|
94
|
+
/>
|
|
95
|
+
<DataRow
|
|
96
|
+
label={language === 'es' ? 'Fecha de Nacimiento' : 'Date of Birth'}
|
|
97
|
+
value={result.extractedDocumentData.dateOfBirth}
|
|
98
|
+
theme={theme}
|
|
99
|
+
/>
|
|
100
|
+
<DataRow
|
|
101
|
+
label={language === 'es' ? 'Nacionalidad' : 'Nationality'}
|
|
102
|
+
value={result.extractedDocumentData.nationality}
|
|
103
|
+
theme={theme}
|
|
104
|
+
/>
|
|
105
|
+
</View>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{/* Warnings */}
|
|
109
|
+
{result.warnings.length > 0 && (
|
|
110
|
+
<View style={styles.warningsContainer}>
|
|
111
|
+
<Text style={[styles.sectionTitle, { color: theme?.warningColor || '#F59E0B' }]}>
|
|
112
|
+
{language === 'es' ? 'Advertencias' : 'Warnings'}
|
|
113
|
+
</Text>
|
|
114
|
+
{result.warnings.map((warning, index) => (
|
|
115
|
+
<Text key={index} style={styles.warningText}>
|
|
116
|
+
• {warning.replace(/_/g, ' ')}
|
|
117
|
+
</Text>
|
|
118
|
+
))}
|
|
119
|
+
</View>
|
|
120
|
+
)}
|
|
121
|
+
</ScrollView>
|
|
122
|
+
|
|
123
|
+
{/* Action Button */}
|
|
124
|
+
<View style={styles.footer}>
|
|
125
|
+
<TouchableOpacity
|
|
126
|
+
style={[
|
|
127
|
+
styles.button,
|
|
128
|
+
{ backgroundColor: theme?.primaryColor || '#6366F1' },
|
|
129
|
+
]}
|
|
130
|
+
onPress={onClose}
|
|
131
|
+
>
|
|
132
|
+
<Text style={styles.buttonText}>
|
|
133
|
+
{language === 'es' ? 'Continuar' : 'Continue'}
|
|
134
|
+
</Text>
|
|
135
|
+
</TouchableOpacity>
|
|
136
|
+
</View>
|
|
137
|
+
</View>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
interface ScoreCardProps {
|
|
142
|
+
label: string;
|
|
143
|
+
score: number;
|
|
144
|
+
passed: boolean;
|
|
145
|
+
theme?: ThemeConfig;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const ScoreCard: React.FC<ScoreCardProps> = ({ label, score, passed, theme }) => (
|
|
149
|
+
<View style={scoreStyles.container}>
|
|
150
|
+
<Text style={scoreStyles.label}>{label}</Text>
|
|
151
|
+
<Text
|
|
152
|
+
style={[
|
|
153
|
+
scoreStyles.score,
|
|
154
|
+
{ color: passed ? (theme?.successColor || '#10B981') : (theme?.errorColor || '#EF4444') },
|
|
155
|
+
]}
|
|
156
|
+
>
|
|
157
|
+
{Math.round(score)}
|
|
158
|
+
</Text>
|
|
159
|
+
<Text style={scoreStyles.status}>{passed ? '✓ Passed' : '✗ Failed'}</Text>
|
|
160
|
+
</View>
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const scoreStyles = StyleSheet.create({
|
|
164
|
+
container: {
|
|
165
|
+
flex: 1,
|
|
166
|
+
backgroundColor: '#F9FAFB',
|
|
167
|
+
padding: 16,
|
|
168
|
+
borderRadius: 12,
|
|
169
|
+
alignItems: 'center',
|
|
170
|
+
marginHorizontal: 8,
|
|
171
|
+
},
|
|
172
|
+
label: {
|
|
173
|
+
fontSize: 14,
|
|
174
|
+
color: '#6B7280',
|
|
175
|
+
marginBottom: 8,
|
|
176
|
+
},
|
|
177
|
+
score: {
|
|
178
|
+
fontSize: 32,
|
|
179
|
+
fontWeight: 'bold',
|
|
180
|
+
marginBottom: 4,
|
|
181
|
+
},
|
|
182
|
+
status: {
|
|
183
|
+
fontSize: 12,
|
|
184
|
+
color: '#9CA3AF',
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
interface DataRowProps {
|
|
189
|
+
label: string;
|
|
190
|
+
value: string;
|
|
191
|
+
theme?: ThemeConfig;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const DataRow: React.FC<DataRowProps> = ({ label, value, theme }) => (
|
|
195
|
+
<View style={dataRowStyles.container}>
|
|
196
|
+
<Text style={[dataRowStyles.label, { color: theme?.secondaryTextColor || '#6B7280' }]}>
|
|
197
|
+
{label}
|
|
198
|
+
</Text>
|
|
199
|
+
<Text style={[dataRowStyles.value, { color: theme?.textColor || '#000000' }]}>
|
|
200
|
+
{value}
|
|
201
|
+
</Text>
|
|
202
|
+
</View>
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const dataRowStyles = StyleSheet.create({
|
|
206
|
+
container: {
|
|
207
|
+
flexDirection: 'row',
|
|
208
|
+
justifyContent: 'space-between',
|
|
209
|
+
paddingVertical: 12,
|
|
210
|
+
borderBottomWidth: 1,
|
|
211
|
+
borderBottomColor: '#E5E7EB',
|
|
212
|
+
},
|
|
213
|
+
label: {
|
|
214
|
+
fontSize: 14,
|
|
215
|
+
fontWeight: '500',
|
|
216
|
+
},
|
|
217
|
+
value: {
|
|
218
|
+
fontSize: 14,
|
|
219
|
+
fontWeight: '600',
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const styles = StyleSheet.create({
|
|
224
|
+
container: {
|
|
225
|
+
flex: 1,
|
|
226
|
+
backgroundColor: '#FFFFFF',
|
|
227
|
+
},
|
|
228
|
+
content: {
|
|
229
|
+
padding: 24,
|
|
230
|
+
alignItems: 'center',
|
|
231
|
+
},
|
|
232
|
+
iconContainer: {
|
|
233
|
+
width: 80,
|
|
234
|
+
height: 80,
|
|
235
|
+
borderRadius: 40,
|
|
236
|
+
justifyContent: 'center',
|
|
237
|
+
alignItems: 'center',
|
|
238
|
+
marginBottom: 24,
|
|
239
|
+
},
|
|
240
|
+
icon: {
|
|
241
|
+
fontSize: 40,
|
|
242
|
+
color: '#FFFFFF',
|
|
243
|
+
fontWeight: 'bold',
|
|
244
|
+
},
|
|
245
|
+
title: {
|
|
246
|
+
fontSize: 28,
|
|
247
|
+
fontWeight: 'bold',
|
|
248
|
+
marginBottom: 8,
|
|
249
|
+
textAlign: 'center',
|
|
250
|
+
},
|
|
251
|
+
subtitle: {
|
|
252
|
+
fontSize: 16,
|
|
253
|
+
marginBottom: 32,
|
|
254
|
+
textAlign: 'center',
|
|
255
|
+
},
|
|
256
|
+
scoresContainer: {
|
|
257
|
+
flexDirection: 'row',
|
|
258
|
+
width: '100%',
|
|
259
|
+
marginBottom: 32,
|
|
260
|
+
},
|
|
261
|
+
dataContainer: {
|
|
262
|
+
width: '100%',
|
|
263
|
+
marginBottom: 24,
|
|
264
|
+
},
|
|
265
|
+
sectionTitle: {
|
|
266
|
+
fontSize: 18,
|
|
267
|
+
fontWeight: 'bold',
|
|
268
|
+
marginBottom: 16,
|
|
269
|
+
},
|
|
270
|
+
warningsContainer: {
|
|
271
|
+
width: '100%',
|
|
272
|
+
backgroundColor: '#FEF3C7',
|
|
273
|
+
padding: 16,
|
|
274
|
+
borderRadius: 8,
|
|
275
|
+
},
|
|
276
|
+
warningText: {
|
|
277
|
+
fontSize: 14,
|
|
278
|
+
color: '#92400E',
|
|
279
|
+
marginVertical: 4,
|
|
280
|
+
},
|
|
281
|
+
footer: {
|
|
282
|
+
padding: 24,
|
|
283
|
+
borderTopWidth: 1,
|
|
284
|
+
borderTopColor: '#E5E7EB',
|
|
285
|
+
},
|
|
286
|
+
button: {
|
|
287
|
+
paddingVertical: 16,
|
|
288
|
+
paddingHorizontal: 32,
|
|
289
|
+
borderRadius: 8,
|
|
290
|
+
alignItems: 'center',
|
|
291
|
+
},
|
|
292
|
+
buttonText: {
|
|
293
|
+
color: '#FFFFFF',
|
|
294
|
+
fontSize: 16,
|
|
295
|
+
fontWeight: '600',
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
export default ResultScreen;
|
|
300
|
+
|
|
301
|
+
|