@trustchex/react-native-sdk 1.250.0 → 1.266.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 +43 -2
- package/android/src/main/java/com/trustchex/reactnativesdk/DeviceBrightnessModule.kt +66 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +12 -0
- package/ios/DeviceBrightnessModule.h +4 -0
- package/ios/DeviceBrightnessModule.m +27 -0
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +25 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +19 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +19 -0
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +18 -5
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +10 -2
- package/lib/module/Screens/Static/ResultScreen.js +52 -3
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +82 -72
- package/lib/module/Shared/Components/EIDScanner.js +63 -3
- package/lib/module/Shared/Components/FaceCamera.js +73 -6
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +9 -4
- package/lib/module/Shared/Components/LanguageSelector.js +14 -10
- package/lib/module/Shared/Components/NavigationManager.js +4 -2
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +6 -1
- package/lib/module/Shared/Components/StyledButton.js +108 -9
- package/lib/module/Shared/Components/StyledTextInput.js +87 -0
- package/lib/module/Shared/Contexts/AppContext.js +3 -1
- package/lib/module/Shared/Contexts/ThemeContext.js +40 -0
- package/lib/module/Shared/Libs/analytics.utils.js +430 -0
- package/lib/module/Shared/Libs/camera.utils.js +58 -2
- package/lib/module/Shared/Libs/deeplink.utils.js +8 -0
- package/lib/module/Shared/Libs/http-client.js +89 -28
- package/lib/module/Shared/Services/AnalyticsService.js +404 -0
- package/lib/module/Shared/Types/analytics.types.js +111 -0
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +1 -0
- package/lib/module/Translation/index.js +17 -5
- package/lib/module/Trustchex.js +52 -16
- package/lib/module/index.js +3 -0
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/LanguageSelector.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/StyledButton.d.ts +12 -2
- package/lib/typescript/src/Shared/Components/StyledButton.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts +15 -0
- package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts +26 -0
- package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Translation/index.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +1 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +6 -7
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +35 -1
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +30 -0
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +30 -0
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +30 -4
- package/src/Screens/Static/QrCodeScanningScreen.tsx +12 -2
- package/src/Screens/Static/ResultScreen.tsx +79 -4
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +113 -90
- package/src/Shared/Components/EIDScanner.tsx +132 -3
- package/src/Shared/Components/FaceCamera.tsx +81 -4
- package/src/Shared/Components/IdentityDocumentCamera.tsx +8 -6
- package/src/Shared/Components/LanguageSelector.tsx +12 -11
- package/src/Shared/Components/NavigationManager.tsx +5 -3
- package/src/Shared/Components/QrCodeScannerCamera.tsx +5 -1
- package/src/Shared/Components/StyledButton.tsx +141 -10
- package/src/Shared/Components/StyledTextInput.tsx +128 -0
- package/src/Shared/Contexts/AppContext.ts +4 -0
- package/src/Shared/Contexts/ThemeContext.tsx +67 -0
- package/src/Shared/Libs/analytics.utils.ts +644 -0
- package/src/Shared/Libs/camera.utils.ts +74 -2
- package/src/Shared/Libs/deeplink.utils.ts +5 -0
- package/src/Shared/Libs/http-client.ts +105 -31
- package/src/Shared/Services/AnalyticsService.ts +470 -0
- package/src/Shared/Types/analytics.types.ts +179 -0
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +1 -0
- package/src/Translation/Resources/tr.ts +2 -1
- package/src/Translation/index.ts +21 -10
- package/src/Trustchex.tsx +65 -20
- package/src/index.tsx +33 -0
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
Platform,
|
|
9
9
|
Linking,
|
|
10
10
|
Dimensions,
|
|
11
|
+
ActivityIndicator,
|
|
12
|
+
NativeModules,
|
|
11
13
|
} from 'react-native';
|
|
12
14
|
import {
|
|
13
15
|
useCameraDevice,
|
|
@@ -26,10 +28,10 @@ import {
|
|
|
26
28
|
} from '../VisionCameraPlugins/FaceDetector';
|
|
27
29
|
import { Worklets, useSharedValue } from 'react-native-worklets-core';
|
|
28
30
|
import { crop } from '../VisionCameraPlugins/Cropper';
|
|
29
|
-
import {
|
|
31
|
+
import { isCircularRegionBright } from '../Libs/camera.utils';
|
|
30
32
|
import { useTranslation } from 'react-i18next';
|
|
31
|
-
import { ActivityIndicator } from 'react-native-paper';
|
|
32
33
|
import StyledButton from './StyledButton';
|
|
34
|
+
import { useTheme } from '../Contexts/ThemeContext';
|
|
33
35
|
|
|
34
36
|
export type FaceCameraProps = {
|
|
35
37
|
onFacesDetected: (
|
|
@@ -38,6 +40,12 @@ export type FaceCameraProps = {
|
|
|
38
40
|
isImageBright: boolean
|
|
39
41
|
) => void;
|
|
40
42
|
onCameraInitialized: (camera: Camera) => void;
|
|
43
|
+
previewRect?: {
|
|
44
|
+
minX: number;
|
|
45
|
+
minY: number;
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
};
|
|
41
49
|
};
|
|
42
50
|
|
|
43
51
|
const VIDEO_WIDTH = 1280;
|
|
@@ -49,8 +57,10 @@ const windowHeight = Dimensions.get('window').height;
|
|
|
49
57
|
const FaceCamera = ({
|
|
50
58
|
onFacesDetected,
|
|
51
59
|
onCameraInitialized,
|
|
60
|
+
previewRect,
|
|
52
61
|
}: FaceCameraProps) => {
|
|
53
62
|
useKeepAwake();
|
|
63
|
+
const theme = useTheme();
|
|
54
64
|
const cameraPermission = useCameraPermission();
|
|
55
65
|
const microphonePermission = useMicrophonePermission();
|
|
56
66
|
const [permissionsRequested, setPermissionsRequested] = useState(false);
|
|
@@ -113,10 +123,74 @@ const FaceCamera = ({
|
|
|
113
123
|
};
|
|
114
124
|
}, [device, format, isFocused]);
|
|
115
125
|
|
|
126
|
+
// Set screen brightness to maximum when camera is active
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const { DeviceBrightness } = NativeModules;
|
|
129
|
+
if (!DeviceBrightness) return;
|
|
130
|
+
|
|
131
|
+
let originalBrightness = -1;
|
|
132
|
+
|
|
133
|
+
const setBrightness = async () => {
|
|
134
|
+
if (isActive) {
|
|
135
|
+
try {
|
|
136
|
+
originalBrightness = await DeviceBrightness.getBrightness();
|
|
137
|
+
await DeviceBrightness.setBrightness(1.0);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.log('Failed to set brightness:', error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
setBrightness();
|
|
145
|
+
|
|
146
|
+
return () => {
|
|
147
|
+
if (originalBrightness >= 0) {
|
|
148
|
+
DeviceBrightness.setBrightness(originalBrightness).catch(() => { });
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}, [isActive]);
|
|
152
|
+
|
|
116
153
|
const handleWorklet = (frame: Frame) => {
|
|
117
154
|
'worklet';
|
|
118
155
|
try {
|
|
119
|
-
|
|
156
|
+
// Calculate brightness based on the circular preview area if provided
|
|
157
|
+
// Otherwise fall back to entire frame brightness
|
|
158
|
+
let isBright = false;
|
|
159
|
+
if (previewRect) {
|
|
160
|
+
// Convert preview rect from screen coordinates to frame coordinates
|
|
161
|
+
const scaleX = frame.width / windowWidth;
|
|
162
|
+
const scaleY = frame.height / windowHeight;
|
|
163
|
+
const frameCircleRect = {
|
|
164
|
+
minX: Math.floor(previewRect.minX * scaleX),
|
|
165
|
+
minY: Math.floor(previewRect.minY * scaleY),
|
|
166
|
+
width: Math.floor(previewRect.width * scaleX),
|
|
167
|
+
height: Math.floor(previewRect.height * scaleY),
|
|
168
|
+
};
|
|
169
|
+
isBright = isCircularRegionBright(frame, frameCircleRect, 60);
|
|
170
|
+
} else {
|
|
171
|
+
// Fallback: check entire frame brightness (use legacy method)
|
|
172
|
+
const buffer = frame.toArrayBuffer();
|
|
173
|
+
const data = new Uint8Array(buffer);
|
|
174
|
+
const width = frame.width;
|
|
175
|
+
const height = frame.height;
|
|
176
|
+
let luminanceSum = 0;
|
|
177
|
+
let pixelCount = 0;
|
|
178
|
+
const centerX = Math.floor(width / 2);
|
|
179
|
+
const centerY = Math.floor(height / 2);
|
|
180
|
+
const halfSizeX = Math.floor(width / 2);
|
|
181
|
+
const halfSizeY = Math.floor(height / 2);
|
|
182
|
+
|
|
183
|
+
for (let y = centerY - halfSizeY; y < centerY + halfSizeY; y++) {
|
|
184
|
+
for (let x = centerX - halfSizeX; x < centerX + halfSizeX; x++) {
|
|
185
|
+
const index = y * width + x;
|
|
186
|
+
if (data[index] !== undefined) {
|
|
187
|
+
luminanceSum += data[index];
|
|
188
|
+
pixelCount++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
isBright = (luminanceSum / pixelCount) > 60;
|
|
193
|
+
}
|
|
120
194
|
|
|
121
195
|
const image = crop(frame, {
|
|
122
196
|
cropRegion: {
|
|
@@ -164,12 +238,13 @@ const FaceCamera = ({
|
|
|
164
238
|
if (!permissionsRequested) {
|
|
165
239
|
return (
|
|
166
240
|
<View style={styles.permissionContainer}>
|
|
167
|
-
<ActivityIndicator size="large" />
|
|
241
|
+
<ActivityIndicator size="large" color={theme.colors.primary} />
|
|
168
242
|
</View>
|
|
169
243
|
);
|
|
170
244
|
}
|
|
171
245
|
|
|
172
246
|
if (!cameraPermission.hasPermission) {
|
|
247
|
+
// Camera permission denied by user - their choice, not actionable
|
|
173
248
|
return (
|
|
174
249
|
<View style={styles.permissionContainer}>
|
|
175
250
|
<Text style={styles.permissionText}>
|
|
@@ -188,6 +263,7 @@ const FaceCamera = ({
|
|
|
188
263
|
}
|
|
189
264
|
|
|
190
265
|
if (!microphonePermission.hasPermission) {
|
|
266
|
+
// Microphone permission denied by user - their choice, not actionable
|
|
191
267
|
return (
|
|
192
268
|
<View style={styles.permissionContainer}>
|
|
193
269
|
<Text style={styles.permissionText}>
|
|
@@ -206,6 +282,7 @@ const FaceCamera = ({
|
|
|
206
282
|
}
|
|
207
283
|
|
|
208
284
|
if (device == null) {
|
|
285
|
+
// No camera device - device limitation, not actionable
|
|
209
286
|
return (
|
|
210
287
|
<View style={styles.permissionContainer}>
|
|
211
288
|
<Text style={styles.permissionText}>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Text,
|
|
12
12
|
Linking,
|
|
13
13
|
Image,
|
|
14
|
+
ActivityIndicator,
|
|
14
15
|
} from 'react-native';
|
|
15
16
|
import {
|
|
16
17
|
Camera,
|
|
@@ -46,11 +47,11 @@ import {
|
|
|
46
47
|
import { getAverageBrightness } from '../Libs/camera.utils';
|
|
47
48
|
import { useTranslation } from 'react-i18next';
|
|
48
49
|
import LottieView from 'lottie-react-native';
|
|
49
|
-
import { ActivityIndicator } from 'react-native-paper';
|
|
50
50
|
import StyledButton from './StyledButton';
|
|
51
51
|
import { type Barcode, scanCodes } from '../VisionCameraPlugins/BarcodeScanner';
|
|
52
52
|
import { speakWithDebounce } from '../Libs/tts.utils';
|
|
53
53
|
import AppContext from '../Contexts/AppContext';
|
|
54
|
+
import { useTheme } from '../Contexts/ThemeContext';
|
|
54
55
|
|
|
55
56
|
export type DocumentScannedData = {
|
|
56
57
|
documentType: 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN';
|
|
@@ -133,6 +134,7 @@ const IdentityDocumentCamera = ({
|
|
|
133
134
|
showDebugImages = false,
|
|
134
135
|
}: IdentityDocumentCameraProps) => {
|
|
135
136
|
useKeepAwake();
|
|
137
|
+
const theme = useTheme();
|
|
136
138
|
const appContext = React.useContext(AppContext);
|
|
137
139
|
const cameraRef = React.useRef<Camera>(null);
|
|
138
140
|
const cameraPermission = useCameraPermission();
|
|
@@ -504,10 +506,10 @@ const IdentityDocumentCamera = ({
|
|
|
504
506
|
face.bounds.x >= width ||
|
|
505
507
|
face.bounds.y >= height
|
|
506
508
|
) {
|
|
507
|
-
console.warn(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
);
|
|
509
|
+
// console.warn(
|
|
510
|
+
// 'Invalid face bounds detected, skipping face:',
|
|
511
|
+
// face.bounds
|
|
512
|
+
// );
|
|
511
513
|
continue;
|
|
512
514
|
}
|
|
513
515
|
|
|
@@ -971,7 +973,7 @@ const IdentityDocumentCamera = ({
|
|
|
971
973
|
if (!permissionsRequested) {
|
|
972
974
|
return (
|
|
973
975
|
<View style={styles.permissionContainer}>
|
|
974
|
-
<ActivityIndicator size="large" />
|
|
976
|
+
<ActivityIndicator size="large" color={theme.colors.primary} />
|
|
975
977
|
</View>
|
|
976
978
|
);
|
|
977
979
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import i18n from '../../Translation';
|
|
4
|
-
import
|
|
4
|
+
import { useTheme } from '../Contexts/ThemeContext';
|
|
5
5
|
|
|
6
6
|
const LanguageSelector = () => {
|
|
7
|
-
const
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
const isEnglish = i18n.language.startsWith('en');
|
|
9
|
+
const isTurkish = i18n.language.startsWith('tr');
|
|
8
10
|
|
|
9
11
|
return (
|
|
10
12
|
<View style={styles.container}>
|
|
@@ -12,21 +14,22 @@ const LanguageSelector = () => {
|
|
|
12
14
|
<Text
|
|
13
15
|
style={[
|
|
14
16
|
styles.buttonText,
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
color: appContext.branding.primaryColor,
|
|
18
|
-
},
|
|
17
|
+
isEnglish && styles.buttonTextSelected,
|
|
18
|
+
isEnglish && { color: theme.colors.primary },
|
|
19
19
|
]}
|
|
20
20
|
>
|
|
21
21
|
EN
|
|
22
22
|
</Text>
|
|
23
23
|
</Pressable>
|
|
24
|
-
<View
|
|
24
|
+
<View
|
|
25
|
+
style={[styles.verticalSeperator, { borderColor: theme.colors.border }]}
|
|
26
|
+
/>
|
|
25
27
|
<Pressable onPress={() => i18n.changeLanguage('tr')}>
|
|
26
28
|
<Text
|
|
27
29
|
style={[
|
|
28
30
|
styles.buttonText,
|
|
29
|
-
|
|
31
|
+
isTurkish && styles.buttonTextSelected,
|
|
32
|
+
isTurkish && { color: theme.colors.primary },
|
|
30
33
|
]}
|
|
31
34
|
>
|
|
32
35
|
TR
|
|
@@ -46,16 +49,14 @@ const styles = StyleSheet.create({
|
|
|
46
49
|
},
|
|
47
50
|
buttonText: {
|
|
48
51
|
fontSize: 18,
|
|
49
|
-
color: '
|
|
52
|
+
color: '#999999',
|
|
50
53
|
},
|
|
51
54
|
buttonTextSelected: {
|
|
52
|
-
color: '#000000',
|
|
53
55
|
fontWeight: 'bold',
|
|
54
56
|
},
|
|
55
57
|
verticalSeperator: {
|
|
56
58
|
borderLeftWidth: 1,
|
|
57
59
|
height: 24,
|
|
58
|
-
borderColor: 'black',
|
|
59
60
|
},
|
|
60
61
|
});
|
|
61
62
|
|
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
import { View, StyleSheet, Alert } from 'react-native';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import i18n from '../../Translation';
|
|
17
|
-
import
|
|
17
|
+
import StyledButton from './StyledButton';
|
|
18
|
+
import { analyticsService } from '../Services/AnalyticsService';
|
|
18
19
|
|
|
19
20
|
// Simple global navigation lock
|
|
20
21
|
let isNavigating = false;
|
|
@@ -187,6 +188,7 @@ const NavigationManager = forwardRef(
|
|
|
187
188
|
appContext.currentWorkflowStep = undefined;
|
|
188
189
|
appContext.workflowSteps = undefined;
|
|
189
190
|
appContext.isDemoSession = false;
|
|
191
|
+
analyticsService.setDemoSession(false);
|
|
190
192
|
appContext.identificationInfo = {
|
|
191
193
|
sessionId: '',
|
|
192
194
|
identificationId: '',
|
|
@@ -235,9 +237,9 @@ const NavigationManager = forwardRef(
|
|
|
235
237
|
appContext.currentWorkflowStep &&
|
|
236
238
|
(!appContext.currentWorkflowStep?.required || canSkipStep) && (
|
|
237
239
|
<View style={styles.container}>
|
|
238
|
-
<
|
|
240
|
+
<StyledButton mode="text" onPress={goToNextRouteWithAlert}>
|
|
239
241
|
{t('navigationManager.skipStepLabel')}
|
|
240
|
-
</
|
|
242
|
+
</StyledButton>
|
|
241
243
|
</View>
|
|
242
244
|
)
|
|
243
245
|
);
|
|
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
23
23
|
import LottieView from 'lottie-react-native';
|
|
24
24
|
import { type Barcode, scanCodes } from '../VisionCameraPlugins/BarcodeScanner';
|
|
25
25
|
import StyledButton from './StyledButton';
|
|
26
|
+
import { useTheme } from '../Contexts/ThemeContext';
|
|
26
27
|
|
|
27
28
|
export interface QrCodeScannerCameraProps {
|
|
28
29
|
onQrCodeScanned: (data: string) => void;
|
|
@@ -30,6 +31,7 @@ export interface QrCodeScannerCameraProps {
|
|
|
30
31
|
|
|
31
32
|
const QrCodeScannerCamera = ({ onQrCodeScanned }: QrCodeScannerCameraProps) => {
|
|
32
33
|
useKeepAwake();
|
|
34
|
+
const theme = useTheme();
|
|
33
35
|
const cameraPermission = useCameraPermission();
|
|
34
36
|
const [permissionsRequested, setPermissionsRequested] = React.useState(false);
|
|
35
37
|
const [isActive, setIsActive] = React.useState(false);
|
|
@@ -164,12 +166,13 @@ const QrCodeScannerCamera = ({ onQrCodeScanned }: QrCodeScannerCameraProps) => {
|
|
|
164
166
|
if (!permissionsRequested) {
|
|
165
167
|
return (
|
|
166
168
|
<View style={styles.permissionContainer}>
|
|
167
|
-
<ActivityIndicator size="large" />
|
|
169
|
+
<ActivityIndicator size="large" color={theme.colors.primary} />
|
|
168
170
|
</View>
|
|
169
171
|
);
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
if (!cameraPermission.hasPermission) {
|
|
175
|
+
// Camera permission denied by user - their choice, not actionable
|
|
173
176
|
return (
|
|
174
177
|
<View style={styles.permissionContainer}>
|
|
175
178
|
<Text style={styles.permissionText}>
|
|
@@ -188,6 +191,7 @@ const QrCodeScannerCamera = ({ onQrCodeScanned }: QrCodeScannerCameraProps) => {
|
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
if (device == null) {
|
|
194
|
+
// No camera device - device limitation, not actionable
|
|
191
195
|
return (
|
|
192
196
|
<View style={styles.permissionContainer}>
|
|
193
197
|
<TextView style={styles.permissionText}>
|
|
@@ -1,21 +1,127 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
Text,
|
|
6
|
+
ActivityIndicator,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
type TextStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import { useTheme } from '../Contexts/ThemeContext';
|
|
5
11
|
|
|
6
|
-
|
|
12
|
+
// Calculate if a color is light or dark to determine text color
|
|
13
|
+
const isLightColor = (color: string): boolean => {
|
|
14
|
+
// Remove # if present
|
|
15
|
+
const hex = color.replace('#', '');
|
|
16
|
+
|
|
17
|
+
// Convert to RGB
|
|
18
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
19
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
20
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
21
|
+
|
|
22
|
+
// Calculate relative luminance using WCAG formula
|
|
23
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
24
|
+
|
|
25
|
+
// Return true if light (luminance > 0.5)
|
|
26
|
+
return luminance > 0.5;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
interface StyledButtonProps {
|
|
30
|
+
mode?: 'text' | 'outlined' | 'contained';
|
|
31
|
+
onPress?: () => void;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
loading?: boolean;
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
style?: ViewStyle;
|
|
36
|
+
labelStyle?: TextStyle;
|
|
37
|
+
buttonColor?: string;
|
|
38
|
+
textColor?: string;
|
|
39
|
+
}
|
|
7
40
|
|
|
8
41
|
const StyledButton: React.FC<StyledButtonProps> = ({
|
|
42
|
+
mode = 'contained',
|
|
43
|
+
onPress,
|
|
44
|
+
disabled = false,
|
|
45
|
+
loading = false,
|
|
46
|
+
children,
|
|
9
47
|
style,
|
|
10
48
|
labelStyle,
|
|
11
|
-
|
|
49
|
+
buttonColor,
|
|
50
|
+
textColor,
|
|
12
51
|
}) => {
|
|
52
|
+
const theme = useTheme();
|
|
53
|
+
const primaryColor = buttonColor || theme.colors.primary;
|
|
54
|
+
|
|
55
|
+
// Determine text color based on background color for contained mode
|
|
56
|
+
const getContainedTextColor = () => {
|
|
57
|
+
if (textColor) return textColor;
|
|
58
|
+
return isLightColor(primaryColor) ? '#000000' : '#FFFFFF';
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const containedTextColor = getContainedTextColor();
|
|
62
|
+
|
|
63
|
+
const getButtonStyle = () => {
|
|
64
|
+
const baseStyle = [styles.button, style];
|
|
65
|
+
if (mode === 'contained') {
|
|
66
|
+
return [
|
|
67
|
+
...baseStyle,
|
|
68
|
+
styles.containedButton,
|
|
69
|
+
{ backgroundColor: primaryColor },
|
|
70
|
+
disabled && styles.disabledButton,
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
if (mode === 'outlined') {
|
|
74
|
+
return [
|
|
75
|
+
...baseStyle,
|
|
76
|
+
styles.outlinedButton,
|
|
77
|
+
{ borderColor: primaryColor },
|
|
78
|
+
disabled && styles.disabledButton,
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
return [...baseStyle, styles.textButton];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const getTextStyle = () => {
|
|
85
|
+
const baseStyle = [styles.label, labelStyle];
|
|
86
|
+
if (mode === 'contained') {
|
|
87
|
+
return [
|
|
88
|
+
...baseStyle,
|
|
89
|
+
styles.containedLabel,
|
|
90
|
+
{ color: containedTextColor },
|
|
91
|
+
disabled && styles.disabledLabel,
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
if (mode === 'outlined') {
|
|
95
|
+
return [
|
|
96
|
+
...baseStyle,
|
|
97
|
+
styles.outlinedLabel,
|
|
98
|
+
{ color: primaryColor },
|
|
99
|
+
disabled && styles.disabledLabel,
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
return [
|
|
103
|
+
...baseStyle,
|
|
104
|
+
styles.textLabel,
|
|
105
|
+
{ color: primaryColor },
|
|
106
|
+
disabled && styles.disabledLabel,
|
|
107
|
+
];
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const loadingColor = mode === 'contained' ? containedTextColor : primaryColor;
|
|
111
|
+
|
|
13
112
|
return (
|
|
14
|
-
<
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
113
|
+
<TouchableOpacity
|
|
114
|
+
style={getButtonStyle()}
|
|
115
|
+
onPress={onPress}
|
|
116
|
+
disabled={disabled || loading}
|
|
117
|
+
activeOpacity={0.7}
|
|
118
|
+
>
|
|
119
|
+
{loading ? (
|
|
120
|
+
<ActivityIndicator size="small" color={loadingColor} />
|
|
121
|
+
) : (
|
|
122
|
+
<Text style={getTextStyle()}>{children}</Text>
|
|
123
|
+
)}
|
|
124
|
+
</TouchableOpacity>
|
|
19
125
|
);
|
|
20
126
|
};
|
|
21
127
|
|
|
@@ -24,11 +130,36 @@ const styles = StyleSheet.create({
|
|
|
24
130
|
borderRadius: 8,
|
|
25
131
|
height: 56,
|
|
26
132
|
justifyContent: 'center',
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
paddingHorizontal: 16,
|
|
135
|
+
},
|
|
136
|
+
containedButton: {
|
|
27
137
|
elevation: 2,
|
|
138
|
+
shadowColor: '#000',
|
|
139
|
+
shadowOffset: { width: 0, height: 2 },
|
|
140
|
+
shadowOpacity: 0.25,
|
|
141
|
+
shadowRadius: 3.84,
|
|
142
|
+
},
|
|
143
|
+
outlinedButton: {
|
|
144
|
+
backgroundColor: 'transparent',
|
|
145
|
+
borderWidth: 2,
|
|
146
|
+
},
|
|
147
|
+
textButton: {
|
|
148
|
+
backgroundColor: 'transparent',
|
|
149
|
+
},
|
|
150
|
+
disabledButton: {
|
|
151
|
+
opacity: 0.5,
|
|
28
152
|
},
|
|
29
153
|
label: {
|
|
30
154
|
fontWeight: 'bold',
|
|
31
155
|
fontSize: 16,
|
|
156
|
+
textAlign: 'center',
|
|
157
|
+
},
|
|
158
|
+
containedLabel: {},
|
|
159
|
+
outlinedLabel: {},
|
|
160
|
+
textLabel: {},
|
|
161
|
+
disabledLabel: {
|
|
162
|
+
opacity: 0.6,
|
|
32
163
|
},
|
|
33
164
|
});
|
|
34
165
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
TextInput,
|
|
5
|
+
View,
|
|
6
|
+
Text,
|
|
7
|
+
type TextInputProps,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
type TextStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
interface StyledTextInputProps extends TextInputProps {
|
|
13
|
+
containerStyle?: ViewStyle;
|
|
14
|
+
inputStyle?: TextStyle;
|
|
15
|
+
placeholderStyle?: TextStyle;
|
|
16
|
+
borderColor?: string;
|
|
17
|
+
focusedBorderColor?: string;
|
|
18
|
+
backgroundColor?: string;
|
|
19
|
+
textColor?: string;
|
|
20
|
+
placeholderColor?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const StyledTextInput = React.forwardRef<TextInput, StyledTextInputProps>(
|
|
24
|
+
(
|
|
25
|
+
{
|
|
26
|
+
containerStyle,
|
|
27
|
+
inputStyle,
|
|
28
|
+
placeholderStyle,
|
|
29
|
+
borderColor = '#DDDDDD',
|
|
30
|
+
focusedBorderColor,
|
|
31
|
+
backgroundColor = '#FFFFFF',
|
|
32
|
+
textColor = '#000000',
|
|
33
|
+
placeholderColor = '#999999',
|
|
34
|
+
style,
|
|
35
|
+
placeholderTextColor,
|
|
36
|
+
onFocus,
|
|
37
|
+
onBlur,
|
|
38
|
+
value,
|
|
39
|
+
...props
|
|
40
|
+
},
|
|
41
|
+
ref
|
|
42
|
+
) => {
|
|
43
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
44
|
+
const hasValue = value !== undefined && value !== '';
|
|
45
|
+
|
|
46
|
+
const handleFocus = (e: any) => {
|
|
47
|
+
setIsFocused(true);
|
|
48
|
+
onFocus?.(e);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleBlur = (e: any) => {
|
|
52
|
+
setIsFocused(false);
|
|
53
|
+
onBlur?.(e);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const currentBorderColor = isFocused
|
|
57
|
+
? focusedBorderColor || borderColor
|
|
58
|
+
: borderColor;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<View style={[styles.container, containerStyle]}>
|
|
62
|
+
<TextInput
|
|
63
|
+
ref={ref}
|
|
64
|
+
{...props}
|
|
65
|
+
value={value}
|
|
66
|
+
placeholder=""
|
|
67
|
+
style={[
|
|
68
|
+
styles.input,
|
|
69
|
+
{
|
|
70
|
+
backgroundColor,
|
|
71
|
+
borderColor: currentBorderColor,
|
|
72
|
+
color: textColor,
|
|
73
|
+
},
|
|
74
|
+
inputStyle,
|
|
75
|
+
style,
|
|
76
|
+
]}
|
|
77
|
+
onFocus={handleFocus}
|
|
78
|
+
onBlur={handleBlur}
|
|
79
|
+
/>
|
|
80
|
+
{!hasValue && props.placeholder && (
|
|
81
|
+
<View style={styles.placeholderContainer} pointerEvents="none">
|
|
82
|
+
<Text
|
|
83
|
+
style={[
|
|
84
|
+
styles.placeholderText,
|
|
85
|
+
{ color: placeholderColor || placeholderTextColor || '#999' },
|
|
86
|
+
placeholderStyle,
|
|
87
|
+
]}
|
|
88
|
+
>
|
|
89
|
+
{props.placeholder}
|
|
90
|
+
</Text>
|
|
91
|
+
</View>
|
|
92
|
+
)}
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
StyledTextInput.displayName = 'StyledTextInput';
|
|
99
|
+
|
|
100
|
+
const styles = StyleSheet.create({
|
|
101
|
+
container: {
|
|
102
|
+
width: '100%',
|
|
103
|
+
position: 'relative',
|
|
104
|
+
},
|
|
105
|
+
input: {
|
|
106
|
+
borderWidth: 2,
|
|
107
|
+
borderRadius: 8,
|
|
108
|
+
height: 56,
|
|
109
|
+
paddingHorizontal: 16,
|
|
110
|
+
fontSize: 16,
|
|
111
|
+
fontWeight: '400',
|
|
112
|
+
},
|
|
113
|
+
placeholderContainer: {
|
|
114
|
+
position: 'absolute',
|
|
115
|
+
top: 0,
|
|
116
|
+
left: 0,
|
|
117
|
+
right: 0,
|
|
118
|
+
bottom: 0,
|
|
119
|
+
justifyContent: 'center',
|
|
120
|
+
paddingHorizontal: 16,
|
|
121
|
+
},
|
|
122
|
+
placeholderText: {
|
|
123
|
+
fontSize: 16,
|
|
124
|
+
fontWeight: '400',
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export default StyledTextInput;
|
|
@@ -19,6 +19,8 @@ export type AppContextType = {
|
|
|
19
19
|
currentWorkflowStep?: WorkflowStep;
|
|
20
20
|
onCompleted?: () => void;
|
|
21
21
|
onError?: (error: string) => void;
|
|
22
|
+
setSessionId?: (id: string) => void;
|
|
23
|
+
setBaseUrl?: (url: string) => void;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export default createContext<AppContextType>({
|
|
@@ -44,4 +46,6 @@ export default createContext<AppContextType>({
|
|
|
44
46
|
currentWorkflowStep: undefined,
|
|
45
47
|
onCompleted: undefined,
|
|
46
48
|
onError: undefined,
|
|
49
|
+
setSessionId: undefined,
|
|
50
|
+
setBaseUrl: undefined,
|
|
47
51
|
});
|