react-native-mytatva-rn-sdk 1.2.97 → 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.
Files changed (32) hide show
  1. package/android/src/main/java/com/mytatvarnsdk/CgmTrackyLibModule.kt +25 -0
  2. package/android/src/main/java/com/mytatvarnsdk/activity/ConnectSensorActivity.kt +164 -18
  3. package/android/src/main/java/com/mytatvarnsdk/activity/HelpActivity.kt +3 -9
  4. package/android/src/main/java/com/mytatvarnsdk/activity/PermissionActivity.kt +8 -16
  5. package/android/src/main/java/com/mytatvarnsdk/activity/PlaceSensorActivity.kt +12 -13
  6. package/android/src/main/java/com/mytatvarnsdk/activity/PlaceTransmitterActivity.kt +12 -13
  7. package/android/src/main/java/com/mytatvarnsdk/activity/SearchTransmitterActivity.kt +9 -14
  8. package/android/src/main/java/com/mytatvarnsdk/activity/SensorConnectSuccessActivity.kt +12 -16
  9. package/android/src/main/java/com/mytatvarnsdk/activity/StartCGMActivity.kt +9 -15
  10. package/android/src/main/java/com/mytatvarnsdk/activity/VideoActivity.kt +96 -50
  11. package/android/src/main/java/com/mytatvarnsdk/network/AuthenticateSDKService.kt +50 -0
  12. package/android/src/main/java/com/mytatvarnsdk/utils/CustomerSupportConfig.kt +137 -0
  13. package/android/src/main/java/com/mytatvarnsdk/utils/ToolbarSupportHelper.kt +83 -0
  14. package/android/src/main/res/layout/activity_connect_sensor.xml +117 -2
  15. package/ios/Custom/CustomTopUIView.swift +25 -0
  16. package/ios/Database/KLTBluetoothManager.m +69 -14
  17. package/ios/MyReactNativeBridge.m +4 -0
  18. package/ios/Support/API.swift +117 -18
  19. package/ios/Support/CustomTopViewSupportHelper.swift +55 -0
  20. package/ios/Support/CustomerSupportConfig.swift +169 -0
  21. package/ios/Support/Global.swift +2 -2
  22. package/ios/Support/GlobalYouTubePlayerView.swift +102 -11
  23. package/ios/TableViewCell/ImageTVC/ImageTVC.swift +147 -15
  24. package/ios/ViewControllers/AttachTransmitterViewController.swift +16 -21
  25. package/ios/ViewControllers/ChatWithExpertViewController.swift +1 -7
  26. package/ios/ViewControllers/ConnectToSensorViewController.swift +213 -34
  27. package/ios/ViewControllers/ConnectToTransmitterViewController.swift +16 -21
  28. package/ios/ViewControllers/ProvidePermissionViewController.swift +16 -21
  29. package/ios/ViewControllers/PutOnTheSensorViewController.swift +16 -21
  30. package/ios/ViewControllers/StartConnectionViewController.swift +18 -25
  31. package/ios/ViewModel/FinalViewModel.swift +2 -2
  32. package/package.json +1 -1
@@ -1,23 +1,24 @@
1
1
  package com.mytatvarnsdk.activity
2
2
 
3
+ import android.annotation.SuppressLint
3
4
  import android.os.Bundle
4
5
  import android.view.View
6
+ import android.webkit.WebChromeClient
7
+ import android.webkit.WebView
8
+ import android.webkit.WebViewClient
5
9
  import androidx.activity.OnBackPressedCallback
6
10
  import androidx.activity.enableEdgeToEdge
7
11
  import androidx.appcompat.app.AppCompatActivity
12
+ import androidx.constraintlayout.widget.ConstraintLayout
8
13
  import androidx.core.view.ViewCompat
9
14
  import androidx.core.view.WindowInsetsCompat
10
15
  import com.mytatvarnsdk.R
11
16
  import com.mytatvarnsdk.databinding.ActivityVideoBinding
12
- import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer
13
- import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
14
- import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.FullscreenListener
15
- import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.options.IFramePlayerOptions
17
+ import com.mytatvarnsdk.utils.CustomerSupportConfig
16
18
 
