react-native-fpay 0.2.9 → 0.3.2

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 (120) hide show
  1. package/lib/module/FountainPayProvider.js +5 -1
  2. package/lib/module/FountainPayProvider.js.map +1 -1
  3. package/lib/module/core/api/index.js +22 -12
  4. package/lib/module/core/api/index.js.map +1 -1
  5. package/lib/module/engine/BLEReceiverService.js.map +1 -1
  6. package/lib/module/engine/FPEngine.js +24 -13
  7. package/lib/module/engine/FPEngine.js.map +1 -1
  8. package/lib/module/engine/useIsForeground.js +17 -0
  9. package/lib/module/engine/useIsForeground.js.map +1 -0
  10. package/lib/module/ui/components/AnimatedDots.js +68 -0
  11. package/lib/module/ui/components/AnimatedDots.js.map +1 -0
  12. package/lib/module/ui/components/ConfirmScreen.js +333 -0
  13. package/lib/module/ui/components/ConfirmScreen.js.map +1 -0
  14. package/lib/module/ui/modals/FPPaymentRequestModal.js +6 -8
  15. package/lib/module/ui/modals/FPPaymentRequestModal.js.map +1 -1
  16. package/lib/module/ui/modals/FPShell.js +7 -4
  17. package/lib/module/ui/modals/FPShell.js.map +1 -1
  18. package/lib/module/ui/screens/ReceiveScreen.js +379 -274
  19. package/lib/module/ui/screens/ReceiveScreen.js.map +1 -1
  20. package/lib/module/ui/screens/SendScreen.js +154 -45
  21. package/lib/module/ui/screens/SendScreen.js.map +1 -1
  22. package/lib/module/ui/screens/styles.js +89 -0
  23. package/lib/module/ui/screens/styles.js.map +1 -0
  24. package/lib/module/ui/screens/sub/receivePayment/Nfc/index.js +361 -0
  25. package/lib/module/ui/screens/sub/receivePayment/Nfc/index.js.map +1 -0
  26. package/lib/module/ui/screens/sub/receivePayment/Qr/index.js +338 -0
  27. package/lib/module/ui/screens/sub/receivePayment/Qr/index.js.map +1 -0
  28. package/lib/module/ui/screens/sub/receivePayment/Transfer/index.js +453 -0
  29. package/lib/module/ui/screens/sub/receivePayment/Transfer/index.js.map +1 -0
  30. package/lib/module/ui/screens/sub/{BluetoothSubScreen.js → sendPayment/BluetoothSubScreen.js} +25 -32
  31. package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js.map +1 -0
  32. package/lib/module/ui/screens/sub/sendPayment/NFCSubScreen.js +354 -0
  33. package/lib/module/ui/screens/sub/sendPayment/NFCSubScreen.js.map +1 -0
  34. package/lib/module/ui/screens/sub/sendPayment/NQRSubScreen.js +440 -0
  35. package/lib/module/ui/screens/sub/sendPayment/NQRSubScreen.js.map +1 -0
  36. package/lib/module/ui/screens/sub/{ProximitySubScreen.js → sendPayment/ProximitySubScreen.js} +20 -111
  37. package/lib/module/ui/screens/sub/sendPayment/ProximitySubScreen.js.map +1 -0
  38. package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js +327 -0
  39. package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js.map +1 -0
  40. package/lib/typescript/src/FountainPayProvider.d.ts.map +1 -1
  41. package/lib/typescript/src/core/api/index.d.ts +20 -27
  42. package/lib/typescript/src/core/api/index.d.ts.map +1 -1
  43. package/lib/typescript/src/core/types/index.d.ts +56 -13
  44. package/lib/typescript/src/core/types/index.d.ts.map +1 -1
  45. package/lib/typescript/src/engine/BLEReceiverService.d.ts +2 -0
  46. package/lib/typescript/src/engine/BLEReceiverService.d.ts.map +1 -1
  47. package/lib/typescript/src/engine/FPEngine.d.ts +3 -1
  48. package/lib/typescript/src/engine/FPEngine.d.ts.map +1 -1
  49. package/lib/typescript/src/engine/useIsForeground.d.ts +2 -0
  50. package/lib/typescript/src/engine/useIsForeground.d.ts.map +1 -0
  51. package/lib/typescript/src/ui/components/AnimatedDots.d.ts +2 -0
  52. package/lib/typescript/src/ui/components/AnimatedDots.d.ts.map +1 -0
  53. package/lib/typescript/src/ui/components/ConfirmScreen.d.ts +10 -0
  54. package/lib/typescript/src/ui/components/ConfirmScreen.d.ts.map +1 -0
  55. package/lib/typescript/src/ui/components/OtpInput/Styles.d.ts +3 -3
  56. package/lib/typescript/src/ui/modals/FPPaymentRequestModal.d.ts.map +1 -1
  57. package/lib/typescript/src/ui/modals/FPShell.d.ts.map +1 -1
  58. package/lib/typescript/src/ui/screens/ReceiveScreen.d.ts +2 -9
  59. package/lib/typescript/src/ui/screens/ReceiveScreen.d.ts.map +1 -1
  60. package/lib/typescript/src/ui/screens/SendScreen.d.ts +4 -2
  61. package/lib/typescript/src/ui/screens/SendScreen.d.ts.map +1 -1
  62. package/lib/typescript/src/ui/screens/styles.d.ts +1390 -0
  63. package/lib/typescript/src/ui/screens/styles.d.ts.map +1 -0
  64. package/lib/typescript/src/ui/screens/sub/receivePayment/Nfc/index.d.ts +10 -0
  65. package/lib/typescript/src/ui/screens/sub/receivePayment/Nfc/index.d.ts.map +1 -0
  66. package/lib/typescript/src/ui/screens/sub/receivePayment/Qr/index.d.ts +10 -0
  67. package/lib/typescript/src/ui/screens/sub/receivePayment/Qr/index.d.ts.map +1 -0
  68. package/lib/typescript/src/ui/screens/sub/receivePayment/Transfer/index.d.ts +5 -0
  69. package/lib/typescript/src/ui/screens/sub/receivePayment/Transfer/index.d.ts.map +1 -0
  70. package/lib/typescript/src/ui/screens/sub/{BluetoothSubScreen.d.ts → sendPayment/BluetoothSubScreen.d.ts} +2 -11
  71. package/lib/typescript/src/ui/screens/sub/sendPayment/BluetoothSubScreen.d.ts.map +1 -0
  72. package/lib/typescript/src/ui/screens/sub/sendPayment/NFCSubScreen.d.ts +3 -0
  73. package/lib/typescript/src/ui/screens/sub/sendPayment/NFCSubScreen.d.ts.map +1 -0
  74. package/lib/typescript/src/ui/screens/sub/sendPayment/NQRSubScreen.d.ts +3 -0
  75. package/lib/typescript/src/ui/screens/sub/sendPayment/NQRSubScreen.d.ts.map +1 -0
  76. package/lib/typescript/src/ui/screens/sub/{ProximitySubScreen.d.ts → sendPayment/ProximitySubScreen.d.ts} +2 -10
  77. package/lib/typescript/src/ui/screens/sub/sendPayment/ProximitySubScreen.d.ts.map +1 -0
  78. package/lib/typescript/src/ui/screens/sub/sendPayment/TransferSubScreen.d.ts +3 -0
  79. package/lib/typescript/src/ui/screens/sub/sendPayment/TransferSubScreen.d.ts.map +1 -0
  80. package/package.json +1 -1
  81. package/src/FountainPayProvider.tsx +7 -1
  82. package/src/core/api/index.ts +34 -19
  83. package/src/core/types/index.ts +67 -13
  84. package/src/engine/BLEReceiverService.ts +2 -0
  85. package/src/engine/FPEngine.ts +29 -14
  86. package/src/engine/useIsForeground.ts +18 -0
  87. package/src/ui/components/AnimatedDots.tsx +81 -0
  88. package/src/ui/components/ConfirmScreen.tsx +421 -0
  89. package/src/ui/modals/FPPaymentRequestModal.tsx +7 -6
  90. package/src/ui/modals/FPShell.tsx +9 -9
  91. package/src/ui/screens/ReceiveScreen.tsx +266 -115
  92. package/src/ui/screens/SendScreen.tsx +141 -19
  93. package/src/ui/screens/styles.ts +101 -0
  94. package/src/ui/screens/sub/receivePayment/Nfc/index.tsx +418 -0
  95. package/src/ui/screens/sub/receivePayment/Qr/index.tsx +391 -0
  96. package/src/ui/screens/sub/receivePayment/Transfer/index.tsx +512 -0
  97. package/src/ui/screens/sub/{BluetoothSubScreen.tsx → sendPayment/BluetoothSubScreen.tsx} +27 -46
  98. package/src/ui/screens/sub/sendPayment/NFCSubScreen.tsx +302 -0
  99. package/src/ui/screens/sub/sendPayment/NQRSubScreen.tsx +490 -0
  100. package/src/ui/screens/sub/{ProximitySubScreen.tsx → sendPayment/ProximitySubScreen.tsx} +24 -44
  101. package/src/ui/screens/sub/sendPayment/TransferSubScreen.tsx +345 -0
  102. package/lib/module/ui/screens/sub/BluetoothSubScreen.js.map +0 -1
  103. package/lib/module/ui/screens/sub/NFCSubScreen.js +0 -164
  104. package/lib/module/ui/screens/sub/NFCSubScreen.js.map +0 -1
  105. package/lib/module/ui/screens/sub/NQRSubScreen.js +0 -131
  106. package/lib/module/ui/screens/sub/NQRSubScreen.js.map +0 -1
  107. package/lib/module/ui/screens/sub/ProximitySubScreen.js.map +0 -1
  108. package/lib/module/ui/screens/sub/TransferSubScreen.js +0 -353
  109. package/lib/module/ui/screens/sub/TransferSubScreen.js.map +0 -1
  110. package/lib/typescript/src/ui/screens/sub/BluetoothSubScreen.d.ts.map +0 -1
  111. package/lib/typescript/src/ui/screens/sub/NFCSubScreen.d.ts +0 -18
  112. package/lib/typescript/src/ui/screens/sub/NFCSubScreen.d.ts.map +0 -1
  113. package/lib/typescript/src/ui/screens/sub/NQRSubScreen.d.ts +0 -12
  114. package/lib/typescript/src/ui/screens/sub/NQRSubScreen.d.ts.map +0 -1
  115. package/lib/typescript/src/ui/screens/sub/ProximitySubScreen.d.ts.map +0 -1
  116. package/lib/typescript/src/ui/screens/sub/TransferSubScreen.d.ts +0 -11
  117. package/lib/typescript/src/ui/screens/sub/TransferSubScreen.d.ts.map +0 -1
  118. package/src/ui/screens/sub/NFCSubScreen.tsx +0 -86
  119. package/src/ui/screens/sub/NQRSubScreen.tsx +0 -62
  120. package/src/ui/screens/sub/TransferSubScreen.tsx +0 -147
