react-native-mytatva-rn-sdk 1.2.98 → 1.2.99

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.
@@ -14,6 +14,8 @@ import android.view.Gravity
14
14
  import android.view.View
15
15
  import android.view.ViewGroup
16
16
  import android.view.Window
17
+ import android.widget.Toast
18
+ import ist.com.sdk.AlgorithmTools
17
19
  import androidx.activity.OnBackPressedCallback
18
20
  import androidx.activity.enableEdgeToEdge
19
21
  import androidx.core.app.ActivityCompat
@@ -21,10 +23,13 @@ import androidx.core.content.ContextCompat
21
23
  import androidx.core.graphics.drawable.toDrawable
22
24
  import androidx.core.view.ViewCompat
23
25
  import androidx.core.view.WindowInsetsCompat
26
+ import cgmblelib.Enum.enumUnBind
27
+ import cgmblelib.ble.gattcallback.GattTool
24
28
  import cgmblelib.ble.gattcallback.ManageGattCallback
25
29
  import cgmblelib.ble.scancallback.ManageScanCallback
26
30
  import cgmblelib.custom.livedata.scan.PocDeviceAndRssi
27
31
  import cgmblelib.database.entity.PocDevice
32
+ import cgmblelib.database.repository.RepositoryDevice
28
33
  import cgmblelib.utils.SharedPreferencesLibraryUtil
29
34
  import com.bumptech.glide.Glide
30
35
  import com.facebook.react.bridge.Arguments
