diff --git a/android/navi-coin/src/main/java/com/navi/coin/ui/compose/screen/RedemptionStatusScreen.kt b/android/navi-coin/src/main/java/com/navi/coin/ui/compose/screen/RedemptionStatusScreen.kt index c18c089477..0c75f0cca3 100644 --- a/android/navi-coin/src/main/java/com/navi/coin/ui/compose/screen/RedemptionStatusScreen.kt +++ b/android/navi-coin/src/main/java/com/navi/coin/ui/compose/screen/RedemptionStatusScreen.kt @@ -8,13 +8,13 @@ package com.navi.coin.ui.compose.screen import android.Manifest.permission.READ_CONTACTS -import android.app.Activity import android.os.Bundle import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -23,16 +23,18 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState -import com.navi.base.deeplink.DeepLinkManager import com.navi.base.model.CtaData import com.navi.base.model.CtaType import com.navi.base.utils.isNotNull import com.navi.base.utils.orFalse +import com.navi.coin.ui.activity.CoinBaseActivity import com.navi.coin.ui.compose.common.Init +import com.navi.coin.utils.constant.Constants.IS_EXPERIMENT_ENABLED import com.navi.coin.utils.constant.Constants.SCREENS.COINS_LOADING_SCREEN_SCREEN_NAME import com.navi.coin.utils.constant.Constants.SCREEN_CONTENT import com.navi.coin.vm.RedemptionStatusVM @@ -55,80 +57,74 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator fun RedemptionStatusScreen( bundle: Bundle? = null, navigator: DestinationsNavigator, - redemptionStatusVM: RedemptionStatusVM = hiltViewModel(), + viewModel: RedemptionStatusVM = hiltViewModel(), ) { - val context = LocalContext.current as Activity + val context = LocalContext.current as CoinBaseActivity val screenContent = remember { getGsonBuilders().fromJson(bundle?.getString(SCREEN_CONTENT), ScreenDefinition::class.java) } var isDataReady by remember { mutableStateOf(false) } var isLottiePlayed by remember { mutableStateOf(false) } var isTransactionSuccessful by remember { mutableStateOf(false) } - var isExperimentEnabled by remember { - mutableStateOf(screenContent?.metaData?.get("isExperimentEnabled").toBoolean()) + val isExperimentEnabled by remember { + mutableStateOf(screenContent?.metaData?.get(IS_EXPERIMENT_ENABLED).toBoolean()) } + val lifecycleOwner = LocalLifecycleOwner.current Init( screenName = COINS_LOADING_SCREEN_SCREEN_NAME, activity = context, - viewModel = redemptionStatusVM, + viewModel = viewModel, navigator = navigator ) val contactsPermissionState = rememberPermissionState(permission = READ_CONTACTS) LaunchedEffect(key1 = contactsPermissionState) { if (isExperimentEnabled) { - redemptionStatusVM.isContactPermissionGranted = contactsPermissionState.status.isGranted - redemptionStatusVM.fetchUserData() + viewModel.isContactPermissionGranted = contactsPermissionState.status.isGranted + viewModel.fetchUserData() } } BackHandler {} + DisposableEffect(key1 = lifecycleOwner) { + val lifecycleEventObserver = viewModel.initLifecycleObserver(context) + lifecycleOwner.lifecycle.addObserver(lifecycleEventObserver) + onDispose { lifecycleOwner.lifecycle.removeObserver(lifecycleEventObserver) } + } + LaunchedEffect(key1 = isDataReady, key2 = isLottiePlayed, key3 = isTransactionSuccessful) { when { isExperimentEnabled.not() && isLottiePlayed -> { - navigator.popBackStack() - val cta = - CtaAction( - ctaData = - CtaData( - url = COINS_HISTORY_BOTTOMSHEET_CTA, - type = CtaType.REDIRECTION_CTA.name - ) + viewModel.nextScreenCtaData = + CtaData( + url = COINS_HISTORY_BOTTOMSHEET_CTA, + type = CtaType.REDIRECTION_CTA.name ) - cta.ctaData?.let { - DeepLinkManager.getDeepLinkListener() - ?.navigateTo(context, it, finish = false, bundle = bundle) + if (viewModel.shouldShowOneProfile(screenContent)) { + viewModel.initiateOneProfile(context) + } else { + viewModel.navigateToNextScreen(context) } } isExperimentEnabled && isLottiePlayed -> { - navigator.popBackStack() - val bundle = Bundle() - - var cta = - CtaAction( - ctaData = - CtaData( - url = COINS_HISTORY_BOTTOMSHEET_CTA, - type = CtaType.REDIRECTION_CTA.name - ) + viewModel.navigationBundle = Bundle() + viewModel.nextScreenCtaData = + CtaData( + url = COINS_HISTORY_BOTTOMSHEET_CTA, + type = CtaType.REDIRECTION_CTA.name ) - val listContact = redemptionStatusVM.getUserData() + val listContact = viewModel.getUserData() if (isTransactionSuccessful && listContact.isNotNull()) { - bundle.putString(USER_DATA, listContact.toJson()) - cta = - CtaAction( - ctaData = - CtaData( - url = COINS_HISTORY_CTA, - type = CtaType.REDIRECTION_CTA.name - ) - ) + viewModel.navigationBundle.putString(USER_DATA, listContact.toJson()) + viewModel.nextScreenCtaData = + CtaData(url = COINS_HISTORY_CTA, type = CtaType.REDIRECTION_CTA.name) } - cta.ctaData?.let { - DeepLinkManager.getDeepLinkListener() - ?.navigateTo(context, it, finish = false, bundle = bundle) + if (viewModel.shouldShowOneProfile(screenContent)) { + viewModel.initiateOneProfile(context) + } else { + viewModel.navigateToNextScreen(context) } isDataReady = false isLottiePlayed = false @@ -137,7 +133,7 @@ fun RedemptionStatusScreen( } LaunchedEffect(Unit) { - redemptionStatusVM.screenActions.collect { action -> + viewModel.screenActions.collect { action -> when (action) { is CtaAction -> { val url = action.ctaData?.url @@ -158,9 +154,9 @@ fun RedemptionStatusScreen( } } Box(modifier = Modifier.fillMaxSize().background(Color.White)) { - screenContent?.metaData?.get(SHARE_MESSAGE)?.let { redemptionStatusVM.shareMessage = it } + screenContent?.metaData?.get(SHARE_MESSAGE)?.let { viewModel.shareMessage = it } screenContent?.screenStructure?.content?.widgets?.forEach { uiTronWidget -> - WidgetRenderer(widget = uiTronWidget, viewModel = redemptionStatusVM) + WidgetRenderer(widget = uiTronWidget, viewModel = viewModel) } } } diff --git a/android/navi-coin/src/main/java/com/navi/coin/utils/analytics/EventConstants.kt b/android/navi-coin/src/main/java/com/navi/coin/utils/analytics/EventConstants.kt new file mode 100644 index 0000000000..16ca9036e8 --- /dev/null +++ b/android/navi-coin/src/main/java/com/navi/coin/utils/analytics/EventConstants.kt @@ -0,0 +1,13 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.coin.utils.analytics + +object EventConstants { + const val ONE_PROFILE_INIT_EVENT = "naviapp_oneprofile_onboarding_init" + const val ORIGIN_SCREEN = "origin_screen" +} diff --git a/android/navi-coin/src/main/java/com/navi/coin/utils/constant/Constants.kt b/android/navi-coin/src/main/java/com/navi/coin/utils/constant/Constants.kt index 7159cc7403..90814bea03 100644 --- a/android/navi-coin/src/main/java/com/navi/coin/utils/constant/Constants.kt +++ b/android/navi-coin/src/main/java/com/navi/coin/utils/constant/Constants.kt @@ -47,6 +47,18 @@ object Constants { const val REDEMPTION_STATUS_SCREEN_CTA_URL = "coin/REDEMPTION_STATUS_SCREEN" const val TOTAL_CASH_REDEEMED = "totalCashRedeemed" const val PHONE_NUMBER = "PhoneNumber" + const val HPC = "HPC" + const val VERTICAL_TYPE = "verticalType" + const val RNR = "RNR" + const val APPLICANT_TYPE = "applicantType" + const val CUSTOMER = "CUSTOMER" + const val JOURNEY_TYPE = "journeyType" + const val NON_ONBOARDING = "NON-ONBOARDING" + const val SOURCE = "source" + const val IS_SKIPPABLE = "isSkippable" + const val TRUE = "true" + const val SHOULD_SHOW_ONE_PROFILE = "should_show_op" + const val IS_EXPERIMENT_ENABLED = "isExperimentEnabled" enum class BottomSheetAction { SHOW, diff --git a/android/navi-coin/src/main/java/com/navi/coin/vm/RedemptionStatusVM.kt b/android/navi-coin/src/main/java/com/navi/coin/vm/RedemptionStatusVM.kt index 56923d4443..a28a50925a 100644 --- a/android/navi-coin/src/main/java/com/navi/coin/vm/RedemptionStatusVM.kt +++ b/android/navi-coin/src/main/java/com/navi/coin/vm/RedemptionStatusVM.kt @@ -7,25 +7,96 @@ package com.navi.coin.vm +import android.os.Bundle +import androidx.core.os.bundleOf +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewModelScope import com.navi.base.model.CtaData import com.navi.base.model.CtaType +import com.navi.base.model.LineItem +import com.navi.base.utils.orFalse +import com.navi.coin.ui.activity.CoinBaseActivity +import com.navi.coin.ui.compose.screen.destinations.RedemptionStatusScreenDestination import com.navi.coin.usecase.ContactUseCase +import com.navi.coin.utils.analytics.EventConstants.ONE_PROFILE_INIT_EVENT +import com.navi.coin.utils.analytics.EventConstants.ORIGIN_SCREEN +import com.navi.coin.utils.constant.Constants.APPLICANT_TYPE +import com.navi.coin.utils.constant.Constants.CUSTOMER +import com.navi.coin.utils.constant.Constants.HPC +import com.navi.coin.utils.constant.Constants.IS_SKIPPABLE +import com.navi.coin.utils.constant.Constants.JOURNEY_TYPE +import com.navi.coin.utils.constant.Constants.NON_ONBOARDING +import com.navi.coin.utils.constant.Constants.RNR +import com.navi.coin.utils.constant.Constants.SCREENS.TRANSACTION_HISTORY_COINS_SCREEN_NAME +import com.navi.coin.utils.constant.Constants.SHOULD_SHOW_ONE_PROFILE +import com.navi.coin.utils.constant.Constants.SOURCE +import com.navi.coin.utils.constant.Constants.TRUE +import com.navi.coin.utils.constant.Constants.VERTICAL_TYPE +import com.navi.coin.utils.navigateTo +import com.navi.common.forge.model.ScreenDefinition +import com.navi.common.navigation.NavArgs +import com.navi.common.navigation.NavigationAction import com.navi.common.uitron.model.action.CtaAction import com.navi.common.uitron.model.action.FetchUserRewardsDataAction +import com.navi.common.utils.Constants.APP_PLATFORM_APPLICATION_TYPE +import com.navi.naviwidgets.utils.AP_LAUNCH import com.navi.rr.referral.models.ReferralContactList +import com.navi.rr.utils.NaviRRAnalytics import com.navi.rr.utils.constants.Constants +import com.navi.rr.utils.constants.ReferralHomeConstants.COINS_HISTORY_BOTTOMSHEET_CTA import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow @HiltViewModel class RedemptionStatusVM @Inject constructor(private val contactUseCase: ContactUseCase) : CoinBaseVM() { - private var userContactsAndRewards: ReferralContactList? = null var isContactPermissionGranted: Boolean = false var shareMessage: String = "" + var nextScreenCtaData: CtaData = + CtaData(url = COINS_HISTORY_BOTTOMSHEET_CTA, type = CtaType.REDIRECTION_CTA.name) + var navigationBundle: Bundle = bundleOf() + + var isOneProfileJourneyInitiated = MutableStateFlow(false) + var shouldPopBackStack = MutableStateFlow(false) + + fun initLifecycleObserver(context: CoinBaseActivity): LifecycleEventObserver { + val observer = LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_PAUSE -> { + if (isOneProfileJourneyInitiated.value) { + shouldPopBackStack.value = true + } + } + Lifecycle.Event.ON_RESUME -> { + if (isOneProfileJourneyInitiated.value && shouldPopBackStack.value) { + shouldPopBackStack.value = false + isOneProfileJourneyInitiated.value = false + navigateToNextScreen(context) + } + } + else -> Unit + } + } + return observer + } + + fun navigateToNextScreen(context: CoinBaseActivity) { + navigateTo( + activity = context, + navHostOwner = context, + navArgs = NavArgs(ctaData = nextScreenCtaData, bundle = navigationBundle), + navAction = + NavigationAction.NextWithPopUpTo( + route = RedemptionStatusScreenDestination.route, + inclusive = true + ) + ) + } + suspend fun fetchUserData(apiAction: FetchUserRewardsDataAction? = null) { contactUseCase.fetchUserData( apiAction = apiAction, @@ -54,4 +125,47 @@ class RedemptionStatusVM @Inject constructor(private val contactUseCase: Contact userContactsAndRewards?.shareMessage = shareMessage return userContactsAndRewards } + + fun shouldShowOneProfile(screenDefinition: ScreenDefinition): Boolean { + return screenDefinition.metaData?.get(SHOULD_SHOW_ONE_PROFILE).toBoolean().orFalse() + } + + fun initiateOneProfile(context: CoinBaseActivity) { + isOneProfileJourneyInitiated.value = true + val listParams = + listOf( + LineItem(APP_PLATFORM_APPLICATION_TYPE, HPC), + LineItem(VERTICAL_TYPE, RNR), + LineItem(APPLICANT_TYPE, CUSTOMER) + ) + val listAdditionalParams = + listOf( + LineItem(JOURNEY_TYPE, NON_ONBOARDING), + LineItem(SOURCE, RNR), + LineItem(IS_SKIPPABLE, TRUE) + ) + NaviRRAnalytics.naviRRAnalytics + .Rewards() + .sendEvent( + eventName = ONE_PROFILE_INIT_EVENT, + extraAttributes = + hashMapOf( + ORIGIN_SCREEN to TRANSACTION_HISTORY_COINS_SCREEN_NAME, + IS_SKIPPABLE to TRUE + ) + ) + navigateTo( + activity = context, + navHostOwner = context, + navArgs = + NavArgs( + ctaData = + CtaData( + url = AP_LAUNCH, + parameters = listParams, + additionalParameters = listAdditionalParams + ) + ) + ) + } } diff --git a/android/navi-common/src/main/java/com/navi/common/navigation/NavigationAction.kt b/android/navi-common/src/main/java/com/navi/common/navigation/NavigationAction.kt index 7322533eb0..49dd9558d4 100644 --- a/android/navi-common/src/main/java/com/navi/common/navigation/NavigationAction.kt +++ b/android/navi-common/src/main/java/com/navi/common/navigation/NavigationAction.kt @@ -19,4 +19,7 @@ sealed class NavigationAction { data object BackWithRefresh : NavigationAction() data object Default : NavigationAction() + + data class NextWithPopUpTo(val route: String, val inclusive: Boolean = false) : + NavigationAction() } diff --git a/android/navi-common/src/main/java/com/navi/common/navigation/navigator/DefaultComposableNavigator.kt b/android/navi-common/src/main/java/com/navi/common/navigation/navigator/DefaultComposableNavigator.kt index 5bd8d35c0b..352f73dfa9 100644 --- a/android/navi-common/src/main/java/com/navi/common/navigation/navigator/DefaultComposableNavigator.kt +++ b/android/navi-common/src/main/java/com/navi/common/navigation/navigator/DefaultComposableNavigator.kt @@ -66,6 +66,21 @@ class DefaultComposableNavigatorImpl @Inject constructor() : ComposableNavigator ?.set(COMPOSABLE_ID, composableId) } + private fun nextWithPopUpTo( + navHostOwner: NavHostControllerOwner, + direction: Direction, + composableId: String, + route: String, + inclusive: Boolean + ) { + navHostOwner.navController.navigate(direction.route) { + popUpTo(route = route) { this.inclusive = inclusive } + } + navHostOwner.navController.currentBackStackEntry + ?.savedStateHandle + ?.set(COMPOSABLE_ID, composableId) + } + @SuppressLint("RestrictedApi") override fun navigate( navHostOwner: NavHostControllerOwner, @@ -104,6 +119,15 @@ class DefaultComposableNavigatorImpl @Inject constructor() : ComposableNavigator direction = direction, composableId = composableId ) + is NavigationAction.NextWithPopUpTo -> { + nextWithPopUpTo( + navHostOwner = navHostOwner, + direction = direction, + composableId = composableId, + route = action.route, + inclusive = action.inclusive + ) + } } } }