@@ -0,0 +1,490 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { View, Text, StyleSheet, ActivityIndicator, Image, TouchableOpacity, Dimensions, Animated, Easing, Alert, Vibration, Linking } from 'react-native';
3
+ import { nqrAPI } from '../../../../core/api';
4
+ import { FPButton } from '../../../components/FPButton';
5
+ import { C, R, S, F, shadow } from '../../../theme';
6
+ import type { FPCurrency, FPError, Props } from '../../../../core/types';
7
+ import { Camera, useCameraDevice, useCameraDevices, useCodeScanner } from 'react-native-vision-camera';
8
+ import styled from 'styled-components/native';
9
+ import { launchImageLibrary } from 'react-native-image-picker';
10
+ import QRKit from 'react-native-qr-kit';
11
+ import Ionicons from 'react-native-vector-icons/Ionicons';
12
+ import { useIsForeground } from '../../../../engine/useIsForeground';
13
+
14
+ const { width, height } = Dimensions.get('window');
15
+
16
+ // Styled Components
17
+ const Container = styled(View)`
18
+ flex: 1;
19
+ background-color: #000000;
20
+ `;
21
+
22
+ const Header = styled(View)`
23
+ flex-direction: row;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ padding: 12px 16px;
27
+ position: absolute !important;
28
+ top: 40px;
29
+ left: 0;
30
+ right: 0;
31
+ z-index: 10;
32
+ elevation: 4;
33
+ shadow-color: #000;
34
+ shadow-offset: 0px 2px;
35
+ shadow-opacity: 0.1;
36
+ shadow-radius: 4px;
37
+ z-index: 9999999999 !important;
38
+ `;
39
+
40
+ const HeaderButton = styled(TouchableOpacity)`
41
+ padding: 8px;
42
+ background-color: #fff;
43
+ width: 40px;
44
+ height: 40px;
45
+ border-radius: 50%;
46
+ justify-content: center;
47
+ align-items: center;
48
+ `;
49
+
50
+ const HeaderCenter = styled(View)`
51
+ flex-direction: row;
52
+ align-items: center;
53
+ gap: 8px;
54
+ `;
55
+
56
+ const HeaderRight = styled(View)`
57
+ flex-direction: row;
58
+ align-items: center;
59
+ gap: 8px;
60
+ `;
61
+
62
+ const CameraContainer = styled(View)`
63
+ flex: 1;
64
+ `;
65
+
66
+ const StyledCamera = styled(Camera)`
67
+ flex: 1;
68
+ `;
69
+
70
+ const CameraPlaceholder = styled(View)`
71
+ flex: 1;
72
+ background-color: #1a1a1a;
73
+ justify-content: center;
74
+ align-items: center;
75
+ `;
76
+
77
+ const PlaceholderText = styled(Text)`
78
+ color: #ffffff;
79
+ font-size: 16px;
80
+ text-align: center;
81
+ padding: 20px;
82
+ `;
83
+
84
+ const PermissionButton = styled(TouchableOpacity)`
85
+ background-color: #10b981;
86
+ padding: 12px 24px;
87
+ border-radius: 12px;
88
+ margin-top: 16px;
89
+ `;
90
+
91
+ const PermissionButtonText = styled(Text)`
92
+ color: #ffffff;
93
+ font-size: 14px;
94
+ font-weight: 600;
95
+ `;
96
+
97
+ const ScanOverlay = styled(View)`
98
+ position: absolute;
99
+ top: 150px;
100
+ left: 0;
101
+ right: 0;
102
+ justify-content: center;
103
+ align-items: center;
104
+ `;
105
+
106
+ const ScanOverlays = styled(View)`
107
+ position: absolute;
108
+ top: 200px;
109
+ left: 0;
110
+ right: 0;
111
+ bottom: ${height * 0.5}px;
112
+ justify-content: center;
113
+ align-items: center;
114
+ `;
115
+ const ScanFrame = styled(View)`
116
+ width: ${width * 0.75}px;
117
+ height: ${width * 0.75}px;
118
+ position: relative;
119
+ `;
120
+
121
+ const CornerBorder = styled(View)<{ position: string }>`
122
+ position: absolute;
123
+ width: 40px;
124
+ height: 40px;
125
+ ${props => {
126
+ switch(props.position) {
127
+ case 'topLeft':
128
+ return 'top: 0; left: 0; border-top-width: 4px; border-left-width: 4px;';
129
+ case 'topRight':
130
+ return 'top: 0; right: 0; border-top-width: 4px; border-right-width: 4px;';
131
+ case 'bottomLeft':
132
+ return 'bottom: 0; left: 0; border-bottom-width: 4px; border-left-width: 4px;';
133
+ case 'bottomRight':
134
+ return 'bottom: 0; right: 0; border-bottom-width: 4px; border-right-width: 4px;';
135
+ default:
136
+ return '';
137
+ }
138
+ }}
139
+ border-color: #10b981;
140
+ `;
141
+
142
+ const ScanLineContainer = styled(View)`
143
+ width: 100%;
144
+ height: 100%;
145
+ overflow: hidden;
146
+ `;
147
+
148
+ const ScanLine = styled(View)`
149
+ width: 100%;
150
+ height: 2px;
151
+ background-color: #10b981;
152
+ shadow-color: #10b981;
153
+ shadow-offset: 0px 0px;
154
+ shadow-opacity: 0.8;
155
+ shadow-radius: 4px;
156
+ elevation: 5;
157
+ `;
158
+
159
+ const ScanInstructionText = styled(Text)`
160
+ color: #ffffff;
161
+ font-size: 16px;
162
+ font-weight: 600;
163
+ text-align: center;
164
+ margin-top: 24px;
165
+ text-shadow-color: rgba(0, 0, 0, 0.75);
166
+ text-shadow-offset: 0px 1px;
167
+ text-shadow-radius: 3px;
168
+ `;
169
+
170
+ const ScannedDataContainer = styled(View)`
171
+ position: absolute;
172
+ top: 100px;
173
+ left: 20px;
174
+ right: 20px;
175
+ background-color: rgba(16, 185, 129, 0.95);
176
+ padding: 16px;
177
+ border-radius: 12px;
178
+ z-index: 5;
179
+ `;
180
+
181
+ const ScannedDataText = styled(Text)`
182
+ color: #ffffff;
183
+ font-size: 14px;
184
+ font-weight: 600;
185
+ `;
186
+
187
+
188
+ export function NQRSubScreen({
189
+ amount,
190
+ currency,
191
+ onClose,
192
+ onProcessTransaction,
193
+ onError
194
+ }: Props) {
195
+ const [hasPermission, setHasPermission] = useState<boolean | null>(null);
196
+ const [scannedData, setScannedData] = useState<string | null>(null);
197
+ const [isScanning, setIsScanning] = useState(true);
198
+ const [torch, setTorch] = useState(false);
199
+ const [layoutReady, setLayoutReady] = useState(false);
200
+
201
+
202
+ const [isActive, setIsActive] = useState(false);
203
+ const isForeground = useIsForeground();
204
+
205
+
206
+ const lineAnim = React.useRef(new Animated.Value(0)).current;
207
+ const lineWidth = 2;
208
+
209
+
210
+ const device = useCameraDevice('back', {
211
+ physicalDevices: ['wide-angle-camera'],
212
+ });
213
+
214
+ useEffect(() => {
215
+ checkCameraPermission();
216
+
217
+ return () => {
218
+ setIsActive(false);
219
+ };
220
+ }, []);
221
+
222
+ useEffect(() => {
223
+ // if (scanned) {
224
+ Animated.loop(
225
+ Animated.sequence([
226
+ Animated.timing(lineAnim, {
227
+ toValue: qrSize - lineWidth, // Move to right edge
228
+ duration: 1500,
229
+ easing: Easing.inOut(Easing.linear),
230
+ useNativeDriver: true,
231
+ }),
232
+ Animated.timing(lineAnim, {
233
+ toValue: 0, // Move back to left
234
+ duration: 1500,
235
+ easing: Easing.inOut(Easing.linear),
236
+ useNativeDriver: true,
237
+ }),
238
+ ])
239
+ ).start();
240
+ // }
241
+ }, []);
242
+
243
+ useEffect(() => {
244
+ if (hasPermission && device && isForeground) {
245
+ const timer = setTimeout(() => {
246
+ setIsActive(true);
247
+ }, 500);
248
+
249
+ return () => clearTimeout(timer);
250
+ }
251
+
252
+ return undefined;
253
+ }, [hasPermission, device, isForeground]);
254
+
255
+ const checkCameraPermission = async () => {
256
+ try {
257
+ const permission = await Camera.getCameraPermissionStatus();
258
+ console.log('Current permission status:', permission);
259
+
260
+ if (permission === 'granted') {
261
+ setHasPermission(true);
262
+ } else if (permission === 'not-determined') {
263
+ const newPermission = await Camera.requestCameraPermission();
264
+ console.log('New permission status:', newPermission);
265
+ setHasPermission(newPermission === 'granted');
266
+ } else {
267
+ setHasPermission(false);
268
+ }
269
+ } catch (error) {
270
+ console.error('Permission check error:', error);
271
+ setHasPermission(false);
272
+ }
273
+ };
274
+
275
+ const requestPermission = async () => {
276
+ try {
277
+ const permission = await Camera.requestCameraPermission();
278
+ console.log('Requested permission:', permission);
279
+
280
+ if (permission === 'denied') {
281
+ Alert.alert(
282
+ 'Permission Required',
283
+ 'Camera permission is required to scan QR codes. Please enable it in settings.',
284
+ [
285
+ { text: 'Cancel', style: 'cancel' },
286
+ { text: 'Open Settings', onPress: () => Linking.openSettings() }
287
+ ]
288
+ );
289
+ setHasPermission(false);
290
+ } else if (permission === 'granted') {
291
+ setHasPermission(true);
292
+ }
293
+ } catch (error) {
294
+ console.error('Permission request error:', error);
295
+ setHasPermission(false);
296
+ }
297
+ };
298
+
299
+ const handleQrData=(data: any)=>{
300
+ console.log('QR Code Data:', data);
301
+ const payload={
302
+ accountNo: data?.accountNo,
303
+ bank: data?.bankName,
304
+ accountDetail: data?.accountName,
305
+ channel: 'qr',
306
+ amount: data?.amount,
307
+ remark: data?.description
308
+ }
309
+ console.log('Qr details:', payload);
310
+ onProcessTransaction?.(payload as any);
311
+ }
312
+
313
+ const scanQrFromGallery = async () => {
314
+ try {
315
+ // Open gallery
316
+ const result: any = await launchImageLibrary({
317
+ mediaType: 'photo',
318
+ quality: 1,
319
+ });
320
+
321
+ if (result.didCancel || !result.assets || !result.assets[0].uri) {
322
+ return;
323
+ }
324
+
325
+ const imageUri = result.assets[0].uri;
326
+ const barcodes: any = await QRKit.decodeQR(imageUri);
327
+ if (barcodes?.success) {
328
+ Vibration.vibrate(100);
329
+
330
+ await handleQrData(JSON.parse(barcodes?.data));
331
+ } else {
332
+ Alert.alert('Error:', barcodes?.message)
333
+ }
334
+ } catch (error) {
335
+ console.error('QR Scan Error:', error);
336
+ }
337
+ };
338
+
339
+
340
+ const codeScanner = useCodeScanner({
341
+ codeTypes: ['qr', 'ean-13'],
342
+ onCodeScanned: (codes: any) => {
343
+ if (isScanning && codes.length > 0) {
344
+ const qrData: any = codes[0].value;
345
+ setScannedData(qrData);
346
+ setIsScanning(false);
347
+
348
+ // Vibrate on scan (optional)
349
+ Vibration.vibrate(100);
350
+ console.log('Scanned QR Code:', JSON.parse(qrData));
351
+ // Show alert with scanned data
352
+ handleQrData(JSON.parse(qrData));
353
+ }
354
+ },
355
+ });
356
+
357
+ // useEffect(() => {
358
+ // setLoading(true);
359
+ // nqrAPI.generate({ amount: amount ? amount * 100 : undefined, currency })
360
+ // .then(setQr).catch(e => onError?.(e)).finally(() => setLoading(false));
361
+ // }, []);
362
+
363
+ const renderCamera = () => {
364
+ console.log('Render camera - hasPermission:', hasPermission, 'device:', !!device, 'isActive:', isActive);
365
+
366
+ if (hasPermission === null) {
367
+ return (
368
+ <CameraPlaceholder>
369
+ <PlaceholderText>Checking camera permission...</PlaceholderText>
370
+ </CameraPlaceholder>
371
+ );
372
+ }
373
+
374
+ if (!hasPermission) {
375
+ return (
376
+ <CameraPlaceholder>
377
+ <PlaceholderText>
378
+ Camera permission is required to scan QR codes
379
+ </PlaceholderText>
380
+ <PermissionButton onPress={requestPermission}>
381
+ <PermissionButtonText>Grant Permission</PermissionButtonText>
382
+ </PermissionButton>
383
+ </CameraPlaceholder>
384
+ );
385
+ }
386
+
387
+ if (!device) {
388
+ return (
389
+ <CameraPlaceholder>
390
+ <PlaceholderText>Loading camera...</PlaceholderText>
391
+ </CameraPlaceholder>
392
+ );
393
+ }
394
+
395
+ if (!isActive) {
396
+ return (
397
+ <CameraPlaceholder>
398
+ <PlaceholderText>Initializing camera...</PlaceholderText>
399
+ </CameraPlaceholder>
400
+ );
401
+ }
402
+
403
+ return (
404
+ <StyledCamera
405
+ device={device}
406
+ isActive={true}
407
+ codeScanner={codeScanner}
408
+ enableZoomGesture={true}
409
+ torch={torch ? 'on' : 'off'}
410
+ />
411
+ );
412
+ };
413
+
414
+ return (
415
+ <Container>
416
+
417
+ {/* Header */}
418
+ <Header>
419
+ <HeaderButton onPress={onClose}>
420
+ <Ionicons name="close" size={24} />
421
+ </HeaderButton>
422
+
423
+ <HeaderCenter>
424
+ <Text style={{ fontWeight: 'bold', fontSize: 16, color:'#FFF' }}>QRIS</Text>
425
+ <Text style={{ fontSize: 10, color: '#FFF' }}>QB Code Standar</Text>
426
+ </HeaderCenter>
427
+
428
+ <HeaderRight>
429
+ <HeaderButton onPress={scanQrFromGallery}>
430
+ <Ionicons name="image-outline" size={22} />
431
+ </HeaderButton>
432
+ <HeaderButton>
433
+ <Ionicons name="settings-outline" size={22} />
434
+ </HeaderButton>
435
+ </HeaderRight>
436
+ </Header>
437
+
438
+ <CameraContainer onLayout={() => setLayoutReady(true)}>
439
+ {renderCamera()}
440
+
441
+ {/* Scan Overlay*/}
442
+ {hasPermission && device && isActive && (
443
+ <ScanOverlay pointerEvents="none">
444
+ <ScanFrame>
445
+ <CornerBorder position="topLeft" />
446
+ <CornerBorder position="topRight" />
447
+ <CornerBorder position="bottomLeft" />
448
+ <CornerBorder position="bottomRight" />
449
+ <Animated.View
450
+ style={[
451
+ styles.scanningLine,
452
+ {
453
+ transform: [{ translateX: lineAnim }],
454
+ },
455
+ ]}
456
+ />
457
+ </ScanFrame>
458
+ <ScanInstructionText>
459
+ {isScanning ? 'Point camera at QR code' : 'QR Code Detected!'}
460
+ </ScanInstructionText>
461
+ </ScanOverlay>
462
+ )}
463
+
464
+ {/* Scanned Data Display */}
465
+ {scannedData && (
466
+ <ScannedDataContainer>
467
+ <ScannedDataText>Scanned: {scannedData}</ScannedDataText>
468
+ </ScannedDataContainer>
469
+ )}
470
+ </CameraContainer>
471
+ {/* <StatusBarBlurBackground /> */}
472
+
473
+
474
+ </Container>
475
+ );
476
+ }
477
+
478
+ const qrSize = width * 0.75;
479
+ const styles = StyleSheet.create({
480
+
481
+ scanningLine: {
482
+ height: qrSize,
483
+ width: 2,
484
+ backgroundColor: '#10b981',
485
+ position: 'absolute',
486
+ top: 0,
487
+ left: 0,
488
+ borderRadius: 1,
489
+ },
490
+ });
@@ -1,16 +1,13 @@
1
1
  import React, { useState, useEffect, useRef } from 'react';