17
19
  class VideoActivity : AppCompatActivity() {
18
20
  private lateinit var binding: ActivityVideoBinding
19
- private var isFullscreen = false
20
- private lateinit var youTubePlayer: YouTubePlayer
21
+ private var webView: WebView? = null
21
22
  private var videoId = ""
22
23
 
23
24
  override fun onCreate(savedInstanceState: Bundle?) {
@@ -34,66 +35,111 @@ class VideoActivity : AppCompatActivity() {
34
35
  init()
35
36
  }
36
37
 
38
+ @SuppressLint("SetJavaScriptEnabled")
37
39
  private fun init() {
38
- videoId = intent.getStringExtra("VideoId").toString()
40
+ videoId = resolveVideoId(intent.getStringExtra("VideoId"))
39
41
 
40
42
  onBackPressedDispatcher.addCallback(onBackPressedCallback)
41
43
 
42
- val iFramePlayerOptions = IFramePlayerOptions.Builder()
43
- .controls(1)
44
- .fullscreen(1)
45
- .build()
46
- binding.youtubePlayerView.enableAutomaticInitialization = false
44
+ binding.youtubePlayerView.visibility = View.GONE
45
+ binding.fullScreenViewContainer.visibility = View.GONE
47
46
 
48
47
  binding.ivClose.setOnClickListener {
49
- handleBackPressedManually()
48
+ finish()
50
49
  }
51
50
 
52
- binding.youtubePlayerView.addFullscreenListener(object : FullscreenListener {
53
- override fun onEnterFullscreen(fullscreenView: View, exitFullscreen: () -> Unit) {
54
- isFullscreen = true
55
-
56
- // the video will continue playing in fullscreenView
57
- binding.youtubePlayerView.visibility = View.GONE
58
- binding.fullScreenViewContainer.visibility = View.VISIBLE
59
- binding.fullScreenViewContainer.addView(fullscreenView)
60
-
61
- // optionally request landscape orientation
62
- // requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
63
- }
64
-
65
- override fun onExitFullscreen() {
66
- isFullscreen = false
67
-
68
- // the video will continue playing in the player
69
- binding.youtubePlayerView.visibility = View.VISIBLE
70
- binding.fullScreenViewContainer.visibility = View.GONE
71
- binding.fullScreenViewContainer.removeAllViews()
72
- }
73
- })
74
-
75
- binding.youtubePlayerView.initialize(object : AbstractYouTubePlayerListener() {
76
- override fun onReady(youTubePlayer: YouTubePlayer) {
77
- this@VideoActivity.youTubePlayer = youTubePlayer
78
- youTubePlayer.loadVideo(videoId, 0f)
51
+ val playerWebView = WebView(this)
52
+ webView = playerWebView
53
+ playerWebView.settings.apply {
54
+ javaScriptEnabled = true
55
+ domStorageEnabled = true
56
+ mediaPlaybackRequiresUserGesture = false
57
+ }
58
+ playerWebView.webViewClient = WebViewClient()
59
+ playerWebView.webChromeClient = WebChromeClient()
60
+
61
+ val layoutParams = ConstraintLayout.LayoutParams(
62
+ ConstraintLayout.LayoutParams.MATCH_PARENT,
63
+ ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
64
+ ).apply {
65
+ topToBottom = R.id.ivClose
66
+ bottomToBottom = R.id.main
67
+ startToStart = R.id.main
68
+ endToEnd = R.id.main
69
+ }
70
+ (binding.root as ConstraintLayout).addView(playerWebView, layoutParams)
71
+ binding.ivClose.bringToFront()
72
+ binding.ivClose.elevation = 12f
73
+
74
+ val html = buildYouTubeEmbedHtml(videoId)
75
+ // Base URL + referrer meta are required by YouTube (errors 152-4 / 153).
76
+ playerWebView.loadDataWithBaseURL(
77
+ EMBED_BASE_URL,
78
+ html,
79
+ "text/html",
80
+ "UTF-8",
81
+ null
82
+ )
83
+ }
79
84
 
80
- }
81
- }, iFramePlayerOptions)
85
+ private fun resolveVideoId(raw: String?): String {
86
+ val value = raw?.trim().orEmpty()
87
+ if (value.isEmpty() || value == "null") {
88
+ return CustomerSupportConfig.DEFAULT_DEMO_VIDEO_ID
89
+ }
90
+ return CustomerSupportConfig.extractYouTubeVideoId(value) ?: value
91
+ }
82
92
 
83
- lifecycle.addObserver(binding.youtubePlayerView)
93
+ private fun buildYouTubeEmbedHtml(videoId: String): String {
94
+ return """
95
+ <!DOCTYPE html>
96
+ <html>
97
+ <head>
98
+ <meta name="viewport" content="width=device-width, initial-scale=1">
99
+ <meta name="referrer" content="strict-origin-when-cross-origin">
100
+ <style>
101
+ html, body { margin: 0; padding: 0; background: #000; height: 100%; }
102
+ .video-container { position: relative; width: 100%; height: 100%; }
103
+ iframe {
104
+ position: absolute;
105
+ top: 0;
106
+ left: 0;
107
+ width: 100%;
108
+ height: 100%;
109
+ border: 0;
110
+ }
111
+ </style>
112
+ </head>
113
+ <body>
114
+ <div class="video-container">
115
+ <iframe
116
+ src="https://www.youtube-nocookie.com/embed/$videoId?playsinline=1&rel=0&modestbranding=1"
117
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
118
+ allowfullscreen
119
+ referrerpolicy="strict-origin-when-cross-origin">
120
+ </iframe>
121
+ </div>
122
+ </body>
123
+ </html>
124
+ """.trimIndent()
84
125
  }
85
126
 
86
127
  private val onBackPressedCallback = object : OnBackPressedCallback(true) {
87
128
  override fun handleOnBackPressed() {
88
- handleBackPressedManually()
129
+ finish()
89
130
  }
90
131
  }
91
132
 
92
- private fun handleBackPressedManually() {
93
- if (isFullscreen) {
94
- youTubePlayer.toggleFullscreen()
95
- } else {
96
- finish()
133
+ override fun onDestroy() {
134
+ webView?.apply {
135
+ stopLoading()
136
+ destroy()
97
137
  }
138
+ webView = null
139
+ super.onDestroy()
140
+ }
141
+
142
+ companion object {
143
+ private const val EMBED_BASE_URL = "https://www.mytatva.in/"
98
144
  }
99
- }
145
+ }
@@ -260,6 +260,56 @@ class AuthenticateSDKService(val scope: CoroutineScope) {
260
260
  }
261
261
  }
262
262
 
263
+ fun fetchCustomerSupportDetails(
264
+ environment: TATVA_ENVIRONMENT,
265
+ token: String,
266
+ onSuccess: (String) -> Unit = {},
267
+ onFailure: () -> Unit = {}
268
+ ) {
269
+ scope.launch {
270
+ try {
271
+ val apiServiceInterface = RetrofitInstance.getRetroInstance().create(
272
+ ApiServiceInterface::class.java
273
+ )
274
+
275
+ val encryptionKey = getEncryptionKey(
276
+ env = TATVA_ENVIRONMENT.PROD,
277
+ prodEncKey = PROD_ENC_KEY,
278
+ stagEncKey = STAGE_ENC_KEY
279
+ )
280
+
281
+ val encryptionIv = getEncryptionIv(
282
+ env = TATVA_ENVIRONMENT.PROD, prodEncIv = PROD_ENC_IV, stagEncIv = STAGE_ENC_IV
283
+ )
284
+
285
+ val headers = mapOf(
286
+ "api-key" to if (environment == TATVA_ENVIRONMENT.PROD) PROD_API_KEY else STAGE_API_KEY,
287
+ "token" to token
288
+ )
289
+
290
+ val baseUrl = if (environment == TATVA_ENVIRONMENT.PROD) PROD_BASE_URL else STAGE_BASE_URL
291
+ val url = "$baseUrl/helper/customer_support_details"
292
+
293
+ Log.d("API Response", "Customer Support Details URL: $url")
294
+
295
+ val response = apiServiceInterface.getCgmStatus(url, headers).string()
296
+
297
+ val decryptedResponse = getDecryptedData(
298
+ cipherText = response.toString(),
299
+ encryptionKey = encryptionKey,
300
+ encryptionIv = encryptionIv
301
+ ) ?: response.toString()
302
+
303
+ Log.d("API Response", "Customer Support Details Response: $decryptedResponse")
304
+ onSuccess(decryptedResponse)
305
+ } catch (e: Exception) {
306
+ Log.d("API Response", "Customer Support Details Exception: ${e.message}")
307
+ e.printStackTrace()
308
+ onFailure()
309
+ }
310
+ }
311
+ }
312
+
263
313
  fun verifyDevice(
264
314
  environment: TATVA_ENVIRONMENT,
265
315
  token: String,
@@ -0,0 +1,137 @@
1
+ package com.mytatvarnsdk.utils
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import android.util.Log
6
+ import org.json.JSONObject
7
+ import java.util.concurrent.CopyOnWriteArraySet
8
+
9
+ object CustomerSupportConfig {
10
+ private const val TAG = "CustomerSupportConfig"
11
+
12
+ const val DEFAULT_WHATSAPP_NUMBER = "918511975757"
13
+ const val DEFAULT_DEMO_VIDEO_ID = "65vl2aE1K_I"
14
+ const val DEFAULT_DEMO_VIDEO_URL =
15
+ "https://www.youtube.com/embed/65vl2aE1K_I"
16
+
17
+ private val mainHandler = Handler(Looper.getMainLooper())
18
+ private val updateListeners = CopyOnWriteArraySet<() -> Unit>()
19
+
20
+ @Volatile
21
+ var isLoaded: Boolean = false
22
+
23
+ @Volatile
24
+ var isShowWhatsapp: Boolean = false
25
+
26
+ @Volatile
27
+ var whatsAppNumber: String = DEFAULT_WHATSAPP_NUMBER
28
+
29
+ @Volatile
30
+ var watchCGMDemoUrl: String = DEFAULT_DEMO_VIDEO_URL
31
+
32
+ fun addUpdateListener(listener: () -> Unit) {
33
+ updateListeners.add(listener)
34
+ }
35
+
36
+ fun removeUpdateListener(listener: () -> Unit) {
37
+ updateListeners.remove(listener)
38
+ }
39
+
40
+ fun applyFailureDefaults() {
41
+ isShowWhatsapp = true
42
+ whatsAppNumber = DEFAULT_WHATSAPP_NUMBER
43
+ watchCGMDemoUrl = DEFAULT_DEMO_VIDEO_URL
44
+ isLoaded = true
45
+ Log.d(
46
+ TAG,
47
+ "Failure defaults applied | showWhatsapp=$isShowWhatsapp | demoUrl=$watchCGMDemoUrl"
48
+ )
49
+ notifyConfigUpdated()
50
+ }
51
+
52
+ fun updateFromResponse(response: String) {
53
+ try {
54
+ val json = JSONObject(response)
55
+ if (json.optInt("code", 0) != 1) {
56
+ Log.w(TAG, "Customer support API returned non-success code, using failure defaults")
57
+ applyFailureDefaults()
58
+ return
59
+ }
60
+
61
+ val data = json.optJSONObject("data")
62
+ if (data == null) {
63
+ Log.w(TAG, "Customer support API missing data, using failure defaults")
64
+ applyFailureDefaults()
65
+ return
66
+ }
67
+
68
+ isShowWhatsapp = parseBoolean(data, "isShowWhatsapp")
69
+ whatsAppNumber = data.optString("whatsAppNumber", "").trim()
70
+ .ifEmpty { DEFAULT_WHATSAPP_NUMBER }
71
+ watchCGMDemoUrl = data.optString("watchCGMDemoUrl", "").trim()
72
+ .ifEmpty { DEFAULT_DEMO_VIDEO_URL }
73
+ isLoaded = true
74
+
75
+ Log.d(
76
+ TAG,
77
+ "Customer support loaded | showWhatsapp=$isShowWhatsapp | demoUrl=$watchCGMDemoUrl"
78
+ )
79
+ notifyConfigUpdated()
80
+ } catch (e: Exception) {
81
+ Log.w(TAG, "Failed to parse customer support response: ${e.message}, using failure defaults")
82
+ applyFailureDefaults()
83
+ }
84
+ }
85
+
86
+ private fun notifyConfigUpdated() {
87
+ mainHandler.post {
88
+ updateListeners.forEach { listener ->
89
+ try {
90
+ listener.invoke()
91
+ } catch (e: Exception) {
92
+ Log.w(TAG, "Customer support listener failed: ${e.message}")
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ fun effectiveWhatsAppNumber(): String =
99
+ whatsAppNumber.ifEmpty { DEFAULT_WHATSAPP_NUMBER }
100
+
101
+ fun shouldShowWhatsapp(): Boolean =
102
+ isLoaded && isShowWhatsapp && effectiveWhatsAppNumber().isNotEmpty()
103
+
104
+ fun getDemoVideoId(isForReconnect: Boolean): String {
105
+ val url = watchCGMDemoUrl.ifEmpty { DEFAULT_DEMO_VIDEO_URL }
106
+ extractYouTubeVideoId(url)?.let { return it }
107
+ return DEFAULT_DEMO_VIDEO_ID
108
+ }
109
+
110
+ fun getDemoVideoUrl(isForReconnect: Boolean): String =
111
+ watchCGMDemoUrl.ifEmpty { DEFAULT_DEMO_VIDEO_URL }
112
+
113
+ fun extractYouTubeVideoId(url: String): String? {
114
+ val trimmed = url.trim()
115
+ if (trimmed.isEmpty()) return null
116
+
117
+ val patterns = listOf(
118
+ Regex("(?:embed/|/v/|watch\\?v=|youtu\\.be/)([\\w-]{11})"),
119
+ Regex("^([\\w-]{11})$")
120
+ )
121
+
122
+ for (pattern in patterns) {
123
+ pattern.find(trimmed)?.groupValues?.getOrNull(1)?.let { return it }
124
+ }
125
+ return null
126
+ }
127
+
128
+ private fun parseBoolean(json: JSONObject, key: String): Boolean {
129
+ if (!json.has(key)) return false
130
+ return when (val value = json.get(key)) {
131
+ is Boolean -> value
132
+ is Int -> value == 1
133
+ is String -> value.equals("true", ignoreCase = true) || value == "1"
134
+ else -> false
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,83 @@
1
+ package com.mytatvarnsdk.utils
2
+
3
+ import android.content.Context
4
+ import android.content.Intent
5
+ import android.net.Uri
6
+ import android.view.View
7
+ import android.widget.Toast
8
+ import androidx.appcompat.app.AppCompatActivity
9
+ import androidx.lifecycle.DefaultLifecycleObserver
10
+ import androidx.lifecycle.LifecycleOwner
11
+ import com.mytatvarnsdk.activity.VideoActivity
12
+
13
+ object ToolbarSupportHelper {
14
+
15
+ fun configureWhatsappButton(
16
+ context: Context,
17
+ btnWhatsapp: View,
18
+ onClickAnalytics: (() -> Unit)? = null
19
+ ) {
20
+ if (CustomerSupportConfig.shouldShowWhatsapp()) {
21
+ btnWhatsapp.visibility = View.VISIBLE
22
+ btnWhatsapp.setOnClickListener {
23
+ onClickAnalytics?.invoke()
24
+ openWhatsApp(context, CustomerSupportConfig.effectiveWhatsAppNumber())
25
+ }
26
+ } else {
27
+ btnWhatsapp.visibility = View.GONE
28
+ btnWhatsapp.setOnClickListener(null)
29
+ }
30
+ }
31
+
32
+ fun bindWhatsappButton(
33
+ activity: AppCompatActivity,
34
+ btnWhatsapp: View,
35
+ onClickAnalytics: (() -> Unit)? = null
36
+ ) {
37
+ btnWhatsapp.setOnClickListener(null)
38
+ val refresh: () -> Unit = {
39
+ configureWhatsappButton(activity, btnWhatsapp, onClickAnalytics)
40
+ }
41
+
42
+ refresh()
43
+ CustomerSupportConfig.addUpdateListener(refresh)
44
+
45
+ activity.lifecycle.addObserver(object : DefaultLifecycleObserver {
46
+ override fun onDestroy(owner: LifecycleOwner) {
47
+ CustomerSupportConfig.removeUpdateListener(refresh)
48
+ }
49
+ })
50
+ }
51
+
52
+ fun configureWatchDemoButton(
53
+ context: Context,
54
+ btnWatchDemo: View,
55
+ isForReconnect: Boolean,
56
+ onClickAnalytics: (() -> Unit)? = null
57
+ ) {
58
+ btnWatchDemo.setOnClickListener {
59
+ onClickAnalytics?.invoke()
60
+ val intent = Intent(context, VideoActivity::class.java)
61
+ val videoId = CustomerSupportConfig.getDemoVideoId(isForReconnect)
62
+ intent.putExtra("VideoId", videoId)
63
+ context.startActivity(intent)
64
+ }
65
+ }
66
+
67
+ fun openWhatsApp(context: Context, number: String) {
68
+ val cleanedNumber = number.replace(Regex("[^0-9]"), "")
69
+ if (cleanedNumber.isEmpty()) {
70
+ Toast.makeText(context, "WhatsApp number unavailable", Toast.LENGTH_SHORT).show()
71
+ return
72
+ }
73
+
74
+ val waUri = Uri.parse("https://wa.me/$cleanedNumber")
75
+ val intent = Intent(Intent.ACTION_VIEW, waUri)
76
+ try {
77
+ context.startActivity(intent)
78
+ } catch (e: Exception) {
79
+ e.printStackTrace()
80
+ Toast.makeText(context, "Error opening WhatsApp", Toast.LENGTH_SHORT).show()
81
+ }
82
+ }
83
+ }
@@ -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>
@@ -48,6 +48,8 @@ class CustomTopUIView: UIView {
48
48
  var onWhatsAppTapped: (() -> Void)?
49
49
  var onCloseTapped: (() -> Void)?
50
50
 
51
+ private var customerSupportConfigObserver: NSObjectProtocol?
52
+
51
53
 
52
54
  // For Interface Builder
53
55
  override init(frame: CGRect) {
@@ -124,6 +126,21 @@ class CustomTopUIView: UIView {
124
126
  whatsappButton.addTarget(self, action: #selector(whatsappButtonTapped), for: .touchUpInside)
125
127
  closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
126
128
 
129
+ customerSupportConfigObserver = NotificationCenter.default.addObserver(
130
+ forName: .customerSupportConfigDidUpdate,
131
+ object: nil,
132
+ queue: .main
133
+ ) { [weak self] _ in
134
+ self?.refreshWhatsappVisibility()
135
+ }
136
+
137
+ refreshWhatsappVisibility()
138
+ }
139
+ }
140
+
141
+ deinit {
142
+ if let customerSupportConfigObserver {
143
+ NotificationCenter.default.removeObserver(customerSupportConfigObserver)
127
144
  }
128
145
  }
129
146
 
@@ -131,6 +148,14 @@ class CustomTopUIView: UIView {
131
148
  demoButton.isHidden = true
132
149
  whatsappButton.isHidden = true
133
150
  }
151
+
152
+ func setWhatsappVisible(_ isVisible: Bool) {
153
+ whatsappButton.isHidden = !isVisible
154
+ }
155
+
156
+ func refreshWhatsappVisibility() {
157
+ setWhatsappVisible(CustomerSupportConfig.shared.shouldShowWhatsapp())
158
+ }
134
159
 
135
160
  @objc private func demoButtonTapped() {
136
161
  onDemoTapped?()