@@ -59,6 +64,8 @@ class ConnectSensorActivity : BaseBleActivity() {
59
64
  var transmitterDeviceInfo: PocDeviceAndRssi? = null
60
65
  private var isForReconnect: Boolean = false
61
66
  private val reactContext = CgmTrackyLibModule.mReactContext
67
+ private var isManualInputVisible: Boolean = false
68
+ private var suppressUnbindErrorForSensorRebind = false
62
69
 
63
70
  override fun onCreate(savedInstanceState: Bundle?) {
64
71
  super.onCreate(savedInstanceState)
@@ -133,6 +140,128 @@ class ConnectSensorActivity : BaseBleActivity() {
133
140
  binding.commonButton.btnProceed.alpha = 0.5f
134
141
  binding.clPermission.visibility = View.VISIBLE
135
142
  }
143
+
144
+ binding.tvEnterIdManually.setOnClickListener {
145
+ toggleManualInput()
146
+ }
147
+
148
+ binding.btnConnectManual.setOnClickListener {
149
+ connectSensorManually()
150
+ }
151
+ }
152
+
153
+ private fun toggleManualInput() {
154
+ isManualInputVisible = !isManualInputVisible
155
+ if (isManualInputVisible) {
156
+ binding.clPermission.visibility = View.GONE
157
+ binding.clManualInput.visibility = View.VISIBLE
158
+ binding.commonButton.root.visibility = View.GONE
159
+ binding.tvCantScanQr.text = "Scan the QR."
160
+ binding.tvEnterIdManually.text = "Scan QR"
161
+ } else {
162
+ binding.clManualInput.visibility = View.GONE
163
+ binding.clPermission.visibility = View.VISIBLE
164
+ binding.commonButton.root.visibility = View.VISIBLE
165
+ binding.tvCantScanQr.setText(R.string.txt_cant_scan_sensor_qr)
166
+ binding.tvEnterIdManually.setText(R.string.txt_enter_id_manually)
167
+ }
168
+ }
169
+
170
+ private fun connectSensorManually() {
171
+ val sensorId = binding.etSensorId.text.toString().trim().uppercase()
172
+ if (sensorId.isEmpty()) {
173
+ Toast.makeText(this, "Please enter Sensor ID", Toast.LENGTH_SHORT).show()
174
+ return
175
+ }
176
+
177
+ val device = transmitterDeviceInfo?.pocDevice
178
+ if (device == null) {
179
+ Toast.makeText(this, "Transmitter not found. Please restart.", Toast.LENGTH_SHORT).show()
180
+ return
181
+ }
182
+
183
+ val transmitterName = device.name
184
+ if (transmitterName.isNullOrEmpty()) {
185
+ Toast.makeText(this, "Transmitter not found. Please restart.", Toast.LENGTH_SHORT).show()
186
+ return
187
+ }
188
+
189
+ val decoded = try {
190
+ AlgorithmTools.getInstance().decodeCT(sensorId.toCharArray())
191
+ } catch (e: Exception) {
192
+ Log.e("ConnectSensor", "Manual entry decode failed: ${e.message}")
193
+ Toast.makeText(this, "Invalid Sensor ID format", Toast.LENGTH_SHORT).show()
194
+ return
195
+ }
196
+
197
+ // Match iOS manual entry: decoded CT code must yield usable algorithm coefficients.
198
+ if (decoded.k <= 0f && decoded.r <= 0f) {
199
+ Toast.makeText(this, getString(R.string.verification_fail), Toast.LENGTH_SHORT).show()
200
+ return
201
+ }
202
+
203
+ val prefsHelper = SharedPreferencesLibraryUtil(this)
204
+ prefsHelper.setQRInformation(
205
+ transmitterName,
206
+ sensorId,
207
+ decoded.k,
208
+ decoded.r,
209
+ sensorId
210
+ )
211
+
212
+ // Match QRActivity.showMessage: verify sensor/transmitter pairing before BLE bind.
213
+ val qrInformation = prefsHelper.qrInformation
214
+ val isEffective = qrInformation?.isEffective(transmitterName, decoded.r, decoded.k) ?: false
215
+ safeLogModuleEvent {
216
+ logModuleEvent(
217
+ where = "cgmJourney.cgm_manual_sensor_is_effective",
218
+ message = "CGM journey | screen=${javaClass.simpleName} | manualEntry isEffective=$isEffective",
219
+ level = SentryLogLevel.INFO,
220
+ jsonPayload = sensorId,
221
+ attributes = mapOf(
222
+ "journey_step" to "cgm_manual_sensor_is_effective",
223
+ "screen" to javaClass.simpleName,
224
+ "is_effective" to isEffective.toString(),
225
+ "transmitter_name" to transmitterName,
226
+ "sensor_id" to sensorId,
227
+ ),
228
+ logsOnly = true,
229
+ )
230
+ }
231
+ if (!isEffective || qrInformation == null) {
232
+ Toast.makeText(this, getString(R.string.verification_fail), Toast.LENGTH_SHORT).show()
233
+ return
234
+ }
235
+
236
+ proceedToSensorBind(device)
237
+ }
238
+
239
+ private fun proceedToSensorBind(device: PocDevice) {
240
+ val lastConnectCgm = CgmTrackyLibModule.lastConnectCgm
241
+ if (lastConnectCgm.equals("Diasens", ignoreCase = true)) {
242
+ validateDiasensSensor(device)
243
+ } else {
244
+ bind(device)
245
+ }
246
+ }
247
+
248
+ /**
249
+ * If the transmitter is already bound (e.g. from a prior failed manual attempt), the BLE stack
250
+ * takes the reset shortcut and skips check/init that programs K/R for the new sensor code.
251
+ * Force-clear the DB bind so the next connection runs the full sensor pairing flow.
252
+ */
253
+ private fun clearExistingBindIfNeeded() {
254
+ try {
255
+ val existing = RepositoryDevice.getInstance(this).getLatestDeviceIoThread() ?: return
256
+ if (!existing.isBound) {
257
+ return
258
+ }
259
+ suppressUnbindErrorForSensorRebind = true
260
+ GattTool.unBindInDatabaseIoThread(this, existing, enumUnBind.UNBIND_FORCE)
261
+ } catch (e: Exception) {
262
+ suppressUnbindErrorForSensorRebind = false
263
+ Log.w("ConnectSensor", "clearExistingBindIfNeeded: ${e.message}")
264
+ }
136
265
  }
137
266
 
138
267
  fun manageErrorState(showError: Boolean, errorStatus: String) {
@@ -185,10 +314,14 @@ class ConnectSensorActivity : BaseBleActivity() {
185
314
  binding.btnRetry.btnProceed.setOnClickListener {
186
315
  sendDataToRN("", "cgm_retry_connect_sensor_clicked")
187
316
 
188
- transmitterDeviceInfo?.let {
189
- QRActivity.startQR(
190
- this, it.pocDevice
191
- )
317
+ if (isManualInputVisible) {
318
+ manageErrorState(false, "")
319
+ } else {
320
+ transmitterDeviceInfo?.let {
321
+ QRActivity.startQR(
322
+ this, it.pocDevice
323
+ )
324
+ }
192
325
  }
193
326
  }
194
327
 
@@ -533,8 +666,12 @@ class ConnectSensorActivity : BaseBleActivity() {
533
666
  binding.btnRetry.btnProceed.setOnClickListener {
534
667
  sendDataToRN("", "cgm_retry_connect_sensor_clicked")
535
668
 
536
- transmitterDeviceInfo?.let {
537
- QRActivity.startQR(this, it.pocDevice)
669
+ if (isManualInputVisible) {
670
+ manageErrorState(false, "")
671
+ } else {
672
+ transmitterDeviceInfo?.let {
673
+ QRActivity.startQR(this, it.pocDevice)
674
+ }
538
675
  }
539
676
  }
540
677
 
@@ -554,6 +691,7 @@ class ConnectSensorActivity : BaseBleActivity() {
554
691
  private fun bind(device: PocDevice) {
555
692
  try {
556
693
  if (device.address != null) {
694
+ clearExistingBindIfNeeded()
557
695
  ManageGattCallback.getInstance().ConnectViaAddress(device.address)
558
696
  ManageScanCallback.getInstance().stopLeScan()
559
697
  ProgressManagement.getInstance()
@@ -686,12 +824,20 @@ class ConnectSensorActivity : BaseBleActivity() {
686
824
 
687
825
  override fun unbindReset() {
688
826
  super.unbindReset()
827
+ if (suppressUnbindErrorForSensorRebind) {
828
+ suppressUnbindErrorForSensorRebind = false
829
+ return
830
+ }
689
831
  manageErrorState(true, "Unbinded")
690
832
 
691
833
  }
692
834
 
693
835
  override fun unbindCommand() {
694
836
  super.unbindCommand()
837
+ if (suppressUnbindErrorForSensorRebind) {
838
+ suppressUnbindErrorForSensorRebind = false
839
+ return
840
+ }
695
841
  manageErrorState(true, "Unbinded")
696
842
 
697
843
  }
@@ -246,7 +246,11 @@
246
246
  app:layout_constraintStart_toStartOf="parent"
247
247
  app:layout_constraintTop_toBottomOf="@id/transmitterImages">
248
248
 
249
- <!-- Searching State -->
249
+ <LinearLayout
250
+ android:layout_width="match_parent"
251
+ android:layout_height="wrap_content"
252
+ android:orientation="vertical">
253
+
250
254
  <androidx.constraintlayout.widget.ConstraintLayout
251
255
  android:id="@+id/clPermission"
252
256
  android:layout_width="match_parent"
@@ -289,7 +293,6 @@
289
293
  app:layout_constraintTop_toBottomOf="@+id/tvDesc"
290
294
  app:layout_constraintVertical_chainStyle="packed" />
291
295
 
292
-
293
296
  <androidx.appcompat.widget.AppCompatImageView
294
297
  android:id="@+id/ivProceedArrow"
295
298
  android:layout_width="18dp"
@@ -303,9 +306,121 @@
303
306
  app:layout_constraintTop_toTopOf="@+id/tvPermission"
304
307
  app:tint="@color/color_green" />
305
308
 
309
+ </androidx.constraintlayout.widget.ConstraintLayout>
310
+
311
+ <androidx.constraintlayout.widget.ConstraintLayout
312
+ android:id="@+id/clManualInput"
313
+ android:layout_width="match_parent"
314
+ android:layout_height="wrap_content"
315
+ android:layout_marginTop="12dp"
316
+ android:background="@drawable/bg_dark_black"
317
+ android:paddingHorizontal="20dp"
318
+ android:paddingVertical="20dp"
319
+ android:visibility="gone">
320
+
321
+ <EditText
322
+ android:id="@+id/etSensorId"
323
+ android:layout_width="0dp"
324
+ android:layout_height="52dp"
325
+ android:background="@drawable/bg_white_corner_10"
326
+ android:fontFamily="@font/roboto_regular"
327
+ android:hint="Enter Sensor ID"
328
+ android:importantForAutofill="no"
329
+ android:inputType="textCapCharacters"
330
+ android:paddingHorizontal="14dp"
331
+ android:textColor="#101828"
332
+ android:textColorHint="#98A2B3"
333
+ android:textSize="14sp"
334
+ app:layout_constraintEnd_toEndOf="parent"
335
+ app:layout_constraintStart_toStartOf="parent"
336
+ app:layout_constraintTop_toTopOf="parent" />
337
+
338
+ <androidx.cardview.widget.CardView
339
+ android:id="@+id/btnConnectManual"
340
+ android:layout_width="0dp"
341
+ android:layout_height="54dp"
342
+ android:layout_marginTop="12dp"
343
+ app:cardBackgroundColor="#2A805A"
344
+ app:cardCornerRadius="12dp"
345
+ app:cardElevation="0dp"
346
+ app:layout_constraintEnd_toEndOf="parent"
347
+ app:layout_constraintStart_toStartOf="parent"
348
+ app:layout_constraintTop_toBottomOf="@+id/etSensorId">
349
+
350
+ <androidx.constraintlayout.widget.ConstraintLayout
351
+ android:layout_width="match_parent"
352
+ android:layout_height="match_parent"
353
+ android:background="@drawable/bg_green_border_20">
354
+
355
+ <TextView
356
+ android:id="@+id/tvConnectManual"
357
+ android:layout_width="wrap_content"
358
+ android:layout_height="wrap_content"
359
+ android:fontFamily="@font/roboto_semibold"
360
+ android:text="Connect"
361
+ android:textColor="#FFFFFF"
362
+ android:textSize="16sp"
363
+ app:layout_constraintBottom_toBottomOf="parent"
364
+ app:layout_constraintEnd_toEndOf="parent"
365
+ app:layout_constraintStart_toStartOf="parent"
366
+ app:layout_constraintTop_toTopOf="parent" />
367
+
368
+ </androidx.constraintlayout.widget.ConstraintLayout>
369
+
370
+ </androidx.cardview.widget.CardView>
371
+
372
+ <TextView
373
+ android:id="@+id/tvManualEntryHint"
374
+ android:layout_width="0dp"
375
+ android:layout_height="wrap_content"
376
+ android:layout_marginTop="10dp"
377
+ android:fontFamily="@font/roboto_regular"
378
+ android:gravity="center"
379
+ android:text="Scan the QR with mobile camera to get a sensor ID."
380
+ android:textAlignment="center"
381
+ android:textColor="#98A2B3"
382
+ android:textSize="12sp"
383
+ app:layout_constraintBottom_toBottomOf="parent"
384
+ app:layout_constraintEnd_toEndOf="parent"
385
+ app:layout_constraintStart_toStartOf="parent"
386
+ app:layout_constraintTop_toBottomOf="@+id/btnConnectManual" />
306
387
 
307
388
  </androidx.constraintlayout.widget.ConstraintLayout>
308
389
 
390
+ <LinearLayout
391
+ android:id="@+id/clManualEntry"
392
+ android:layout_width="match_parent"
393
+ android:layout_height="wrap_content"
394
+ android:layout_marginTop="12dp"
395
+ android:background="@drawable/bg_manual_entry_bar"
396
+ android:gravity="center"
397
+ android:orientation="horizontal"
398
+ android:paddingHorizontal="16dp"
399
+ android:paddingVertical="14dp">
400
+
401
+ <TextView
402
+ android:id="@+id/tvCantScanQr"
403
+ android:layout_width="wrap_content"
404
+ android:layout_height="wrap_content"
405
+ android:fontFamily="@font/roboto_regular"
406
+ android:text="@string/txt_cant_scan_sensor_qr"
407
+ android:textColor="#2D3282"
408
+ android:textSize="14sp" />
409
+
410
+ <TextView
411
+ android:id="@+id/tvEnterIdManually"
412
+ android:layout_width="wrap_content"
413
+ android:layout_height="wrap_content"
414
+ android:layout_marginStart="4dp"
415
+ android:fontFamily="@font/roboto_bold"
416
+ android:text="@string/txt_enter_id_manually"
417
+ android:textColor="@color/color_green"
418
+ android:textSize="14sp" />
419
+
420
+ </LinearLayout>
421
+
422
+ </LinearLayout>
423
+
309
424
  </FrameLayout>
310
425
 
311
426
  </androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,4 +1,4 @@
1
- //
1
+ ​//
2
2
  // KLTBluetoothManager.m
3
3
  // KaiLiTe
4
4
  //
@@ -28,6 +28,8 @@
28
28
  // 蓝牙管理状态
29
29
  @property (nonatomic, assign) CBManagerState centralManagerState;
30
30
 
31
+ - (BOOL)klt_safeWriteData:(NSData *)data withType:(CBCharacteristicWriteType)type;
32
+
31
33
  @end
32
34
 
33
35
  @implementation KLTBluetoothManager
@@ -71,6 +73,19 @@
71
73
  return self;
72
74
  }
73
75
 
76
+ /// NSTimer must not be invalidated synchronously from its own fired callback — that
77
+ /// can block the main thread for seconds (Sentry AppHang in -[KLTBluetoothManager endScan]).
78
+ - (void)invalidateConnectTimerSafely {
79
+ NSTimer *timer = _connectTimer;
80
+ _connectTimer = nil;
81
+ if (!timer) {
82
+ return;
83
+ }
84
+ dispatch_async(dispatch_get_main_queue(), ^{
85
+ [timer invalidate];
86
+ });
87
+ }
88
+
74
89
  + (KLTBluetoothManager *)sharedManager {
75
90
  static KLTBluetoothManager *bluetooth;
76
91
  static dispatch_once_t onceToken;
@@ -81,6 +96,21 @@
81
96
  return bluetooth;
82
97
  }
83
98
 
99
+ - (BOOL)klt_safeWriteData:(NSData *)data withType:(CBCharacteristicWriteType)type {
100
+ if (!data || data.length == 0) {
101
+ return NO;
102
+ }
103
+
104
+ CBPeripheral *peripheral = self.connectedPeripheral;
105
+ CBCharacteristic *characteristic = self.writeReadCharacteristic;
106
+ if (!peripheral || peripheral.state != CBPeripheralStateConnected || !characteristic) {
107
+ KLTLog(@"BLE write skipped - peripheral or write characteristic unavailable");
108
+ return NO;
109
+ }
110
+
111
+ [peripheral writeValue:data forCharacteristic:characteristic type:type];
112
+ return YES;
113
+ }
84
114
 
85
115
  #pragma mark - cbcenteralmanager delegate
86
116
 
@@ -476,7 +506,8 @@
476
506
  Device *currentDevice = self.currentDevice;
477
507
  if (KLTLocalSettingManager.shareInstance.canConnectOtherDevice) {
478
508
  // 直接开始自检协议
479
- [self.connectedPeripheral writeValue:[ProtocalTools check_request:self.eDevice] forCharacteristic:self.writeReadCharacteristic type:CBCharacteristicWriteWithoutResponse];
509
+ [self klt_safeWriteData:[ProtocalTools check_request:self.eDevice]
510
+ withType:CBCharacteristicWriteWithoutResponse];
480
511
  } else {
481
512
  // 二次重连 0x22快传批量数据
482
513
  NSInteger maxGlucoseId = [[KLTDatabaseHandler shared] getLatestAndMaxGlucoseIdOfDevice:currentDevice];
@@ -515,7 +546,9 @@
515
546
  }
516
547
 
517
548
  NSData *data = [ProtocalTools pullGlucose_series_request:glucoseId + 1 andNumber:pullNums andEDevice:self.eDevice];
518
- [self.connectedPeripheral writeValue:data forCharacteristic:self.writeReadCharacteristic type:CBCharacteristicWriteWithoutResponse];
549
+ if (![self klt_safeWriteData:data withType:CBCharacteristicWriteWithoutResponse]) {
550
+ return;
551
+ }
519
552
  self.status = BluetoothManagerStatusResendStart;
520
553
  self.isInResend = YES;
521
554
  }
@@ -532,7 +565,8 @@
532
565
  [Notification_Center postNotificationName:@"DidHandleResendData" object:nil];
533
566
 
534
567
  // CT3/CT4发送重置协议这条命令0x11,结束重传状态
535
- [self.connectedPeripheral writeValue:[ProtocalTools reset_request:self.eDevice] forCharacteristic:self.writeReadCharacteristic type:CBCharacteristicWriteWithoutResponse];
568
+ [self klt_safeWriteData:[ProtocalTools reset_request:self.eDevice]
569
+ withType:CBCharacteristicWriteWithoutResponse];
536
570
  } else {
537
571
  for (Glucose *glucose in glucoseList) {
538
572
  ReceiveData *receive = [self handleCommonReceiveGluData:glucose commandText:@"22"];
@@ -553,7 +587,7 @@
553
587
  pullNums = remain_points;
554
588
  }
555
589
  NSData *data = [ProtocalTools pullGlucose_series_request:maxGlucoseId + 1 andNumber:pullNums andEDevice:self.eDevice];
556
- [self.connectedPeripheral writeValue:data forCharacteristic:self.writeReadCharacteristic type:CBCharacteristicWriteWithoutResponse];
590
+ [self klt_safeWriteData:data withType:CBCharacteristicWriteWithoutResponse];
557
591
  }
558
592
  }
559
593
  }
@@ -595,8 +629,7 @@
595
629
  [self.peripherals removeAllObjects];
596
630
  self.connectedPeripheral = nil;
597
631
 
598
- [_connectTimer invalidate];
599
- _connectTimer = nil;
632
+ [self invalidateConnectTimerSafely];
600
633
  [_rescanTimer invalidate];
601
634
  _rescanTimer = nil;
602
635
 
@@ -610,6 +643,14 @@
610
643
  #pragma mark - other method
611
644
 
612
645
  - (void)endScan {
646
+ // endScan is the _connectTimer target. Defer to the next run-loop turn so we never
647
+ // invalidate the firing timer or run stopScan/KVO inside the timer callback.
648
+ dispatch_async(dispatch_get_main_queue(), ^{
649
+ [self performEndScanAfterTimer];
650
+ });
651
+ }
652
+
653
+ - (void)performEndScanAfterTimer {
613
654
  // 20s后扫描设备超时未连上再次尝试连接
614
655
  if (self.currentDevice && BluetoothManagerStatusScanStart == self.status) {
615
656
  for (CBPeripheral *per in self.peripherals) {
@@ -620,7 +661,7 @@
620
661
  }
621
662
  }
622
663
  }
623
-
664
+
624
665
  [self stopScan];
625
666
  self.status = BluetoothManagerStatusTimeOut;
626
667
  }
@@ -630,8 +671,7 @@
630
671
  self.status = BluetoothManagerStatusScanStop;
631
672
  [self.centralManager stopScan];
632
673
 
633
- [_connectTimer invalidate];
634
- _connectTimer = nil;
674
+ [self invalidateConnectTimerSafely];
635
675
  }
636
676
 
637
677
  // 开始扫描
@@ -678,23 +718,38 @@
678
718
  //}
679
719
 
680
720
  - (void)startScan {
721
+ // Defer to the next main-queue run-loop turn so scanForPeripheralsWithServices is
722
+ // never invoked synchronously from an NSTimer callback (Sentry AppHang on iOS).
723
+ // CBCentralManager is bound to the main queue — this does not move BLE off main.
724
+ dispatch_async(dispatch_get_main_queue(), ^{
725
+ [self performStartScan];
726
+ });
727
+ }
728
+
729
+ - (void)performStartScan {
730
+ if (self.status == BluetoothManagerStatusScanStart) {
731
+ KLTLog(@"startScan skipped — scan already in progress");
732
+ return;
733
+ }
734
+
681
735
  self.connectedPeripheral = nil;
682
736
  [self.peripherals removeAllObjects];
683
-
737
+
684
738
  if (self.centralManager.state != CBManagerStatePoweredOn) {
685
739
  NSLog(@"Bluetooth is not powered on. Aborting scan.");
686
740
  // Do not change status here – just wait for BluetoothEnable notification
687
741
  // The UI layer listens for BluetoothEnable and will start scan once BLE is ready.
688
742
  return;
689
743
  }
690
-
744
+
691
745
  NSLog(@"Allen manager start scan %d", __LINE__);
692
746
  self.status = BluetoothManagerStatusScanStart;
693
747
  self.isBgForceConnectDevice = NO;
694
-
748
+
695
749
  NSDictionary *scanOptions = @{ CBCentralManagerScanOptionAllowDuplicatesKey: @(YES) };
696
750
  [self.centralManager scanForPeripheralsWithServices:nil options:scanOptions];
697
-
751
+
752
+ [self invalidateConnectTimerSafely];
698
753
  _connectTimer = [NSTimer scheduledTimerWithTimeInterval:20.0
699
754
  target:self
700
755
  selector:@selector(endScan)
@@ -7,58 +7,191 @@
7
7
 
8
8
  import UIKit
9
9
  import AVFoundation
10
+
10
11
  class ImageTVC: UITableViewCell {
11
12
 
12
13
  @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
13
14
  @IBOutlet weak var displayImageView: UIImageView!
14
15
  @IBOutlet weak var topConstraint: NSLayoutConstraint!
15
-
16
+
16
17
  @IBOutlet var gifView: UIImageView!
17
18
  @IBOutlet weak var cameraView: CustomView!
18
19
  @IBOutlet weak var cameraTitleLabel: UILabel!
19
20
  @IBOutlet weak var cameraDescLabel: UILabel!
20
-
21
+
21
22
  @IBOutlet var ic_green_arrow: UIImageView!
22
23
  var scannerView: QRScannerView!
24
+
25
+ var onConnectTapped: ((String) -> Void)?
26
+ var onTextFieldDidBeginEditing: (() -> Void)?
27
+
28
+ private var manualEntryView: UIView?
29
+ private var sensorIdTextField: UITextField?
30
+ private var isManualEntryVisible = false
31
+
32
+ var activeSensorIdTextField: UITextField? {
33
+ isManualEntryVisible ? sensorIdTextField : nil
34
+ }
35
+
23
36
  override func awakeFromNib() {
24
37
  super.awakeFromNib()
25
38
  gifView.isHidden = true
26
39
  ic_green_arrow.image = loadImage(named: "ic_green_arrow")
27
- // Initialization code
28
40
  cameraTitleLabel.text = "GoodFlip requires camera permission to scan the QR and connect with the sensor"
29
-
30
41
  cameraTitleLabel.font = FontManager.font(ofSize: 14, weight: .medium)
31
-
32
42
  cameraTitleLabel.textColor = .white
33
-
34
- let first = "Provide Camera Permissions"
35
43
 
44
+ let first = "Provide Camera Permissions"
36
45
  let attributedString = NSMutableAttributedString(string: first, attributes: [
37
46
  .font: FontManager.font(ofSize: 16, weight: .semibold),
38
47
  .foregroundColor: CustomColor.shared.lightGreen,
39
48
  .underlineStyle: NSUnderlineStyle.single.rawValue,
40
49
  ])
41
-
42
50
  cameraDescLabel.attributedText = attributedString
51
+
52
+ setupManualEntryView()
43
53
  }
44
54
 
45
55
  override func setSelected(_ selected: Bool, animated: Bool) {
46
56
  super.setSelected(selected, animated: animated)
57
+ }
47
58
 
48
- // Configure the view for the selected state
59
+ override func prepareForReuse() {
60
+ super.prepareForReuse()
61
+ onConnectTapped = nil
62
+ onTextFieldDidBeginEditing = nil
63
+ scannerView?.stopScanning()
64
+ scannerView?.removeFromSuperview()
65
+ scannerView = nil
66
+ manualEntryView?.isHidden = true
67
+ sensorIdTextField?.text = nil
68
+ isManualEntryVisible = false
49
69
  }
50
-
70
+
71
+ // MARK: - Manual Entry UI Setup
72
+
73
+ private func setupManualEntryView() {
74
+ let container = UIView()
75
+ container.translatesAutoresizingMaskIntoConstraints = false
76
+ container.backgroundColor = UIColor(red: 16/255, green: 24/255, blue: 40/255, alpha: 1)
77
+ container.layer.cornerRadius = 12
78
+ container.isHidden = true
79
+ contentView.addSubview(container)
80
+
81
+ let textField = UITextField()
82
+ textField.translatesAutoresizingMaskIntoConstraints = false
83
+ textField.placeholder = "Enter Sensor ID"
84
+ textField.autocapitalizationType = .allCharacters
85
+ textField.autocorrectionType = .no
86
+ textField.returnKeyType = .done
87
+ textField.font = FontManager.font(ofSize: 14, weight: .regular)
88
+ textField.textColor = UIColor(red: 16/255, green: 24/255, blue: 40/255, alpha: 1)
89
+ textField.backgroundColor = .white
90
+ textField.layer.cornerRadius = 8
91
+ textField.layer.masksToBounds = true
92
+ textField.delegate = self
93
+ let leftPad = UIView(frame: CGRect(x: 0, y: 0, width: 14, height: 1))
94
+ textField.leftView = leftPad
95
+ textField.leftViewMode = .always
96
+ textField.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 14, height: 1))
97
+ textField.rightViewMode = .always
98
+ container.addSubview(textField)
99
+
100
+ let connectButton = UIButton(type: .custom)
101
+ connectButton.translatesAutoresizingMaskIntoConstraints = false
102
+ connectButton.setTitle("Connect", for: .normal)
103
+ connectButton.setTitleColor(.white, for: .normal)
104
+ connectButton.titleLabel?.font = FontManager.font(ofSize: 16, weight: .semibold)
105
+ connectButton.backgroundColor = CustomColor.shared.lightGreen
106
+ connectButton.layer.cornerRadius = 12
107
+ connectButton.addTarget(self, action: #selector(connectButtonTapped), for: .touchUpInside)
108
+ container.addSubview(connectButton)
109
+
110
+ let hintLabel = UILabel()
111
+ hintLabel.translatesAutoresizingMaskIntoConstraints = false
112
+ hintLabel.text = "Scan the QR with mobile camera to get a sensor ID."
113
+ hintLabel.font = FontManager.font(ofSize: 12, weight: .regular)
114
+ hintLabel.textColor = UIColor(white: 1, alpha: 0.55)
115
+ hintLabel.textAlignment = .center
116
+ hintLabel.numberOfLines = 0
117
+ container.addSubview(hintLabel)
118
+
119
+ NSLayoutConstraint.activate([
120
+ textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 16),
121
+ textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
122
+ textField.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16),
123
+ textField.heightAnchor.constraint(equalToConstant: 48),
124
+
125
+ connectButton.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 12),
126
+ connectButton.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
127
+ connectButton.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16),
128
+ connectButton.heightAnchor.constraint(equalToConstant: 52),
129
+
130
+ hintLabel.topAnchor.constraint(equalTo: connectButton.bottomAnchor, constant: 10),
131
+ hintLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
132
+ hintLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16),
133
+ hintLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -16),
134
+
135
+ container.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
136
+ container.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
137
+ container.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
138
+ container.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -8),
139
+ ])
140
+
141
+ manualEntryView = container
142
+ sensorIdTextField = textField
143
+ }
144
+
145
+ // MARK: - Public API
146
+
147
+ func configureAsStaticImage() {
148
+ isManualEntryVisible = false
149
+ manualEntryView?.isHidden = true
150
+ cameraView.isHidden = true
151
+ scannerView?.stopScanning()
152
+ }
153
+
154
+ func configure(isManualEntry: Bool) {
155
+ isManualEntryVisible = isManualEntry
156
+ if isManualEntry {
157
+ cameraView.isHidden = true
158
+ manualEntryView?.isHidden = false
159
+ scannerView?.stopScanning()
160
+ } else {
161
+ cameraView.isHidden = false
162
+ manualEntryView?.isHidden = true
163
+ sensorIdTextField?.text = nil
164
+ sensorIdTextField?.resignFirstResponder()
165
+ }
166
+ }
167
+
51
168
  func setupQRView() {
52
169
  scannerView = QRScannerView(frame: cameraView.bounds)
53
170
  scannerView.onCodeDetected = { code in
54
171
  print("Detected QR Code: \(code)")
55
- // You can stop scanning or navigate
56
172
  self.scannerView.stopScanning()
57
173
  }
58
-
59
174
  cameraView.addSubview(scannerView)
60
175
  }
