@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.
Files changed (100) hide show
  1. package/README.md +43 -2
  2. package/android/src/main/java/com/trustchex/reactnativesdk/DeviceBrightnessModule.kt +66 -0
  3. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +12 -0
  4. package/ios/DeviceBrightnessModule.h +4 -0
  5. package/ios/DeviceBrightnessModule.m +27 -0
  6. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +25 -0
  7. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +19 -0
  8. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +19 -0
  9. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +18 -5
  10. package/lib/module/Screens/Static/QrCodeScanningScreen.js +10 -2
  11. package/lib/module/Screens/Static/ResultScreen.js +52 -3
  12. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +82 -72
  13. package/lib/module/Shared/Components/EIDScanner.js +63 -3
  14. package/lib/module/Shared/Components/FaceCamera.js +73 -6
  15. package/lib/module/Shared/Components/IdentityDocumentCamera.js +9 -4
  16. package/lib/module/Shared/Components/LanguageSelector.js +14 -10
  17. package/lib/module/Shared/Components/NavigationManager.js +4 -2
  18. package/lib/module/Shared/Components/QrCodeScannerCamera.js +6 -1
  19. package/lib/module/Shared/Components/StyledButton.js +108 -9
  20. package/lib/module/Shared/Components/StyledTextInput.js +87 -0
  21. package/lib/module/Shared/Contexts/AppContext.js +3 -1
  22. package/lib/module/Shared/Contexts/ThemeContext.js +40 -0
  23. package/lib/module/Shared/Libs/analytics.utils.js +430 -0
  24. package/lib/module/Shared/Libs/camera.utils.js +58 -2
  25. package/lib/module/Shared/Libs/deeplink.utils.js +8 -0
  26. package/lib/module/Shared/Libs/http-client.js +89 -28
  27. package/lib/module/Shared/Services/AnalyticsService.js +404 -0
  28. package/lib/module/Shared/Types/analytics.types.js +111 -0
  29. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +1 -0
  30. package/lib/module/Translation/index.js +17 -5
  31. package/lib/module/Trustchex.js +52 -16
  32. package/lib/module/index.js +3 -0
  33. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  34. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  35. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  36. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  37. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  38. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  39. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  40. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  41. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
  42. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  43. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  44. package/lib/typescript/src/Shared/Components/LanguageSelector.d.ts.map +1 -1
  45. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  46. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  47. package/lib/typescript/src/Shared/Components/StyledButton.d.ts +12 -2
  48. package/lib/typescript/src/Shared/Components/StyledButton.d.ts.map +1 -1
  49. package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts +15 -0
  50. package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts.map +1 -0
  51. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
  52. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  53. package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts +26 -0
  54. package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts.map +1 -0
  55. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
  56. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
  57. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
  58. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
  59. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  60. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  61. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
  62. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
  63. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
  64. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
  65. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
  66. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  67. package/lib/typescript/src/Translation/index.d.ts.map +1 -1
  68. package/lib/typescript/src/Trustchex.d.ts +1 -0
  69. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  70. package/lib/typescript/src/index.d.ts +4 -0
  71. package/lib/typescript/src/index.d.ts.map +1 -1
  72. package/package.json +6 -7
  73. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +35 -1
  74. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +30 -0
  75. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +30 -0
  76. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +30 -4
  77. package/src/Screens/Static/QrCodeScanningScreen.tsx +12 -2
  78. package/src/Screens/Static/ResultScreen.tsx +79 -4
  79. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +113 -90
  80. package/src/Shared/Components/EIDScanner.tsx +132 -3
  81. package/src/Shared/Components/FaceCamera.tsx +81 -4
  82. package/src/Shared/Components/IdentityDocumentCamera.tsx +8 -6
  83. package/src/Shared/Components/LanguageSelector.tsx +12 -11
  84. package/src/Shared/Components/NavigationManager.tsx +5 -3
  85. package/src/Shared/Components/QrCodeScannerCamera.tsx +5 -1
  86. package/src/Shared/Components/StyledButton.tsx +141 -10
  87. package/src/Shared/Components/StyledTextInput.tsx +128 -0
  88. package/src/Shared/Contexts/AppContext.ts +4 -0
  89. package/src/Shared/Contexts/ThemeContext.tsx +67 -0
  90. package/src/Shared/Libs/analytics.utils.ts +644 -0
  91. package/src/Shared/Libs/camera.utils.ts +74 -2
  92. package/src/Shared/Libs/deeplink.utils.ts +5 -0
  93. package/src/Shared/Libs/http-client.ts +105 -31
  94. package/src/Shared/Services/AnalyticsService.ts +470 -0
  95. package/src/Shared/Types/analytics.types.ts +179 -0
  96. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +1 -0
  97. package/src/Translation/Resources/tr.ts +2 -1
  98. package/src/Translation/index.ts +21 -10
  99. package/src/Trustchex.tsx +65 -20
  100. 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 { isFrameBright } from '../Libs/camera.utils';
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
- const isBright = isFrameBright(frame);
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
- 'Invalid face bounds detected, skipping face:',
509
- face.bounds
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 AppContext from '../Contexts/AppContext';
4
+ import { useTheme } from '../Contexts/ThemeContext';
5
5
 
6
6
  const LanguageSelector = () => {
7
- const appContext = React.useContext(AppContext);
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
- i18n.language.startsWith('en') ? styles.buttonTextSelected : null,
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 style={styles.verticalSeperator} />
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
- i18n.language.startsWith('tr') ? styles.buttonTextSelected : null,
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: 'black',
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 { Button } from 'react-native-paper';
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
- <Button mode="text" onPress={goToNextRouteWithAlert}>
240
+ <StyledButton mode="text" onPress={goToNextRouteWithAlert}>
239
241
  {t('navigationManager.skipStepLabel')}
240
- </Button>
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 { StyleSheet } from 'react-native';
3
- import { Button } from 'react-native-paper';
4
- import type { ButtonProps } from 'react-native-paper';
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
- type StyledButtonProps = ButtonProps;
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
- ...props
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
- <Button
15
- {...props}
16
- style={[styles.button, style]}
17
- labelStyle={[styles.label, labelStyle]}
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
  });