diff --git a/android/app/src/main/java/com/naviapp/analytics/utils/NaviAnalytics.kt b/android/app/src/main/java/com/naviapp/analytics/utils/NaviAnalytics.kt index 29b9ccd081..c8edf0be60 100644 --- a/android/app/src/main/java/com/naviapp/analytics/utils/NaviAnalytics.kt +++ b/android/app/src/main/java/com/naviapp/analytics/utils/NaviAnalytics.kt @@ -430,6 +430,14 @@ class NaviAnalytics private constructor() { fun topNavRevealed() { NaviTrackEvent.trackEvent("home_page_top_nav_expanded") } + + fun onShakeListenerRegister() { + NaviTrackEvent.trackEvent("home_page_shake_listener_registered") + } + + fun onShakeListenerUnregister() { + NaviTrackEvent.trackEvent("home_page_shake_listener_unregistered") + } } inner class Notifications { diff --git a/android/app/src/main/java/com/naviapp/home/compose/activity/HomePageActivity.kt b/android/app/src/main/java/com/naviapp/home/compose/activity/HomePageActivity.kt index 4ab514fa28..9366567edf 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/activity/HomePageActivity.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/activity/HomePageActivity.kt @@ -9,6 +9,9 @@ package com.naviapp.home.compose.activity import android.content.Intent import android.graphics.Color +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener import android.net.Uri import android.os.Bundle import android.provider.Settings @@ -136,6 +139,7 @@ import com.naviapp.home.model.BottomBarTabType import com.naviapp.home.reducer.HpEffects import com.naviapp.home.reducer.HpEvents import com.naviapp.home.usecase.HomePageRedirectionUseCase +import com.naviapp.home.usecase.HomeSensorManager import com.naviapp.home.utils.PaymentSdkResultUtil import com.naviapp.home.viewmodel.HomeViewModel import com.naviapp.home.viewmodel.NotificationVM @@ -190,7 +194,8 @@ class HomePageActivity : ActionHandler.ActionOwner, WidgetCallback, PaymentListener, - InAppUpdateBridge { + InAppUpdateBridge, + SensorEventListener { @Inject lateinit var userDataUseCase: UploadUserDataUseCase @@ -210,6 +215,8 @@ class HomePageActivity : @Inject lateinit var screenOverlayEffectHandler: ScreenOverlayEffectHandler + @Inject lateinit var homeSensorManager: Lazy + private val homeVM by lazy { ViewModelProvider(this)[HomeViewModel::class.java] } private val profileVM by lazy { ViewModelProvider(this)[ProfileVM::class.java] } private val notificationVM by lazy { ViewModelProvider(this)[NotificationVM::class.java] } @@ -317,6 +324,7 @@ class HomePageActivity : initResourceManager() TraceManager.endTrace(TC.TRACE_HP_ON_CREATE) TraceManager.endAllTraces() + homeSensorManager.get().initSensorManager(this) } private fun handleInitialTouchEventState() { @@ -373,6 +381,11 @@ class HomePageActivity : handleStatusBarColor() homeVM.updateCurrentModule(tabId) bottomNavBarVM.triggerTabClickedEvent(tabId = tabId, screenName = screenName) + if (tabId == BottomBarTabType.HOME.name) { + homeSensorManager.get().registerListener(this, naviAnalyticsEventTracker) + } else { + homeSensorManager.get().unregisterListener(this, naviAnalyticsEventTracker) + } } private fun updateTab(screen: String, bundle: Bundle? = null, isResetCall: Boolean = false) { @@ -474,6 +487,17 @@ class HomePageActivity : bottomNavBarVM.setBottomNudge(false) } } + if ( + sharedVM.getSelectedTabId() == BottomBarTabType.HOME.name && + !homeVM.state.value.profileDrawerState + ) { + homeSensorManager.get().registerListener(this, naviAnalyticsEventTracker) + } + } + + override fun onPause() { + super.onPause() + homeSensorManager.get().unregisterListener(this, naviAnalyticsEventTracker) } override fun postLocationData() { @@ -490,6 +514,12 @@ class HomePageActivity : homeVM.clearVideoView() } + override fun onSensorChanged(event: SensorEvent) { + homeSensorManager.get().onSensorChanged(event, this) + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} + override fun onDestroy() { super.onDestroy() paymentInitListener = null diff --git a/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrame.kt b/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrame.kt index 0b248adb17..a1e6c2bd95 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrame.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/home/ui/screen/HomeContentFrame.kt @@ -102,7 +102,9 @@ fun HomeContentFrame( drawerState.open() activity.fetchProfileItems(isProfileDrawerOpen = true) } + activity.homeSensorManager.get().unregisterListener(activity, naviHomeAnalytics) } else { + activity.homeSensorManager.get().registerListener(activity, naviHomeAnalytics) if (drawerState.isOpen) drawerState.close() } } diff --git a/android/app/src/main/java/com/naviapp/home/usecase/HomeSensorManager.kt b/android/app/src/main/java/com/naviapp/home/usecase/HomeSensorManager.kt new file mode 100644 index 0000000000..d782814f85 --- /dev/null +++ b/android/app/src/main/java/com/naviapp/home/usecase/HomeSensorManager.kt @@ -0,0 +1,198 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.naviapp.home.usecase + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorManager +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import androidx.appcompat.app.AppCompatActivity.SENSOR_SERVICE +import com.navi.base.model.CtaData +import com.navi.base.model.LineItem +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.HOMEPAGE_SHAKE_MIN_RAM_THRESHOLD +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.HOMEPAGE_SHAKE_THRESHOLD_GRAVITY +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.REGISTER_SHAKE_LISTENER_ON_HOMEPAGE +import com.navi.common.utils.Constants.SCREEN_SOURCE +import com.navi.common.utils.Constants.SHAKE_ON_HOME_SCREEN +import com.navi.common.utils.getTotalRamMemory +import com.navi.pay.common.model.view.NaviPayScreenType +import com.navi.pay.utils.NAVI_PAY_CTA_URL_PREFIX +import com.naviapp.analytics.utils.NaviAnalytics +import com.naviapp.common.navigator.NaviDeepLinkNavigator +import com.naviapp.home.compose.activity.HomePageActivity +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import kotlin.math.pow +import kotlin.math.sqrt + +/** + * Manager class responsible for handling device shake detection using the accelerometer sensor. It + * triggers QR scanner navigation when the device is shaken. Key shake detection parameters (e.g., + * sensitivity threshold, shake count) are configurable, with some values potentially sourced from + * Firebase Remote Config. + */ +class HomeSensorManager @Inject constructor(@ApplicationContext private val context: Context) { + private lateinit var sensorManager: SensorManager + private var sensor: Sensor? = null + private lateinit var vibrator: Vibrator + + private var lastShakeTimestamp: Long = 0 + private var previousAcceleration = AccelerationValues() + private var consecutiveShakeCount = 0 + private var isDeviceUpright = false + private val totalRamMemory = getTotalRamMemory(context = context)?.toDoubleOrNull() ?: 100.0 + + private val gravityThresholdY = 2f + + private val shouldRegisterShake = + FirebaseRemoteConfigHelper.getBoolean( + key = REGISTER_SHAKE_LISTENER_ON_HOMEPAGE, + defaultValue = false, + ) + + private val shakeThresholdGravity: Float = + FirebaseRemoteConfigHelper.getLong( + key = HOMEPAGE_SHAKE_THRESHOLD_GRAVITY, + defaultValue = 10L, + ) + .toFloat() + + private val minRamThreshold: Double = + FirebaseRemoteConfigHelper.getDouble( + key = HOMEPAGE_SHAKE_MIN_RAM_THRESHOLD, + defaultValue = 4.0, + ) + + private data class AccelerationValues(var x: Float = 0f, var y: Float = 0f, var z: Float = 0f) + + companion object { + private const val SHAKE_COOLDOWN_MS = 400 + private const val REQUIRED_SHAKE_COUNT = 2 + private const val SHAKE_RESET_TIMEOUT_MS = 1500 + } + + /** + * Initializes the sensor manager and accelerometer. Shows a toast message if the device doesn't + * have an accelerometer. + * + * @param activity The HomePageActivity instance + */ + fun initSensorManager(activity: HomePageActivity) { + sensorManager = activity.getSystemService(SENSOR_SERVICE) as SensorManager + vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + } + + /** + * Handles sensor change events to detect device shakes. Triggers QR scanner navigation after + * detecting required number of consecutive shakes. + * + * @param event The sensor event containing acceleration data + * @param activity The HomePageActivity instance + */ + fun onSensorChanged(event: SensorEvent, activity: HomePageActivity) { + if (event.sensor?.type != Sensor.TYPE_ACCELEROMETER) return + + val currentTime = System.currentTimeMillis() + val timeSinceLastShake = currentTime - lastShakeTimestamp + + // Check if enough time has passed since the last shake + if (timeSinceLastShake > SHAKE_COOLDOWN_MS) { + val currentAcceleration = + AccelerationValues(x = event.values[0], y = event.values[1], z = event.values[2]) + + // Check if device is upright using gravity thresholds + isDeviceUpright = + currentAcceleration.let { acc -> acc.y > gravityThresholdY && acc.z > 0 } + + val accelerationMagnitude = calculateAccelerationMagnitude(currentAcceleration) + + when { + accelerationMagnitude > shakeThresholdGravity && isDeviceUpright -> { + handleShakeDetected(currentTime, activity) + } + timeSinceLastShake > SHAKE_RESET_TIMEOUT_MS -> { + consecutiveShakeCount = 0 + } + } + + previousAcceleration = currentAcceleration + } + } + + private fun calculateAccelerationMagnitude(current: AccelerationValues): Float { + return sqrt( + (current.x - previousAcceleration.x).pow(2) + + (current.y - previousAcceleration.y).pow(2) + + (current.z - previousAcceleration.z).pow(2) + ) + } + + private fun handleShakeDetected(currentTime: Long, activity: HomePageActivity) { + consecutiveShakeCount++ + lastShakeTimestamp = currentTime + + if (consecutiveShakeCount >= REQUIRED_SHAKE_COUNT) { + // Vibrate for 200ms + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate( + VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE) + ) + } else { + vibrator.vibrate(200) + } + navigateToNaviPayQRScanner(activity) + consecutiveShakeCount = 0 + } + } + + /** + * Registers the sensor listener when the activity resumes. + * + * @param activity The HomePageActivity instance + */ + fun registerListener(activity: HomePageActivity, analyticsEventTracker: NaviAnalytics.Home) { + if (shouldRegisterShake && sensor != null && totalRamMemory >= minRamThreshold) { + analyticsEventTracker.onShakeListenerRegister() + sensorManager.registerListener(activity, sensor, SensorManager.SENSOR_DELAY_UI) + } + } + + /** + * Unregisters the sensor listener when the activity is paused to conserve battery. + * + * @param activity The HomePageActivity instance + */ + fun unregisterListener(activity: HomePageActivity, analyticsEventTracker: NaviAnalytics.Home) { + if (shouldRegisterShake && sensor != null && totalRamMemory >= minRamThreshold) { + analyticsEventTracker.onShakeListenerUnregister() + sensorManager.unregisterListener(activity) + } + } + + /** + * Navigates to the Navi Pay QR scanner screen. + * + * @param activity The HomePageActivity instance + */ + private fun navigateToNaviPayQRScanner(activity: HomePageActivity) { + val url = "$NAVI_PAY_CTA_URL_PREFIX${NaviPayScreenType.NAVI_PAY_QR_SCANNER_SCREEN.name}" + + val ctaData = + CtaData( + url = url, + parameters = listOf(LineItem(key = SCREEN_SOURCE, value = SHAKE_ON_HOME_SCREEN)), + ) + + NaviDeepLinkNavigator.navigate(activity = activity, ctaData = ctaData, needsResult = true) + } +} diff --git a/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt b/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt index b1eca5964a..d3c604171f 100644 --- a/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt +++ b/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt @@ -151,6 +151,7 @@ object FirebaseRemoteConfigHelper { const val AUTO_REDIRECT_TO_HOME_PAGE_LIMIT_TIME_IN_MIN = "AUTO_REDIRECT_TO_HOME_PAGE_LIMIT_TIME_IN_MIN" + const val NAVI_PAY_CLOSE_AFTER_SHAKE_DELAY_MS = "NAVI_PAY_CLOSE_AFTER_SHAKE_DELAY_MS" // COMMON const val LITMUS_EXPERIMENTS_CACHE_DURATION_IN_MILLIS = @@ -328,6 +329,9 @@ object FirebaseRemoteConfigHelper { const val ENABLE_DELAYED_HP_WIDGETS_RENDERING = "ENABLE_DELAYED_HP_WIDGETS_RENDERING" const val HOMEPAGE_WIDGET_RENDER_DELAY_MS = "HOMEPAGE_WIDGET_RENDER_DELAY_MS" const val HOMEPAGE_SCRATCH_CARD_RENDER_DELAY_MS = "HOMEPAGE_SCRATCH_CARD_RENDER_DELAY_MS" + const val REGISTER_SHAKE_LISTENER_ON_HOMEPAGE = "REGISTER_SHAKE_LISTENER_ON_HOMEPAGE" + const val HOMEPAGE_SHAKE_THRESHOLD_GRAVITY = "HOMEPAGE_SHAKE_THRESHOLD_GRAVITY" + const val HOMEPAGE_SHAKE_MIN_RAM_THRESHOLD = "HOMEPAGE_SHAKE_MIN_RAM_THRESHOLD" private fun getFirebaseRemoteConfig(): FirebaseRemoteConfig { val remoteConfig = Firebase.remoteConfig @@ -434,6 +438,16 @@ object FirebaseRemoteConfigHelper { } } + fun getDouble(key: String, defaultValue: Double): Double { + return try { + val rawValue = remoteConfig.getValue(key) + if (rawValue.source == FirebaseRemoteConfig.VALUE_SOURCE_STATIC) defaultValue + else rawValue.asDouble() + } catch (_: Exception) { + defaultValue + } + } + fun getLong(key: String): Long { return try { remoteConfig.getLong(key) diff --git a/android/navi-common/src/main/java/com/navi/common/utils/Constants.kt b/android/navi-common/src/main/java/com/navi/common/utils/Constants.kt index 593772563f..5dd179058e 100644 --- a/android/navi-common/src/main/java/com/navi/common/utils/Constants.kt +++ b/android/navi-common/src/main/java/com/navi/common/utils/Constants.kt @@ -228,6 +228,8 @@ object Constants { const val DEFAULT_ADVERSE_EVENT_SYNC_INTERVAL_IN_SECS = 30L const val DEFAULT_ADVERSE_NETWORK_TIMEOUT = 10L const val ONE_DAY_IN_MILLIS = 86400000L + const val SCREEN_SOURCE = "SCREEN_SOURCE" + const val SHAKE_ON_HOME_SCREEN = "SHAKE_ON_HOME_SCREEN" const val SAPHYRA_APP_LOGIN_EVENT = "APP_LOGIN_EVENT" diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/analytics/NaviPayAnalytics.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/analytics/NaviPayAnalytics.kt index f05271c34e..b69459ae1d 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/analytics/NaviPayAnalytics.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/analytics/NaviPayAnalytics.kt @@ -2035,6 +2035,49 @@ class NaviPayAnalytics private constructor() { ), ) } + + fun onScreenLandAfterHomePageShake( + naviPaySessionAttributes: Map, + isNaviPayActivity: Boolean, + ) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviPay_QrScanner_AfterHomePageShake", + eventValues = + mapOf( + "naviPaySessionId" to + naviPaySessionAttributes["naviPaySessionId"].orEmpty(), + "naviPayCustomerStatusMap" to + naviPaySessionAttributes["naviPayCustomerStatusMap"].orEmpty(), + "isNaviPayActivity" to isNaviPayActivity.toString(), + ), + ) + } + + fun onAutoCloseAfterHomePageShake(naviPaySessionAttributes: Map) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviPay_QrScanner_AutoCloseAfterHomePageShake", + eventValues = + mapOf( + "naviPaySessionId" to + naviPaySessionAttributes["naviPaySessionId"].orEmpty(), + "naviPayCustomerStatusMap" to + naviPaySessionAttributes["naviPayCustomerStatusMap"].orEmpty(), + ), + ) + } + + fun onQrBackPressedAfterHomePageShake(naviPaySessionAttributes: Map) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "NaviPay_QrScanner_BackPressedAfterHomePageShake", + eventValues = + mapOf( + "naviPaySessionId" to + naviPaySessionAttributes["naviPaySessionId"].orEmpty(), + "naviPayCustomerStatusMap" to + naviPaySessionAttributes["naviPayCustomerStatusMap"].orEmpty(), + ), + ) + } } inner class NaviPayBankDetailsInput { diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/ui/QrScannerScreen.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/ui/QrScannerScreen.kt index e4be9bca96..59e8e19889 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/ui/QrScannerScreen.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/ui/QrScannerScreen.kt @@ -94,6 +94,8 @@ import com.google.mlkit.vision.common.InputImage import com.navi.analytics.utils.AlfredFacade import com.navi.common.R as CommonR import com.navi.common.ui.activity.NaviCoreActivity +import com.navi.common.utils.Constants.SCREEN_SOURCE +import com.navi.common.utils.Constants.SHAKE_ON_HOME_SCREEN import com.navi.common.utils.TemporaryStorageHelper import com.navi.common.utils.log import com.navi.common.utils.navigateUp @@ -400,7 +402,13 @@ fun QrScannerScreenContent( val localHapticFeedback = LocalHapticFeedback.current - BackHandler { onBackClick() } + BackHandler { + triggerBackPressEventAfterShake( + naviPayAnalytics = naviPayAnalytics, + qrScannerViewModel = qrScannerViewModel, + ) + onBackClick() + } val closeSheet: () -> Unit = { scope @@ -521,6 +529,21 @@ fun QrScannerScreenContent( } } + LaunchedEffect(Unit) { + if (isScreenSourceHomePageShake(qrScannerViewModel)) { + naviPayAnalytics.onScreenLandAfterHomePageShake( + naviPaySessionAttributes = qrScannerViewModel.getNaviPaySessionAttributes(), + isNaviPayActivity = activity is NaviPayActivity, + ) + qrScannerViewModel.registerAutoFinishAfterInActivity { + naviPayAnalytics.onAutoCloseAfterHomePageShake( + naviPaySessionAttributes = qrScannerViewModel.getNaviPaySessionAttributes() + ) + activity.finish() + } + } + } + val colorAdjustedContent = remember(cameraPermissionsState.allPermissionsGranted) { if (cameraPermissionsState.allPermissionsGranted) { @@ -649,7 +672,10 @@ fun QrScannerScreenContent( Spacer(modifier = Modifier.height(toolbarTopMargin)) NavigationIconWithOtherAppsView( modifier = Modifier.fillMaxWidth().padding(16.dp), - onBackClick = onBackClick, + onBackClick = { + triggerBackPressEventAfterShake(qrScannerViewModel, naviPayAnalytics) + onBackClick() + }, cameraPermissionsGranted = cameraPermissionsState.allPermissionsGranted, genericOffersList = genericOffersList, onOffersStripClicked = qrScannerViewModel::onOffersStripClicked, @@ -1262,3 +1288,19 @@ private fun navigateToGlobal( ) } } + +private fun isScreenSourceHomePageShake(qrScannerViewModel: QrScannerViewModel): Boolean { + return qrScannerViewModel.naviPayActivityDataProvider.getString(SCREEN_SOURCE) == + SHAKE_ON_HOME_SCREEN +} + +private fun triggerBackPressEventAfterShake( + qrScannerViewModel: QrScannerViewModel, + naviPayAnalytics: NaviPayAnalytics.NaviPayQrScanner, +) { + if (isScreenSourceHomePageShake(qrScannerViewModel)) { + naviPayAnalytics.onQrBackPressedAfterHomePageShake( + naviPaySessionAttributes = qrScannerViewModel.getNaviPaySessionAttributes() + ) + } +} diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/viewmodel/QrScannerViewModel.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/viewmodel/QrScannerViewModel.kt index 71634d1b84..ead9d8e45b 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/viewmodel/QrScannerViewModel.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/viewmodel/QrScannerViewModel.kt @@ -19,6 +19,7 @@ import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_AUTO_FLASH_DISABLED import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_AUTO_FLASH_LIGHT_LUX_THRESHOLD import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_AUTO_ZOOM_DELAY_IN_SECONDS +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_CLOSE_AFTER_SHAKE_DELAY_MS import com.navi.common.usecase.LitmusExperimentsUseCase import com.navi.pay.R import com.navi.pay.analytics.NaviPayAnalytics @@ -55,6 +56,7 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -135,6 +137,8 @@ constructor( FirebaseRemoteConfigHelper.NAVI_PAY_UPI_GLOBAL_ENABLED ) + private var autoCloseJob: Job? = null + init { init() } @@ -146,6 +150,22 @@ constructor( observeAutoZoomChanges() } + fun registerAutoFinishAfterInActivity(onDelayEnd: () -> Unit) { + autoCloseJob?.cancel() + autoCloseJob = + viewModelScope.launch(coroutineDispatcherProvider.default) { + val delay = + FirebaseRemoteConfigHelper.getLong( + key = NAVI_PAY_CLOSE_AFTER_SHAKE_DELAY_MS, + defaultValue = 5000, + ) + delay(delay) // Auto-finish after delay if no action is performed + if (!isAnyScreenActionPerformed.value) { + onDelayEnd() + } + } + } + private fun observeAutoZoomChanges() { viewModelScope.launch(coroutineDispatcherProvider.io) { val delayForAutoZoom = @@ -163,6 +183,10 @@ constructor( fun toggleIsAnyActionPerformed(value: Boolean = true) { isAnyScreenActionPerformed.update { value } + if (value) { + // Cancel auto-finish if user performs any action + autoCloseJob?.cancel() + } } fun toggleTriggerAutoZoom(value: Boolean) {