2
2
  import styled from 'styled-components/native';
3
3
  import Svg, { Path } from "react-native-svg";
4
- import { View, Text, FlatList, TouchableOpacity, StyleSheet, ActivityIndicator, TextInput, Animated, Easing } from 'react-native';
5
- import { proximityAPI, transferAPI } from '../../../core/api';
6
- import { FPButton } from '../../components/FPButton';
7
- import { C, R, S, F, shadow } from '../../theme';
8
- import type { FPCurrency, FPProximityPeer, FPError, FPTransaction } from '../../../core/types';
9
- import { FPEngine } from '../../../engine/FPEngine';
10
- import NearbyUsersService from '../../../engine/NearbyUsersService';
11
- import { PulseAnimation } from '../../components/PulseAnimation';
4
+ import { View, Text, TouchableOpacity, StyleSheet, Animated, Easing } from 'react-native';
5
+ import { C, R, S, F, shadow } from '../../../theme';
6
+ import type { FPError, Props, FPSendPaymentRequest } from '../../../../core/types';
7
+ import NearbyUsersService from '../../../../engine/NearbyUsersService';
8
+ import { PulseAnimation } from '../../../components/PulseAnimation';
12
9
  import Ionicons from 'react-native-vector-icons/Ionicons';
13
- import LoadingAnimation from '../../components/LoadingAnimation';
10
+ import LoadingAnimation from '../../../components/LoadingAnimation';
14
11
 