61
-
176
+
177
+ // MARK: - Actions
178
+
179
+ @objc private func connectButtonTapped() {
180
+ sensorIdTextField?.resignFirstResponder()
181
+ let sensorId = (sensorIdTextField?.text ?? "").trimmingCharacters(in: .whitespaces).uppercased()
182
+ onConnectTapped?(sensorId)
183
+ }
184
+ }
185
+
186
+ extension ImageTVC: UITextFieldDelegate {
187
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
188
+ textField.resignFirstResponder()
189
+ return true
190
+ }
191
+
192
+ func textFieldDidBeginEditing(_ textField: UITextField) {
193
+ onTextFieldDidBeginEditing?()
194
+ }
62
195
  }
63
196
 
64
197
  class QRScannerView: UIView {
@@ -90,7 +223,6 @@ class QRScannerView: UIView {
90
223
  let metadataOutput = AVCaptureMetadataOutput()
91
224
  if captureSession.canAddOutput(metadataOutput) {
92
225
  captureSession.addOutput(metadataOutput)
93
-
94
226
  metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
95
227
  metadataOutput.metadataObjectTypes = [.qr]
96
228
  }
@@ -100,7 +232,7 @@ class QRScannerView: UIView {
100
232
  previewLayer.videoGravity = .resizeAspectFill
101
233
  layer.addSublayer(previewLayer)
102
234
 
103
- self.captureSession.startRunning()
235
+ captureSession.startRunning()
104
236
  }
105
237
 
106
238
  func stopScanning() {
@@ -26,6 +26,7 @@ class ConnectToSensorViewController: UIViewController {
26
26
  case verticalLabel
27
27
  case image
28
28
  case camera
29
+ case manualEntryBar
29
30
  }
30
31
 
31
32
  enum enumSuccessTableRow: Int, CaseIterable {
@@ -52,19 +53,33 @@ class ConnectToSensorViewController: UIViewController {
52
53
  var screenType: enumScreenType = .camera
53
54
  var isCodeDetected = false
54
55
  var isPermissionGiven: Bool = false
56
+ var isManualEntryMode: Bool = false
57
+ private var keyboardInset: CGFloat = 0
58
+ private var keyboardObserversRegistered = false
55
59
  let manager = KLTBluetoothManager.shared()
56
60
  let debouncer = EnumDebouncer<CGMConnectionStatus>()
57
61
  var deviceVerificationErrorMessage: String? = nil
58
62
 
59
63
  override func viewDidLoad() {
60
64
  super.viewDidLoad()
61
- // Do any additional setup after loading the view.
62
65
  setupLayout()
63
66
  }
64
67
 
65
- override func viewWillAppear(_ animated: Bool) {
68
+ override func viewWillAppear(_ animated: Bool) {
66
69
  super.viewWillAppear(animated)
67
70
  CustomTopViewSupportHelper.configureWhatsapp(customTopView: customTopView)
71
+ registerKeyboardObserversIfNeeded()
72
+ }
73
+
74
+ override func viewWillDisappear(_ animated: Bool) {
75
+ super.viewWillDisappear(animated)
76
+ removeKeyboardObservers()
77
+ resetKeyboardInsets()
78
+ view.endEditing(true)
79
+ }
80
+
81
+ deinit {
82
+ removeKeyboardObservers()
68
83
  }
69
84
 
70
85
  func setupLayout() {
@@ -73,6 +88,7 @@ class ConnectToSensorViewController: UIViewController {
73
88
 
74
89
  tableView.delegate = self
75
90
  tableView.dataSource = self
91
+ tableView.keyboardDismissMode = .interactive
76
92
  tableView.registerNibs([VerticalLabelsTVC.self,
77
93
  SeparatorTVC.self,
78
94
  ImageTVC.self,
@@ -82,6 +98,7 @@ class ConnectToSensorViewController: UIViewController {
82
98
  NoteTVC.self,
83
99
  PodDetailsTVC.self,
84
100
  WatchVideoTVC.self])
101
+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ManualEntryBarCell")
85
102
  tableView.reloadData()
86
103
  contactSupport.isHidden = screenType != .error
87
104
  contactSupport.labelText = "Contact Support"
@@ -242,7 +259,7 @@ class ConnectToSensorViewController: UIViewController {
242
259
  let data = AlgorithmTools.decodeCT(value)
243
260
 
244
261
  if data.k > 0 || data.r > 0 {
245
- //KLTLocalSettingManager.shareInstance.deviceInfoSensor = value
262
+ KLTLocalSettingManager.shareInstance().deviceInfoSensor = value
246
263
  UserDefaults.standard.set(data.k, forKey: "algo_k")
247
264
  UserDefaults.standard.set(data.r, forKey: "algo_r")
248
265
  UserDefaults.standard.synchronize()
@@ -250,18 +267,22 @@ class ConnectToSensorViewController: UIViewController {
250
267
  let lastConnectCgm = UserDefaults.standard.string(forKey: "lastConnectCgm") ?? ""
251
268
 
252
269
  if lastConnectCgm.lowercased() == "diasens" {
253
- // Diasens user - validate sensor via API before connecting
254
270
  validateDiasensSensor(sensorId: value)
255
271
  } else {
256
- // Non-Diasens user - proceed directly
257
272
  viewModel.debouncer.update(with: .connected)
258
273
  self.showConfirmInsulinUser()
259
274
  }
260
275
  return
261
276
  }
277
+
278
+ // decodeCT returned k=0 and r=0 — sensor QR is not effective for this transmitter
279
+ showSensorInvalidAlert(message: "This sensor is not compatible with your transmitter. Please verify the sensor QR.")
280
+ isCodeDetected = false
281
+ return
262
282
  }
263
283
 
264
- //controller.resumeScanning()
284
+ // Sensor ID format is invalid
285
+ showSensorInvalidAlert(message: "The scanned sensor ID is not valid. Please try again or enter the sensor ID manually.")
265
286
  }
266
287
 
267
288
  func validateDiasensSensor(sensorId: String) {
@@ -319,7 +340,99 @@ class ConnectToSensorViewController: UIViewController {
319
340
  // Connect the transmitter
320
341
  manager?.connectPeripheral(manager?.readyToConnectedPeripheral)
321
342
  }
343
+
344
+ @objc func toggleManualEntry() {
345
+ isManualEntryMode.toggle()
346
+ if !isManualEntryMode {
347
+ resetKeyboardInsets()
348
+ view.endEditing(true)
349
+ }
350
+ tableView.reloadData()
351
+ }
352
+
353
+ private var manualEntryIndexPath: IndexPath {
354
+ IndexPath(row: enumTableRow.camera.rawValue, section: enumTableSection.details.rawValue)
355
+ }
356
+
357
+ private func registerKeyboardObserversIfNeeded() {
358
+ guard !keyboardObserversRegistered else { return }
359
+ keyboardObserversRegistered = true
360
+ NotificationCenter.default.addObserver(
361
+ self,
362
+ selector: #selector(keyboardWillShow(_:)),
363
+ name: UIResponder.keyboardWillShowNotification,
364
+ object: nil
365
+ )
366
+ NotificationCenter.default.addObserver(
367
+ self,
368
+ selector: #selector(keyboardWillHide(_:)),
369
+ name: UIResponder.keyboardWillHideNotification,
370
+ object: nil
371
+ )
372
+ }
373
+
374
+ private func removeKeyboardObservers() {
375
+ guard keyboardObserversRegistered else { return }
376
+ keyboardObserversRegistered = false
377
+ NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
378
+ NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
379
+ }
380
+
381
+ private func resetKeyboardInsets() {
382
+ keyboardInset = 0
383
+ tableView.contentInset.bottom = 0
384
+ tableView.verticalScrollIndicatorInsets.bottom = 0
385
+ }
386
+
387
+ @objc private func keyboardWillShow(_ notification: Notification) {
388
+ guard isManualEntryMode,
389
+ let userInfo = notification.userInfo,
390
+ let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
391
+ return
392
+ }
393
+
394
+ let convertedFrame = view.convert(keyboardFrame, from: nil)
395
+ let inset = max(0, view.bounds.maxY - convertedFrame.minY - view.safeAreaInsets.bottom)
396
+ keyboardInset = inset
397
+ tableView.contentInset.bottom = inset
398
+ tableView.verticalScrollIndicatorInsets.bottom = inset
399
+
400
+ let duration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.25
401
+ UIView.animate(withDuration: duration) {
402
+ self.scrollManualEntryTextFieldAboveKeyboard(animated: false)
403
+ }
404
+ }
405
+
406
+ @objc private func keyboardWillHide(_ notification: Notification) {
407
+ guard let userInfo = notification.userInfo else {
408
+ resetKeyboardInsets()
409
+ return
410
+ }
411
+ let duration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.25
412
+ UIView.animate(withDuration: duration) {
413
+ self.resetKeyboardInsets()
414
+ }
415
+ }
416
+
417
+ private func scrollManualEntryTextFieldAboveKeyboard(animated: Bool) {
418
+ guard isManualEntryMode,
419
+ let cell = tableView.cellForRow(at: manualEntryIndexPath) as? ImageTVC,
420
+ let textField = cell.activeSensorIdTextField else {
421
+ return
422
+ }
423
+
424
+ let fieldRect = textField.convert(textField.bounds, to: tableView)
425
+ tableView.scrollRectToVisible(fieldRect.insetBy(dx: 0, dy: -12), animated: animated)
426
+ }
322
427
 
428
+ private func showSensorInvalidAlert(message: String) {
429
+ DispatchQueue.main.async {
430
+ let alert = UIAlertController(title: "Invalid Sensor", message: message, preferredStyle: .alert)
431
+ alert.addAction(UIAlertAction(title: "OK", style: .default))
432
+ self.present(alert, animated: true)
433
+ }
434
+ }
435
+
323
436
  func showConfirmInsulinUser() {
324
437
  let typeOfDiabetes = UserDefaults.standard.object(forKey: "profilePatientType") as? NSNumber
325
438
 
@@ -457,7 +570,9 @@ extension ConnectToSensorViewController: UITableViewDelegate, UITableViewDataSou
457
570
  case .image:
458
571
  return (UIScreen.main.bounds.width * 120 / 390) + 40
459
572
  case .camera:
460
- return 220
573
+ return isManualEntryMode ? 200 : 220
574
+ case .manualEntryBar:
575
+ return 60
461
576
  default:
462
577
  return UITableView.automaticDimension
463
578
  }
@@ -491,17 +606,35 @@ extension ConnectToSensorViewController: UITableViewDelegate, UITableViewDataSou
491
606
  return cell
492
607
  case .image:
493
608
  let cell: ImageTVC = tableView.dequeueReusableCell(for: indexPath)
609
+ cell.configureAsStaticImage()
494
610
  cell.displayImageView.image = loadImage(named: "flip_the_reference")
495
611
  cell.displayImageView.contentMode = .scaleAspectFill
496
612
  cell.displayImageView.isHidden = false
497
- cell.cameraView.isHidden = true
498
613
  return cell
499
614
  case .camera:
500
615
  let cell: ImageTVC = tableView.dequeueReusableCell(for: indexPath)
501
616
  cell.displayImageView.contentMode = .scaleAspectFill
502
617
  cell.displayImageView.isHidden = true
503
- cell.cameraView.isHidden = false
504
- if isPermissionGiven {
618
+ cell.configure(isManualEntry: isManualEntryMode)
619
+ if isManualEntryMode {
620
+ cell.onTextFieldDidBeginEditing = { [weak self] in
621
+ self?.scrollManualEntryTextFieldAboveKeyboard(animated: true)
622
+ }
623
+ cell.onConnectTapped = { [weak self] sensorId in
624
+ guard let self = self else { return }
625
+ guard !sensorId.isEmpty else {
626
+ let alert = UIAlertController(
627
+ title: nil,
628
+ message: "Please enter a Sensor ID",
629
+ preferredStyle: .alert
630
+ )
631
+ alert.addAction(UIAlertAction(title: "OK", style: .default))
632
+ self.present(alert, animated: true)
633
+ return
634
+ }
635
+ self.connectSensor(value: sensorId, controller: self)
636
+ }
637
+ } else if isPermissionGiven {
505
638
  DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
506
639
  cell.setupQRView()
507
640
  cell.scannerView?.onCodeDetected = { data in
@@ -513,6 +646,59 @@ extension ConnectToSensorViewController: UITableViewDelegate, UITableViewDataSou
513
646
  }
514
647
  }
515
648
  return cell
649
+ case .manualEntryBar:
650
+ let cell = tableView.dequeueReusableCell(withIdentifier: "ManualEntryBarCell")!
651
+ cell.selectionStyle = .none
652
+ cell.backgroundColor = .clear
653
+ cell.contentView.subviews.forEach { $0.removeFromSuperview() }
654
+
655
+ let bar = UIView()
656
+ bar.translatesAutoresizingMaskIntoConstraints = false
657
+ bar.backgroundColor = UIColor(red: 240/255, green: 244/255, blue: 255/255, alpha: 1)
658
+ bar.layer.cornerRadius = 10
659
+ bar.layer.borderWidth = 1
660
+ bar.layer.borderColor = UIColor(red: 220/255, green: 225/255, blue: 245/255, alpha: 1).cgColor
661
+
662
+ let prefixText = isManualEntryMode ? "Scan the QR." : "Can't scan the sensor QR?"
663
+ let linkText = isManualEntryMode ? " Scan QR" : " Enter ID Manually"
664
+
665
+ let prefixLabel = UILabel()
666
+ prefixLabel.text = prefixText
667
+ prefixLabel.font = FontManager.font(ofSize: 14, weight: .regular)
668
+ prefixLabel.textColor = UIColor(red: 45/255, green: 50/255, blue: 130/255, alpha: 1)
669
+
670
+ let linkLabel = UILabel()
671
+ let attr = NSMutableAttributedString(string: linkText, attributes: [
672
+ .font: FontManager.font(ofSize: 14, weight: .bold),
673
+ .foregroundColor: CustomColor.shared.lightGreen,
674
+ .underlineStyle: NSUnderlineStyle.single.rawValue,
675
+ ])
676
+ linkLabel.attributedText = attr
677
+
678
+ let stack = UIStackView(arrangedSubviews: [prefixLabel, linkLabel])
679
+ stack.translatesAutoresizingMaskIntoConstraints = false
680
+ stack.axis = .horizontal
681
+ stack.alignment = .center
682
+ stack.spacing = 4
683
+ bar.addSubview(stack)
684
+
685
+ // Tap the entire bar so the hit area is large and reliable
686
+ bar.isUserInteractionEnabled = true
687
+ let tap = UITapGestureRecognizer(target: self, action: #selector(toggleManualEntry))
688
+ bar.addGestureRecognizer(tap)
689
+
690
+ cell.contentView.addSubview(bar)
691
+ NSLayoutConstraint.activate([
692
+ stack.centerXAnchor.constraint(equalTo: bar.centerXAnchor),
693
+ stack.centerYAnchor.constraint(equalTo: bar.centerYAnchor),
694
+ stack.leadingAnchor.constraint(greaterThanOrEqualTo: bar.leadingAnchor, constant: 12),
695
+ stack.trailingAnchor.constraint(lessThanOrEqualTo: bar.trailingAnchor, constant: -12),
696
+ bar.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 24),
697
+ bar.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -24),
698
+ bar.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 4),
699
+ bar.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -4),
700
+ ])
701
+ return cell
516
702
  }
517
703
  case .success:
518
704
  switch enumSuccessTableRow(rawValue: indexPath.row)! {
@@ -542,10 +728,9 @@ extension ConnectToSensorViewController: UITableViewDelegate, UITableViewDataSou
542
728
  switch enumFailureTableRow(rawValue: indexPath.row)! {
543
729
  case .failAnimation:
544
730
  let cell: ImageTVC = tableView.dequeueReusableCell(for: indexPath)
731
+ cell.configureAsStaticImage()
545
732
  cell.displayImageView.loadGif(named: "failure")
546
733
  cell.bottomConstraint.constant = 11
547
- // Hide camera view to prevent it from overlapping error content (cell reuse)
548
- cell.cameraView.isHidden = true
549
734
  cell.displayImageView.isHidden = false
550
735
  // Scale down only for validation API error (65% of original size)
551
736
  if deviceVerificationErrorMessage != nil {
@@ -577,11 +762,10 @@ extension ConnectToSensorViewController: UITableViewDelegate, UITableViewDataSou
577
762
  return cell
578
763
  case .image:
579
764
  let cell: ImageTVC = tableView.dequeueReusableCell(for: indexPath)
765
+ cell.configureAsStaticImage()
580
766
  cell.topConstraint.constant = 0
581
767
  cell.displayImageView.image = loadImage(named: "flip_the_reference")
582
768
  cell.displayImageView.contentMode = .scaleAspectFill
583
- // Hide camera view to prevent it from overlapping error content (cell reuse)
584
- cell.cameraView.isHidden = true
585
769
  cell.displayImageView.isHidden = false
586
770
  return cell
587
771
  case .watchVideo:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mytatva-rn-sdk",
3
- "version": "1.2.98",
3
+ "version": "1.2.99",
4
4
  "description": "a package to inject data into visit health pwa",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",