15
12
 
16
13
  const Container = styled(View)`
@@ -221,15 +218,13 @@ const ProximityIcon =({ color = "#111", size = 22 })=>(
221
218
  </Svg>
222
219
  )
223
220
 
224
- interface Props {
225
- amount: number;
226
- currency: FPCurrency;
227
- onDone: () => void;
228
- onSuccess?: (tx: FPTransaction) => void;
229
- onError?: (err: FPError) => void;
230
- }
231
221
 
232
- export function ProximitySubScreen({ amount, currency, onDone, onSuccess, onError }: Props) {
222
+ export function ProximitySubScreen({
223
+ amount,
224
+ currency,
225
+ onClose,
226
+ onProcessTransaction,
227
+ onError }: Props) {
233
228
 
234
229
  const [nearbyUsers, setNearbyUsers] = useState([]);
235
230
  const [loading, setLoading] = useState(false);
@@ -250,14 +245,17 @@ export function ProximitySubScreen({ amount, currency, onDone, onSuccess, onErro
250
245
 
251
246
  const handlePayment = async (selected: any) => {
252
247
  if (!selected) return;
253
-
254
- setLoading(true);
255
248
  try {
256
- const result = await transferAPI.send({ accountNumber: selected.accountNumber, bankCode: selected.bankCode, amount: amount * 100, narration: 'Proximity payment' });
257
- onSuccess?.({ id: result.reference, reference: result.reference, type: 'debit', channel: 'proximity', amount: result.amount, currency: result.currency, status: result.status, recipient: { accountName: selected.displayName }, createdAt: result.createdAt });
258
- onDone();
249
+ const payload: FPSendPaymentRequest = {
250
+ amount: amount,
251
+ currency: currency,
252
+ channel: 'proximity',
253
+ narration: `Send ${currency}${amount} to ${selected.displayName}`,
254
+ recipient: selected
255
+ }
256
+
257
+ onProcessTransaction?.(payload);
259
258
  } catch (e) { onError?.(e as FPError); }
260
- finally { setLoading(false); }
261
259
  };
262
260
 
263
261
  useEffect(() => {
@@ -295,11 +293,10 @@ export function ProximitySubScreen({ amount, currency, onDone, onSuccess, onErro
295
293
 
296
294
  return (
297
295
  <Container>
298
- {/* Status Bar */}
299
- {loading && (
300
- <LoadingAnimation />
301
- )}
302
296
  <Header>
297
+ <HeaderButton onPress={onClose}>
298
+ <Ionicons name="close" size={24} />
299
+ </HeaderButton>
303
300
  <HeaderCenter>
304
301
  <Text style={{ fontWeight: 'bold', fontSize: 16, color:'#FFF' }}>Proximity</Text>
305
302
  <Text style={{ fontSize: 10, color: '#FFF' }}>Transfer</Text>
@@ -373,20 +370,3 @@ export function ProximitySubScreen({ amount, currency, onDone, onSuccess, onErro
373
370
  </Container>
374
371
  );
375
372
  }
376
-
377
- const st = StyleSheet.create({
378
- wrap: { flex: 1, padding: S.lg },
379
- back: { color: C.brand, fontSize: F.md, fontWeight: '600', marginBottom: S.md },
380
- title: { fontSize: F.xl, fontWeight: '800', color: C.ink, marginBottom: S.lg },
381
- peerCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: C.surface, borderRadius: R.lg, padding: S.md, borderWidth: 2, borderColor: 'transparent' },
382
- peerSelected: { borderColor: C.brand, backgroundColor: '#EEF4FF' },
383
- avatar: { width: 44, height: 44, borderRadius: 22, backgroundColor: C.brand, justifyContent: 'center', alignItems: 'center', marginRight: S.md },
384
- avatarText: { color: C.white, fontWeight: '800', fontSize: F.lg },
385
- peerName: { fontSize: F.md, fontWeight: '700', color: C.ink },
386
- peerBank: { fontSize: F.sm, color: C.muted },
387
- dist: { fontSize: F.xs, color: C.brand },
388
- check: { color: C.brand, fontWeight: '800' },
389
- empty: { textAlign: 'center', color: C.muted, padding: S.xl },
390
- footer: { backgroundColor: C.surface, borderRadius: R.lg, padding: S.lg, marginTop: S.md },
391
- footerText: { fontSize: F.md, color: C.ink, fontWeight: '600', marginBottom: S.md },
392
- });