TP-83681 | Naman Khurmi | Home mvi startup optimization (#12575)
Signed-off-by: namankhurmi <naman.khurmi@navi.com> Co-authored-by: Girish Suragani <girish.suragani@navi.com>
This commit is contained in:
@@ -77,7 +77,7 @@ constructor(private val bottomNavBarRepository: BottomNavBarRepository) : BaseVM
|
||||
_updateTabSelection.resetReplayCache()
|
||||
}
|
||||
|
||||
fun getBottomNudgeLatestState() = bottomStickyNudgeData.value.bottomStickyNudgeState
|
||||
fun getBottomNudgeLatestState() = _bottomStickyNudgeData.value.bottomStickyNudgeState
|
||||
|
||||
private fun getGiNavCta(): CtaData? {
|
||||
return _giNavCtaFlow.value?.cta
|
||||
|
||||
@@ -9,26 +9,21 @@ package com.naviapp.home.common.actions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.navi.base.utils.orTrue
|
||||
import com.navi.common.uitron.model.action.V3HomeAction
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
|
||||
@Composable
|
||||
fun HandleApiAction(
|
||||
viewModel: HomeVM,
|
||||
viewModel: HomeViewModel,
|
||||
activity: HomePageActivity,
|
||||
naviAnalyticsEventTracker: NaviAnalytics.Home,
|
||||
isPaymentLoaderShowing: Boolean
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.getActionCallback().collect { action ->
|
||||
when (action) {
|
||||
is V3HomeAction -> {
|
||||
viewModel.fetchCards(
|
||||
showLoader = action.showLoader.orTrue(),
|
||||
naviAnalyticsEventTracker = naviAnalyticsEventTracker,
|
||||
viewModel.loadHomeElements(
|
||||
activity = activity,
|
||||
isPaymentLoaderShowing = isPaymentLoaderShowing
|
||||
)
|
||||
|
||||
@@ -12,11 +12,11 @@ import com.navi.ap.common.handler.HandlePublishEventAction
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.home.common.actions.HandleApiAction
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
|
||||
@Composable
|
||||
fun InitActionsHandler(
|
||||
viewModel: HomeVM,
|
||||
viewModel: HomeViewModel,
|
||||
activity: HomePageActivity,
|
||||
naviAnalyticsEventTracker: NaviAnalytics.Home,
|
||||
isPaymentLoaderShowing: Boolean
|
||||
@@ -25,7 +25,6 @@ fun InitActionsHandler(
|
||||
HandleApiAction(
|
||||
viewModel = viewModel,
|
||||
activity = activity,
|
||||
naviAnalyticsEventTracker = naviAnalyticsEventTracker,
|
||||
isPaymentLoaderShowing = isPaymentLoaderShowing
|
||||
)
|
||||
HandleInstallDynamicModulesAction(
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.common.handler
|
||||
|
||||
import android.Manifest
|
||||
import com.google.gson.Gson
|
||||
import com.navi.base.sharedpref.PreferenceManager
|
||||
import com.navi.base.utils.BaseUtils
|
||||
import com.navi.common.model.ModuleNameV2
|
||||
import com.navi.common.network.ApiConstants
|
||||
import com.navi.common.utils.TemporaryStorageHelper
|
||||
import com.navi.uitron.model.action.UpdateDataAction
|
||||
import com.navi.uitron.model.action.UpdateViewStateAction
|
||||
import com.navi.uitron.model.data.TextData
|
||||
import com.navi.uitron.model.data.UiTronActionData
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.common.transformer.AppLoadTimerMapper
|
||||
import com.naviapp.home.compose.model.CtaActionEvent
|
||||
import com.naviapp.home.compose.model.InitiatePaymentFromComposeData
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.network.di.DataDeserializers
|
||||
import com.naviapp.utils.Constants
|
||||
import com.naviapp.utils.Constants.HomePageConstants.NAVI_APP_NAV_HOME_PAGE_VIEWED
|
||||
import com.naviapp.utils.Constants.HomePageConstants.NAVI_APP_NAV_INSURANCE_PAGE_VIEWED
|
||||
import com.naviapp.utils.Constants.HomePageConstants.NAVI_APP_NAV_INVESTMENT_PAGE_VIEWED
|
||||
import com.naviapp.utils.Constants.HomePageConstants.NAVI_APP_NAV_LOAN_PAGE_VIEWED
|
||||
import com.naviapp.utils.Constants.Notification.HIDE_NOTIFICATION_COUNT
|
||||
import com.naviapp.utils.Constants.Notification.NINE_PLUS
|
||||
import com.naviapp.utils.Constants.Notification.NOTIFICATION_COUNT_TEXT
|
||||
import com.naviapp.utils.Constants.Notification.SHOW_NOTIFICATION_COUNT
|
||||
import com.naviapp.utils.NOTIFICATION_PERMISSION_SHOWN
|
||||
import javax.inject.Inject
|
||||
|
||||
class EffectHandler
|
||||
@Inject
|
||||
constructor(
|
||||
@DataDeserializers private val dataDeserializers: Gson,
|
||||
private val dashBoardAnalytics: NaviAnalytics.Dashboard,
|
||||
private val selectiveRefreshHandler: SelectiveRefreshHandler,
|
||||
) {
|
||||
private val naviAnalyticsEventTracker = lazy { NaviAnalytics.naviAnalytics.Home() }
|
||||
|
||||
suspend fun handleEffects(
|
||||
effects: HpEffects,
|
||||
homeVM: HomeViewModel,
|
||||
onPaymentInitiated: (InitiatePaymentFromComposeData) -> Unit,
|
||||
onFetchHomeApiCall: () -> Unit,
|
||||
handleCtaAction: (CtaActionEvent) -> Unit
|
||||
) {
|
||||
when (effects) {
|
||||
is HpEffects.OnActionData -> homeVM.handleActions(effects.actionData)
|
||||
is HpEffects.OnUitronAction -> homeVM.handleAction(effects.action)
|
||||
is HpEffects.OnActionsFromJson -> handleActionsFromJson(effects, homeVM)
|
||||
is HpEffects.TrackBottomBarEvents -> trackBottomBarEvents()
|
||||
is HpEffects.ShowNotificationPermissions -> handleNotificationPermissions(effects)
|
||||
is HpEffects.OnNotificationUpdatedCount -> handleNotificationCount(effects, homeVM)
|
||||
is HpEffects.OnRenderActions -> handleRenderActions(homeVM)
|
||||
is HpEffects.OnApiFailure -> handleApiFailure(effects, homeVM)
|
||||
is HpEffects.LogAppLaunchTime -> logAppLaunchTime(effects)
|
||||
is HpEffects.OnHideBalanceAction -> handleHideBalanceAction(homeVM)
|
||||
is HpEffects.OnPrioritySectionRendered -> homeVM.handleRemainingItemsForFirstLoad()
|
||||
is HpEffects.InitiatePayment -> onPaymentInitiated(effects.data)
|
||||
is HpEffects.FetchHomeApi -> onFetchHomeApiCall()
|
||||
is HpEffects.HandleCtaActionEvents -> handleCtaAction(effects.event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleActionsFromJson(effects: HpEffects.OnActionsFromJson, homeVM: HomeViewModel) {
|
||||
TemporaryStorageHelper.setIsDataModified(
|
||||
screen = TemporaryStorageHelper.HOME,
|
||||
isDataModified = false
|
||||
)
|
||||
homeVM.handleActionsFromJson(
|
||||
actionsString = effects.handler?.actionsString.orEmpty(),
|
||||
variableMap = effects.handler?.variableMap.orEmpty(),
|
||||
gson = dataDeserializers
|
||||
)
|
||||
}
|
||||
|
||||
private fun trackBottomBarEvents() {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
val events =
|
||||
listOf(
|
||||
NAVI_APP_NAV_HOME_PAGE_VIEWED to "1",
|
||||
NAVI_APP_NAV_INVESTMENT_PAGE_VIEWED to "2",
|
||||
NAVI_APP_NAV_LOAN_PAGE_VIEWED to "3",
|
||||
NAVI_APP_NAV_INSURANCE_PAGE_VIEWED to "4"
|
||||
)
|
||||
events.forEach { (event, position) ->
|
||||
dashBoardAnalytics.trackEventWithProperties(
|
||||
event,
|
||||
mapOf(NaviAnalytics.POSITION to position)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNotificationPermissions(effects: HpEffects.ShowNotificationPermissions) {
|
||||
effects.manager.let { manager ->
|
||||
if (
|
||||
!manager.hasPermission(Manifest.permission.POST_NOTIFICATIONS) &&
|
||||
BaseUtils.isUserLoggedIn() &&
|
||||
!PreferenceManager.getBooleanPreference(NOTIFICATION_PERMISSION_SHOWN, false)
|
||||
) {
|
||||
PreferenceManager.setBooleanPreference(NOTIFICATION_PERMISSION_SHOWN, true)
|
||||
manager.requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNotificationCount(
|
||||
effects: HpEffects.OnNotificationUpdatedCount,
|
||||
homeVM: HomeViewModel
|
||||
) {
|
||||
val count = effects.count
|
||||
if (count > 0) {
|
||||
val countText = if (count > 9) NINE_PLUS else count.toString()
|
||||
UpdateDataAction(
|
||||
listOf(
|
||||
UpdateDataAction.ViewData(
|
||||
layoutId = NOTIFICATION_COUNT_TEXT,
|
||||
data = TextData(text = countText)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
homeVM.state.value.screenDefinition
|
||||
?.screenStructure
|
||||
?.collapsingToolbar
|
||||
?.toolBarNav
|
||||
?.uiTronActionWithKey
|
||||
?.find { it.key == if (count > 0) SHOW_NOTIFICATION_COUNT else HIDE_NOTIFICATION_COUNT }
|
||||
?.uiTronAction
|
||||
?.let { homeVM.handleAction(it) }
|
||||
}
|
||||
|
||||
private fun handleRenderActions(homeVM: HomeViewModel) {
|
||||
homeVM.state.value.screenDefinition?.screenStructure?.renderActions?.let { renderActions ->
|
||||
renderActions.postRenderAction?.let { homeVM.handleActions(it) }
|
||||
renderActions.apiSuccessRenderAction?.let { homeVM.handleActions(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleApiFailure(effects: HpEffects.OnApiFailure, homeVM: HomeViewModel) {
|
||||
when (effects.errorMessage?.statusCode) {
|
||||
ApiConstants.API_CODE_SOCKET_TIMEOUT -> selectiveRefreshHandler.handleErrorState(homeVM)
|
||||
ApiConstants.API_CODE_UNKNOWN_HOST,
|
||||
ApiConstants.API_CODE_CONNECT_EXCEPTION,
|
||||
ApiConstants.NO_INTERNET -> {}
|
||||
else -> {
|
||||
val errorUnifiedResponse =
|
||||
homeVM.getErrorUnifiedResponse(effects.errors, effects.errorMessage)
|
||||
homeVM.sendFailureEvent(
|
||||
NaviAnalytics.NEW_HOME_ACTIVITY,
|
||||
errorUnifiedResponse,
|
||||
ModuleNameV2.App.name
|
||||
)
|
||||
homeVM.sendEvent(HpEvents.UpdateError(errorUnifiedResponse.errorResponse))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun logAppLaunchTime(effects: HpEffects.LogAppLaunchTime) {
|
||||
if (!AppLoadTimerMapper.getAppLaunchEventStatus()) {
|
||||
val activityTime =
|
||||
System.currentTimeMillis() - AppLoadTimerMapper.getActivityStartTime()
|
||||
val coldBootTime = activityTime + AppLoadTimerMapper.getApplicationOnCreateTime()
|
||||
naviAnalyticsEventTracker.value.appLaunchTimeEvent(
|
||||
destination = effects.destination,
|
||||
coldBootTime = coldBootTime,
|
||||
activityBootTime = activityTime,
|
||||
applicationBootTime = AppLoadTimerMapper.getApplicationOnCreateTime()
|
||||
)
|
||||
AppLoadTimerMapper.setAppLaunchEventStatus(true)
|
||||
AppLoadTimerMapper.resetApplicationCreatedOnLaunchStatus()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleHideBalanceAction(homeVM: HomeViewModel) {
|
||||
val hideBalanceActionData =
|
||||
UiTronActionData(
|
||||
actions =
|
||||
listOf(
|
||||
UpdateViewStateAction(
|
||||
viewStates =
|
||||
mapOf(
|
||||
Constants.CHECK_BALANCE_ROW to Constants.VISIBLE,
|
||||
Constants.HIDE_BALANCE_ROW to Constants.INVISIBLE,
|
||||
Constants.CHECK_BALANCE_CLICK to Constants.VISIBLE,
|
||||
Constants.HIDE_BALANCE_CLICK to Constants.INVISIBLE
|
||||
)
|
||||
),
|
||||
UpdateDataAction(
|
||||
viewDataList =
|
||||
listOf(
|
||||
UpdateDataAction.ViewData(
|
||||
layoutId = Constants.BALANCE_TEXT,
|
||||
data = TextData(text = Constants.BALANCE_TEXT_MASKED_VALUE)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
homeVM.handleActions(hideBalanceActionData)
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.common.handler
|
||||
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.home.model.WidgetUiState
|
||||
import com.naviapp.home.model.WidgetUiStateData
|
||||
import javax.inject.Inject
|
||||
|
||||
typealias WidgetId = String
|
||||
|
||||
class HomePageDataUpdateHandler @Inject constructor() {
|
||||
|
||||
// List to observe existingHomePageData
|
||||
private var existingHomePageWidgets:
|
||||
MutableList<AlchemistWidgetModelDefinition<UiTronResponse>> =
|
||||
mutableListOf()
|
||||
private val widgetUiStateMap = mutableStateMapOf<WidgetId, WidgetUiStateData>()
|
||||
private var homeContentList = mutableListOf<WidgetId>()
|
||||
|
||||
fun updateHomePageData(
|
||||
screenDefinition: AlchemistScreenDefinition,
|
||||
updateHomePageSuccess: (AlchemistScreenDefinition) -> Unit
|
||||
) {
|
||||
val content = screenDefinition.screenStructure?.content?.widgets
|
||||
when (widgetUiStateMap.isEmpty()) {
|
||||
true -> {
|
||||
if (content != null) {
|
||||
val homeContent = mutableListOf<WidgetId>()
|
||||
content.forEach { naviWidget ->
|
||||
val widgetId = naviWidget.widgetId.orEmpty()
|
||||
if (widgetId.isNotEmpty()) {
|
||||
homeContent.add(widgetId)
|
||||
widgetUiStateMap[widgetId] =
|
||||
WidgetUiStateData(
|
||||
widgetUiState = WidgetUiState.VISIBLE,
|
||||
widgetData = naviWidget.widgetData
|
||||
)
|
||||
}
|
||||
}
|
||||
homeContentList = homeContent
|
||||
}
|
||||
}
|
||||
false -> {
|
||||
if (content != null) {
|
||||
homeContentList =
|
||||
constructHomePageList(
|
||||
oldData = existingHomePageWidgets.toMutableList(),
|
||||
newData = content.toMutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update home data
|
||||
updateHomePageSuccess.invoke(screenDefinition)
|
||||
// Replace the existing widgets to new once
|
||||
existingHomePageWidgets = content?.toMutableList() ?: mutableListOf()
|
||||
}
|
||||
|
||||
private fun constructHomePageList(
|
||||
oldData: MutableList<AlchemistWidgetModelDefinition<UiTronResponse>>,
|
||||
newData: MutableList<AlchemistWidgetModelDefinition<UiTronResponse>>
|
||||
): MutableList<WidgetId> {
|
||||
val updatedHomeList: MutableList<WidgetId> = mutableListOf()
|
||||
val seenElements = mutableSetOf<WidgetId>()
|
||||
// For addition of new widgets compared to previous data
|
||||
newData.forEach { naviWidget ->
|
||||
val widgetId = naviWidget.widgetId
|
||||
if (widgetId?.isNotBlank() == true) {
|
||||
seenElements.add(widgetId)
|
||||
val uiState =
|
||||
if (oldData.any { it.widgetId.orEmpty() == widgetId }) {
|
||||
WidgetUiState.VISIBLE
|
||||
} else {
|
||||
WidgetUiState.NEWLY_ADDED
|
||||
}
|
||||
widgetUiStateMap[widgetId] = WidgetUiStateData(uiState, naviWidget.widgetData)
|
||||
updatedHomeList.add(widgetId)
|
||||
}
|
||||
}
|
||||
// For deletion of existing widgets compared to new data
|
||||
oldData.forEachIndexed { index, naviWidget ->
|
||||
val widgetId = naviWidget.widgetId
|
||||
if (widgetId !in seenElements && widgetId?.isNotBlank() == true) {
|
||||
seenElements.add(widgetId)
|
||||
widgetUiStateMap[widgetId]?.apply { widgetUiState = WidgetUiState.NOT_VISIBLE }
|
||||
updatedHomeList.getOrNull(index)?.let { updatedHomeList.add(index, widgetId) }
|
||||
?: updatedHomeList.add(widgetId)
|
||||
}
|
||||
}
|
||||
return updatedHomeList
|
||||
}
|
||||
|
||||
fun getWidgetUiStateMap() = widgetUiStateMap
|
||||
|
||||
fun getHomeContentList() = homeContentList
|
||||
}
|
||||
@@ -13,11 +13,11 @@ import com.navi.common.uitron.model.action.InstallDynamicModulesAction
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.home.utils.checkForModulesInstall
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
|
||||
@Composable
|
||||
fun HandleInstallDynamicModulesAction(
|
||||
viewModel: HomeVM,
|
||||
viewModel: HomeViewModel,
|
||||
activity: HomePageActivity,
|
||||
naviAnalyticsEventTracker: NaviAnalytics.Home
|
||||
) {
|
||||
|
||||
@@ -10,7 +10,6 @@ package com.naviapp.home.compose.activity
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import androidx.activity.SystemBarStyle
|
||||
@@ -40,6 +39,7 @@ import com.navi.base.model.NaviWidgetClickWithActionData
|
||||
import com.navi.base.sharedpref.CommonPrefConstants.CURRENT_USER
|
||||
import com.navi.base.sharedpref.PreferenceManager
|
||||
import com.navi.base.utils.AppLaunchUtils
|
||||
import com.navi.base.utils.BaseUtils
|
||||
import com.navi.base.utils.ConnectivityObserver
|
||||
import com.navi.base.utils.ConnectivityObserverImpl
|
||||
import com.navi.base.utils.DateUtils
|
||||
@@ -79,9 +79,7 @@ import com.navi.common.utils.Constants.ScreenLockConstants.DISABLED
|
||||
import com.navi.common.utils.Constants.ScreenLockConstants.ENABLED
|
||||
import com.navi.common.utils.Constants.ScreenLockConstants.IS_SCREEN_LOCK_ENABLED
|
||||
import com.navi.common.utils.TemporaryStorageHelper
|
||||
import com.navi.common.utils.getDensityName
|
||||
import com.navi.common.utils.getDeviceSignature
|
||||
import com.navi.common.utils.getInstalledDynamicModulesCommaSeparated
|
||||
import com.navi.common.utils.getLocalStorageLocation
|
||||
import com.navi.common.utils.getNetworkType
|
||||
import com.navi.common.utils.getScreenRefreshRate
|
||||
@@ -103,12 +101,12 @@ import com.navi.naviwidgets.models.LottieFieldData
|
||||
import com.navi.naviwidgets.models.NaviTextComponent
|
||||
import com.navi.naviwidgets.models.response.BottomSheetInfoV2
|
||||
import com.navi.naviwidgets.utils.AP_LAUNCH
|
||||
import com.navi.naviwidgets.utils.CURRENT_VERSION_IN_STORE
|
||||
import com.navi.naviwidgets.utils.IN_APP_UPDATE
|
||||
import com.navi.naviwidgets.utils.LottieEnums
|
||||
import com.navi.naviwidgets.utils.NaviWidgetIconUtils
|
||||
import com.navi.naviwidgets.utils.toCtaData
|
||||
import com.navi.naviwidgets.widgets.ParameterValueJsonDeserializer.Companion.KEY_CTA_DATA
|
||||
import com.navi.pay.common.setup.NaviPayManager
|
||||
import com.navi.pay.utils.DATE_TIME_FORMAT_DATE_MONTH_NAME_AT_TIME
|
||||
import com.navi.pay.utils.KEY_CHECK_BALANCE_ACTION
|
||||
import com.navi.pay.utils.KEY_VALUE_MAPPING
|
||||
@@ -151,6 +149,7 @@ import com.naviapp.common.viewmodel.InAppUpdateVM
|
||||
import com.naviapp.dashboard.DashboardBaseActivity
|
||||
import com.naviapp.dashboard.listeners.DialogBoxPrimaryCtaListener
|
||||
import com.naviapp.dashboard.views.fragment.PaymentFailedBottomSheet
|
||||
import com.naviapp.home.common.handler.EffectHandler
|
||||
import com.naviapp.home.common.hopperProcessor.processHandlerImpl.Hopper
|
||||
import com.naviapp.home.compose.home.ui.screen.HomePageActivityMainScreen
|
||||
import com.naviapp.home.compose.model.BottomStickyNudgeState
|
||||
@@ -160,8 +159,10 @@ import com.naviapp.home.dashboard.ui.DashboardFragment
|
||||
import com.naviapp.home.dashboard.ui.ProductFragment.DashboardTypes
|
||||
import com.naviapp.home.listener.StickyBottomNudgeListener
|
||||
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.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.ProfileVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
@@ -195,11 +196,13 @@ import com.naviapp.utils.addDivider
|
||||
import com.naviapp.utils.deleteCacheAndOpenLoginPage
|
||||
import com.naviapp.utils.giDeeplink
|
||||
import com.naviapp.utils.toast
|
||||
import dagger.Lazy
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@@ -228,8 +231,12 @@ class HomePageActivity :
|
||||
|
||||
@Inject lateinit var hopper: Hopper
|
||||
|
||||
@Inject lateinit var naviPayManager: Lazy<NaviPayManager>
|
||||
|
||||
@Inject lateinit var homeEffectHandler: Lazy<EffectHandler>
|
||||
|
||||
private val configVM by lazy { ViewModelProvider(this)[ConfigVM::class.java] }
|
||||
private val homeVM by lazy { ViewModelProvider(this)[HomeVM::class.java] }
|
||||
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] }
|
||||
private val registrationVM by lazy { ViewModelProvider(this)[RegistrationVM::class.java] }
|
||||
@@ -296,11 +303,11 @@ class HomePageActivity :
|
||||
AppLoadTimerMapper.initActivityStartTime()
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
redirectionUseCase.redirectToDestination(this, homeVM)
|
||||
redirectionUseCase.redirectToDestination(homeVM)
|
||||
enableEdgeToEdge(
|
||||
statusBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT)
|
||||
)
|
||||
initHomeItems()
|
||||
homeVM.loadHomeElements(this@HomePageActivity)
|
||||
checkForAppLaunchEvent()
|
||||
setContent {
|
||||
HomePageMaterialTheme {
|
||||
@@ -341,7 +348,7 @@ class HomePageActivity :
|
||||
}
|
||||
|
||||
private fun fetchOtherModulesData() {
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
fetchUserProfileData()
|
||||
}
|
||||
}
|
||||
@@ -381,15 +388,15 @@ class HomePageActivity :
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
val selectedTabId = sharedVM.getSelectedTabId()
|
||||
if (homeVM.isProfileDrawerOpen) {
|
||||
homeVM.updateProfileDrawerState(false)
|
||||
if (homeVM.state.value.profileDrawerState) {
|
||||
homeVM.sendEvent(HpEvents.UpdateProfileDrawerState(false))
|
||||
} else if (selectedTabId == BottomBarTabType.HOME.name) {
|
||||
TemporaryStorageHelper.homePageBackPressed = true
|
||||
super.onBackPressed()
|
||||
} else if (selectedTabId == BottomBarTabType.LOAN.name && sharedVM.showBottomSheet.value) {
|
||||
sharedVM.setBottomSheetState(false)
|
||||
} else if (selectedTabId == BottomBarTabType.INVESTMENT.name) {
|
||||
hopper.cancelProcess(this)
|
||||
hopper.cancelProcess(this@HomePageActivity)
|
||||
navigateToHomeTab()
|
||||
} else {
|
||||
navigateToHomeTab()
|
||||
@@ -401,7 +408,7 @@ class HomePageActivity :
|
||||
}
|
||||
|
||||
private fun startPeriodicDataUploadWorker() {
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
val isReadSmsPermissionGranted = isReadSmsPermissionGranted(applicationContext)
|
||||
if (isReadSmsPermissionGranted) {
|
||||
userDataAnalyticsTracker.onDataPermissionAvailable(
|
||||
@@ -432,10 +439,10 @@ class HomePageActivity :
|
||||
fetchGiNavCta()
|
||||
TemporaryStorageHelper.fetchGiNavCta = false
|
||||
}
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
notificationVM.fetchNotificationsItems(true)
|
||||
fetchProfileItems()
|
||||
homeVM.syncNaviPayDelayedOnboardingExperiment()
|
||||
homeVM.coroutineScope.launch { naviPayManager.get().syncOnBoardingExperiment() }
|
||||
}
|
||||
TempStorageHelper.clear()
|
||||
if (connectivityObserver.isInternetConnected()) {
|
||||
@@ -449,7 +456,9 @@ class HomePageActivity :
|
||||
}
|
||||
}
|
||||
if (redirectionUseCase.isUpiNuxRedirection(intent.extras).not()) {
|
||||
homeVM.showNotificationPermission(permissionsManager)
|
||||
homeVM.sendEffect(homeVM.coroutineScope) {
|
||||
HpEffects.ShowNotificationPermissions(permissionsManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,38 +546,36 @@ class HomePageActivity :
|
||||
}
|
||||
|
||||
private fun observeNetworkConnectivity() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeVM.connectivityObserverHolder.collect {
|
||||
when (it) {
|
||||
ConnectivityObserver.Status.Available -> {
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
callHomeItemsApi()
|
||||
fetchProfileItems()
|
||||
}
|
||||
// Set the connecting nudge and
|
||||
// remove any network related nudge after 2 seconds
|
||||
bottomNavBarVM.setBottomNudge(
|
||||
true,
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeVM.internetConnectivity.collect {
|
||||
when (it) {
|
||||
ConnectivityObserver.Status.Available -> {
|
||||
homeVM.loadHomeElements(this@HomePageActivity)
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
fetchProfileItems()
|
||||
}
|
||||
// Set the connecting nudge and
|
||||
// remove any network related nudge after 2 seconds
|
||||
bottomNavBarVM.setBottomNudge(
|
||||
true,
|
||||
BottomStickyNudgeState.NetworkConnectivityNudgeState(
|
||||
networkConnectivityData = getNudgeDataForInternetConnected()
|
||||
)
|
||||
)
|
||||
delay(2.seconds)
|
||||
bottomNavBarVM.setBottomNudge(false)
|
||||
}
|
||||
ConnectivityObserver.Status.Unavailable,
|
||||
ConnectivityObserver.Status.Losing,
|
||||
ConnectivityObserver.Status.Lost -> {
|
||||
bottomNavBarVM.setBottomNudge(
|
||||
visible = true,
|
||||
bottomStickyNudgeState =
|
||||
BottomStickyNudgeState.NetworkConnectivityNudgeState(
|
||||
networkConnectivityData = getNudgeDataForInternetConnected()
|
||||
getNudgeDataForInternetDisconnected()
|
||||
)
|
||||
)
|
||||
delay(2.seconds)
|
||||
bottomNavBarVM.setBottomNudge(false)
|
||||
}
|
||||
ConnectivityObserver.Status.Unavailable,
|
||||
ConnectivityObserver.Status.Losing,
|
||||
ConnectivityObserver.Status.Lost -> {
|
||||
bottomNavBarVM.setBottomNudge(
|
||||
visible = true,
|
||||
bottomStickyNudgeState =
|
||||
BottomStickyNudgeState.NetworkConnectivityNudgeState(
|
||||
getNudgeDataForInternetDisconnected()
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -681,7 +688,7 @@ class HomePageActivity :
|
||||
}
|
||||
inAppUpdateVM.showInstallAppSnackBar.observe(this) { shouldShowAppUpdateStrip ->
|
||||
if (shouldShowAppUpdateStrip) {
|
||||
homeVM.updateHomeScreenSnackBarState(true)
|
||||
homeVM.sendEvent(HpEvents.UpdateSnackBarState(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -689,43 +696,47 @@ class HomePageActivity :
|
||||
private fun observeNpsRatingSubmitResponse() {
|
||||
npsVM.ratingSubmitResponse.observeNonNull(this) {
|
||||
if (it.response == Constants.SUCCESS) {
|
||||
homeVM.homeFeatures.value?.survey?.content?.dialogBoxInfo?.let { dialogBoxResponse
|
||||
->
|
||||
if (
|
||||
intent.extras
|
||||
?.getParcelable<PreviousScreenNameRequest>(Constants.PREVIOUS_SCREEN)
|
||||
?.moduleType
|
||||
.orEmpty() == GI
|
||||
) {
|
||||
NaviInsuranceDeeplinkNavigator.navigate(
|
||||
activity = this,
|
||||
ctaData =
|
||||
CtaData(
|
||||
url =
|
||||
giDeeplink(
|
||||
NaviInsuranceDeeplinkNavigator.DASHBOARD.plus(
|
||||
Constants.DIVIDER
|
||||
)
|
||||
.plus(NaviInsuranceDeeplinkNavigator.HOME)
|
||||
)
|
||||
),
|
||||
finish = true,
|
||||
bundle =
|
||||
Bundle().apply {
|
||||
putParcelable(NPS_SUBMIT_DIALOG, dialogBoxResponse)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
val commonDialogBox =
|
||||
CommonDialogBox.newInstance(
|
||||
screenName = NaviAnalytics.FEEDBACK_OFFER_DIALOG,
|
||||
title = dialogBoxResponse.title,
|
||||
description = dialogBoxResponse.subtitle,
|
||||
ctaData = dialogBoxResponse.cta,
|
||||
iconCode = dialogBoxResponse.iconCode,
|
||||
lottieAnimationFile = "success.json"
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
homeVM.state.value.homeFeatures?.survey?.content?.dialogBoxInfo?.let {
|
||||
dialogBoxResponse ->
|
||||
if (
|
||||
intent.extras
|
||||
?.getParcelable<PreviousScreenNameRequest>(
|
||||
Constants.PREVIOUS_SCREEN
|
||||
)
|
||||
?.moduleType
|
||||
.orEmpty() == GI
|
||||
) {
|
||||
NaviInsuranceDeeplinkNavigator.navigate(
|
||||
activity = this@HomePageActivity,
|
||||
ctaData =
|
||||
CtaData(
|
||||
url =
|
||||
giDeeplink(
|
||||
NaviInsuranceDeeplinkNavigator.DASHBOARD.plus(
|
||||
Constants.DIVIDER
|
||||
)
|
||||
.plus(NaviInsuranceDeeplinkNavigator.HOME)
|
||||
)
|
||||
),
|
||||
finish = true,
|
||||
bundle =
|
||||
Bundle().apply {
|
||||
putParcelable(NPS_SUBMIT_DIALOG, dialogBoxResponse)
|
||||
}
|
||||
)
|
||||
safelyShowDialogFragment(commonDialogBox, CommonDialogBox.TAG)
|
||||
} else {
|
||||
val commonDialogBox =
|
||||
CommonDialogBox.newInstance(
|
||||
screenName = NaviAnalytics.FEEDBACK_OFFER_DIALOG,
|
||||
title = dialogBoxResponse.title,
|
||||
description = dialogBoxResponse.subtitle,
|
||||
ctaData = dialogBoxResponse.cta,
|
||||
iconCode = dialogBoxResponse.iconCode,
|
||||
lottieAnimationFile = "success.json"
|
||||
)
|
||||
safelyShowDialogFragment(commonDialogBox, CommonDialogBox.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -733,32 +744,46 @@ class HomePageActivity :
|
||||
}
|
||||
|
||||
private fun observeHomeFeaturesData() {
|
||||
homeVM.homeFeatures.observeNonNull(this) {
|
||||
if (
|
||||
it.ppeFeatures?.positiveReinforcement?.cta != null &&
|
||||
it.ppeFeatures.positiveReinforcement.enable == true &&
|
||||
AppLaunchUtils.isLandingFirstTimeAfterAppOpen(
|
||||
AppLaunchUtils.HOME_FEATURE_POSITIVE_REINFORCEMENT
|
||||
)
|
||||
) {
|
||||
AppLaunchUtils.setAppOpenStatus(AppLaunchUtils.HOME_FEATURE_POSITIVE_REINFORCEMENT)
|
||||
homeVM.handleCtaData(it.ppeFeatures.positiveReinforcement.cta, this) {}
|
||||
} else if (
|
||||
it.ppeFeatures?.negativeReinforcement?.cta != null &&
|
||||
it.ppeFeatures.negativeReinforcement.enable == true &&
|
||||
AppLaunchUtils.isLandingFirstTimeAfterAppOpen(
|
||||
AppLaunchUtils.HOME_FEATURE_NEGATIVE_REINFORCEMENT
|
||||
)
|
||||
) {
|
||||
AppLaunchUtils.setAppOpenStatus(AppLaunchUtils.HOME_FEATURE_NEGATIVE_REINFORCEMENT)
|
||||
homeVM.handleCtaData(it.ppeFeatures.negativeReinforcement.cta, this) {}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
homeVM.state.collect { state ->
|
||||
state.homeFeatures.let {
|
||||
if (
|
||||
it?.ppeFeatures?.positiveReinforcement?.cta != null &&
|
||||
it.ppeFeatures.positiveReinforcement.enable == true &&
|
||||
AppLaunchUtils.isLandingFirstTimeAfterAppOpen(
|
||||
AppLaunchUtils.HOME_FEATURE_POSITIVE_REINFORCEMENT
|
||||
)
|
||||
) {
|
||||
AppLaunchUtils.setAppOpenStatus(
|
||||
AppLaunchUtils.HOME_FEATURE_POSITIVE_REINFORCEMENT
|
||||
)
|
||||
homeVM.handleCtaData(
|
||||
it.ppeFeatures.positiveReinforcement.cta,
|
||||
this@HomePageActivity
|
||||
) {}
|
||||
} else if (
|
||||
it?.ppeFeatures?.negativeReinforcement?.cta != null &&
|
||||
it.ppeFeatures.negativeReinforcement.enable == true &&
|
||||
AppLaunchUtils.isLandingFirstTimeAfterAppOpen(
|
||||
AppLaunchUtils.HOME_FEATURE_NEGATIVE_REINFORCEMENT
|
||||
)
|
||||
) {
|
||||
AppLaunchUtils.setAppOpenStatus(
|
||||
AppLaunchUtils.HOME_FEATURE_NEGATIVE_REINFORCEMENT
|
||||
)
|
||||
homeVM.handleCtaData(
|
||||
it.ppeFeatures.negativeReinforcement.cta,
|
||||
this@HomePageActivity
|
||||
) {}
|
||||
}
|
||||
|
||||
it.survey?.let { npsResponse ->
|
||||
safelyShowDialogFragment(
|
||||
NetPromoterScoreFragment.getInstance(npsResponse),
|
||||
NetPromoterScoreFragment.TAG
|
||||
)
|
||||
it?.survey?.let { npsResponse ->
|
||||
safelyShowDialogFragment(
|
||||
NetPromoterScoreFragment.getInstance(npsResponse),
|
||||
NetPromoterScoreFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -888,11 +913,11 @@ class HomePageActivity :
|
||||
}
|
||||
|
||||
private fun trackLaunchEvents() {
|
||||
homeVM.trackBottomBarEvent(dashboardAnalytics)
|
||||
homeVM.sendEffect(homeVM.coroutineScope) { HpEffects.TrackBottomBarEvents }
|
||||
}
|
||||
|
||||
private fun initHomeScreenActions() {
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
naviAnalyticsEventTracker.onHomePageCreated()
|
||||
sendFCEvent()
|
||||
sendLocationUpdates()
|
||||
@@ -908,11 +933,12 @@ class HomePageActivity :
|
||||
}
|
||||
|
||||
private fun fetchHomeData() {
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
fetchNuxScreenDataForEligibleUsers()
|
||||
configVM.fetchHomeExtras()
|
||||
fetchBottomNavigationBar()
|
||||
fetchOfferDialog()
|
||||
|
||||
homeVM.fetchHomeFeature(Constants.HOME_FEATURE)
|
||||
}
|
||||
}
|
||||
@@ -976,14 +1002,14 @@ class HomePageActivity :
|
||||
}
|
||||
|
||||
private fun fetchGiNavCta() {
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
bottomNavBarVM.fetchGiNavCta()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchOfferDialog() {
|
||||
if (
|
||||
homeVM.isUserLoggedIn() &&
|
||||
BaseUtils.isUserLoggedIn() &&
|
||||
intent?.getStringExtra(Constants.REDIRECT_STATUS) != Constants.EMI_DATE_CHANGE
|
||||
)
|
||||
registrationVM.fetchOfferDialog()
|
||||
@@ -1135,9 +1161,7 @@ class HomePageActivity :
|
||||
override fun getCurrentFragmentScreenName(): String {
|
||||
return if (sharedVM.getSelectedTabId() == BottomBarTabType.HOME.name) {
|
||||
NaviAnalytics.NEW_HOME
|
||||
} else {
|
||||
homeVM.getCurrentFragmentScreenName()
|
||||
}
|
||||
} else homeVM.state.value.currentLoadedFragmentScreenName
|
||||
}
|
||||
|
||||
override fun removeInstallAppSnackBar() {
|
||||
@@ -1146,7 +1170,7 @@ class HomePageActivity :
|
||||
}
|
||||
|
||||
private fun uploadUserData() {
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
val isLocationPermissionGranted = isLocationPermissionGranted(applicationContext)
|
||||
val isReadSmsPermissionGranted = isReadSmsPermissionGranted(applicationContext)
|
||||
naviAnalyticsEventTracker.homePagePermissionGrantedInfoEvent(
|
||||
@@ -1175,24 +1199,6 @@ class HomePageActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun initHomeItems() {
|
||||
if (homeVM.isUserLoggedIn()) {
|
||||
callHomeItemsApi()
|
||||
}
|
||||
}
|
||||
|
||||
private fun callHomeItemsApi(showLoader: Boolean = false) {
|
||||
homeVM.fetchHomeItems(
|
||||
showLoader = showLoader,
|
||||
density = getDensityName(context = this).orEmpty(),
|
||||
connectivityType = getNetworkType(context = this),
|
||||
availableAppVersionCode =
|
||||
PreferenceManager.getIntPreferenceApp(CURRENT_VERSION_IN_STORE),
|
||||
installedModules = getInstalledDynamicModulesCommaSeparated(),
|
||||
clearReferralPopupPreferences = false
|
||||
)
|
||||
}
|
||||
|
||||
override fun shouldUpdateApp(appUpgradeSettings: AppUpgradeResponse) {
|
||||
if (appUpgradeSettings.hardUpgrade.orTrue() || appUpgradeSettings.softUpgrade.orTrue()) {
|
||||
val intent =
|
||||
@@ -1741,9 +1747,9 @@ class HomePageActivity :
|
||||
|
||||
private fun initResourceManager() {
|
||||
homeVM.viewModelScope.launch(Dispatchers.IO) {
|
||||
homeVM.initiateResourceManager.collect { initResourceManager ->
|
||||
homeVM.state.collect {
|
||||
if (
|
||||
initResourceManager &&
|
||||
it.renderingFirstTime.not() &&
|
||||
FirebaseRemoteConfigHelper.getBoolean(RESOURCE_MANAGER_ENABLED)
|
||||
) {
|
||||
ResourceManager.init(context = applicationContext)
|
||||
|
||||
@@ -21,7 +21,8 @@ import com.naviapp.common.viewmodel.InAppUpdateVM
|
||||
import com.naviapp.dashboard.viewmodels.DashboardSharedVM
|
||||
import com.naviapp.home.common.hopperProcessor.processHandlerImpl.Hopper
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.payment.viewmodel.PaymentVM
|
||||
@@ -31,7 +32,8 @@ fun HomePageNavHost(
|
||||
homePageActivity: HomePageActivity,
|
||||
navController: NavHostController,
|
||||
dashboardSharedVM: DashboardSharedVM,
|
||||
homeVM: HomeVM,
|
||||
homeViewModel: () -> HomeViewModel,
|
||||
hpStates: () -> HpStates,
|
||||
notificationVM: NotificationVM,
|
||||
sharedVM: SharedVM,
|
||||
paymentVM: PaymentVM,
|
||||
@@ -53,14 +55,15 @@ fun HomePageNavHost(
|
||||
activity = homePageActivity,
|
||||
tabId = tab.tabId,
|
||||
dashboardSharedVM = dashboardSharedVM,
|
||||
homeVM = homeVM,
|
||||
hpStates = hpStates,
|
||||
notificationVM = notificationVM,
|
||||
paymentVM = paymentVM,
|
||||
sharedVM = sharedVM,
|
||||
naviHomeAnalytics = naviHomeAnalytics,
|
||||
investmentListState = investmentListState,
|
||||
inAppUpdateVM = inAppUpdateVM,
|
||||
hopper = hopper
|
||||
hopper = hopper,
|
||||
homeVM = homeViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.navi.common.ui.fragment.BaseFragment
|
||||
import com.navi.common.utils.TemporaryStorageHelper
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.common.viewmodel.InAppUpdateVM
|
||||
import com.naviapp.dashboard.viewmodels.DashboardSharedVM
|
||||
@@ -29,7 +30,10 @@ import com.naviapp.home.compose.home.ui.footer.utils.FragmentContainer
|
||||
import com.naviapp.home.compose.home.ui.screen.HomeScreen
|
||||
import com.naviapp.home.compose.listener.HomeScreenCallbackListener
|
||||
import com.naviapp.home.dashboard.ui.compose.loansTab.LoansTabScreen
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.payment.viewmodel.PaymentVM
|
||||
@@ -39,7 +43,8 @@ fun NavGraphNavigationItem(
|
||||
activity: HomePageActivity,
|
||||
tabId: String,
|
||||
dashboardSharedVM: DashboardSharedVM,
|
||||
homeVM: HomeVM,
|
||||
hpStates: () -> HpStates,
|
||||
homeVM: () -> HomeViewModel,
|
||||
paymentVM: PaymentVM,
|
||||
notificationVM: NotificationVM,
|
||||
sharedVM: SharedVM,
|
||||
@@ -54,24 +59,30 @@ fun NavGraphNavigationItem(
|
||||
HomeScreen(
|
||||
activity = activity,
|
||||
dashboardSharedVM = dashboardSharedVM,
|
||||
homeVM = homeVM,
|
||||
paymentVM = paymentVM,
|
||||
hpStates = hpStates(),
|
||||
sharedVM = sharedVM,
|
||||
notificationVM = notificationVM,
|
||||
naviAnalyticsEventTracker = naviHomeAnalytics,
|
||||
inAppUpdateVM = inAppUpdateVM
|
||||
inAppUpdateVM = inAppUpdateVM,
|
||||
homeVM = homeVM,
|
||||
onHomeScreenEvent = { homeVM().sendEvent(it) }
|
||||
) {
|
||||
when (it) {
|
||||
HomeScreenCallbackListener.ApiCallingWithCondition -> {
|
||||
homeVM.homePageApiCallingWithCondition(
|
||||
hidden = false,
|
||||
activity = activity,
|
||||
isPaymentLoaderShowing = paymentVM.isPaymentLoaderShowing(),
|
||||
naviAnalyticsEventTracker = naviHomeAnalytics
|
||||
TemporaryStorageHelper.updateViewVisibility(
|
||||
TemporaryStorageHelper.HOME,
|
||||
false
|
||||
)
|
||||
homeVM()
|
||||
.loadHomeElements(
|
||||
activity = activity,
|
||||
isPaymentLoaderShowing = paymentVM.isPaymentLoaderShowing()
|
||||
)
|
||||
}
|
||||
is HomeScreenCallbackListener.InitiatePaymentOnHomePage -> {
|
||||
homeVM.updatePaymentDataComposeScreen(it.paymentData)
|
||||
homeVM().sendEffect(homeVM().coroutineScope) {
|
||||
HpEffects.InitiatePayment(it.paymentData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,9 +114,9 @@ fun NavGraphNavigationItem(
|
||||
fragmentManager = activity.supportFragmentManager
|
||||
) { containerId ->
|
||||
val fragment = InsuranceContainerFragment.getInstance(insuranceScrollState)
|
||||
homeVM.setCurrentFragmentScreenName(
|
||||
val screenName =
|
||||
(fragment as? BaseFragment)?.screenName ?: NaviAnalytics.NEW_HOME_ACTIVITY
|
||||
)
|
||||
homeVM().sendEvent(HpEvents.UpdateCurrentLoadedFragmentName(screenName))
|
||||
if (!fragment.isStateSaved && !fragment.isAdded) {
|
||||
if (fragment.isAdded) {
|
||||
show(fragment)
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.compose.home.ui.content
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.navi.common.alchemist.model.AlchemistCollapsingToolbar
|
||||
import com.naviapp.home.compose.widgetfactory.HomeWidgetRenderer
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
|
||||
@Composable
|
||||
fun BackLayerContent(
|
||||
modifier: Modifier,
|
||||
backLayerData: AlchemistCollapsingToolbar?,
|
||||
homeVM: HomeVM,
|
||||
homeScrollState: () -> ScrollState
|
||||
) {
|
||||
Column(modifier.fillMaxWidth()) {
|
||||
val isShowBackLayer by
|
||||
homeVM.isReadyToRenderBackLayerAndTopAppBar.collectAsStateWithLifecycle()
|
||||
if (isShowBackLayer) {
|
||||
LaunchedEffect(Unit) { homeVM.setIsReadyToRenderUpperMiddleContent(true) }
|
||||
backLayerData?.collapsingTopNav?.uiTronResponse?.let {
|
||||
HomeWidgetRenderer(
|
||||
widgetData = it.data,
|
||||
viewModel = homeVM,
|
||||
composeView = it.parentComposeView,
|
||||
homeScrollState = homeScrollState
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,11 @@ import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.LocalOverscrollConfiguration
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -32,84 +34,68 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.airbnb.lottie.compose.LottieAnimation
|
||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||
import com.airbnb.lottie.compose.LottieConstants
|
||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
|
||||
import com.navi.common.alchemist.model.WidgetRenderState
|
||||
import com.navi.naviwidgets.R
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.home.compose.widgetfactory.HomeWidgetRenderer
|
||||
import com.naviapp.home.model.WidgetUiState
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.utils.getHomeWidgetAnimationSpec
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.utils.Constants
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
typealias WidgetId = String
|
||||
|
||||
/**
|
||||
* Order of composition:
|
||||
* 1. UpperContent and ContentLoader Lottie
|
||||
* 2. BackLayer and TopAppBar
|
||||
* 3. UpperMiddleContent
|
||||
* 4. LowerMiddleContent
|
||||
* 5. LowerContent
|
||||
* 6. ProfileScreen
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||
fun FrontLayerContent(
|
||||
modifier: Modifier,
|
||||
isShimmerVisible: Boolean,
|
||||
homeVM: HomeVM,
|
||||
homeVM: HomeViewModel,
|
||||
frontLayerShape: Shape,
|
||||
hpStates: () -> HpStates,
|
||||
homeScrollState: () -> ScrollState
|
||||
) {
|
||||
CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
|
||||
Column(
|
||||
modifier
|
||||
Modifier.fillMaxHeight()
|
||||
.background(Color.White, frontLayerShape)
|
||||
.verticalScroll(homeScrollState())
|
||||
.semantics { testTagsAsResourceId = true }
|
||||
.testTag("homeScreenWidgetsList")
|
||||
) {
|
||||
if (isShimmerVisible.not()) {
|
||||
val widgetOrderList = homeVM.getHomeContentList()
|
||||
val widgets = hpStates().screenDefinition?.screenStructure?.content?.widgets
|
||||
if (!widgets.isNullOrEmpty()) {
|
||||
Column {
|
||||
UpperContent(homeVM) {
|
||||
RenderUiTronContent(
|
||||
widgetOrderList.take(homeVM.upperContentSize),
|
||||
homeVM,
|
||||
homeScrollState
|
||||
)
|
||||
}
|
||||
val middleAndLowerContent = widgetOrderList.drop(homeVM.upperContentSize)
|
||||
UpperMiddleContent(homeVM) {
|
||||
RenderUiTronContent(
|
||||
middleAndLowerContent.take(Constants.SECTION_CONTENT_SIZE),
|
||||
homeVM,
|
||||
homeScrollState
|
||||
)
|
||||
}
|
||||
val lowerContent = middleAndLowerContent.drop(Constants.SECTION_CONTENT_SIZE)
|
||||
LowerMiddleContent(homeVM) {
|
||||
RenderUiTronContent(
|
||||
lowerContent.take(Constants.SECTION_CONTENT_SIZE),
|
||||
homeVM,
|
||||
homeScrollState
|
||||
)
|
||||
}
|
||||
LowerContent(homeVM) {
|
||||
RenderUiTronContent(
|
||||
lowerContent.drop(Constants.SECTION_CONTENT_SIZE),
|
||||
homeVM,
|
||||
homeScrollState
|
||||
)
|
||||
}
|
||||
ContentLoaderLottie(homeVM = homeVM)
|
||||
RenderUiTronContent(widgets, homeVM, hpStates, homeScrollState)
|
||||
ContentLoaderLottie(hpStates)
|
||||
}
|
||||
} else run { HomePageContentShimmer() }
|
||||
} else HomePageContentShimmer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentLoaderLottie(hpStates: () -> HpStates) {
|
||||
if (hpStates().renderingFirstTime) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 32.dp, bottom = 100.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
val composition by
|
||||
rememberLottieComposition(
|
||||
spec = LottieCompositionSpec.RawRes(R.raw.cta_loader_purple),
|
||||
)
|
||||
LottieAnimation(composition = composition, iterations = LottieConstants.IterateForever)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,107 +128,56 @@ private fun AnimatedContainerForWidgets(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpperContent(
|
||||
homeVM: HomeVM,
|
||||
upperContent: @Composable () -> Unit,
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
homeVM.logAppLaunchTimeEvent(Constants.HOME_SCREEN_IN_CAPS)
|
||||
// Show back layer and top app bar
|
||||
homeVM.setIsReadyToRenderBackLayerAndTopAppBar(true)
|
||||
}
|
||||
upperContent()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpperMiddleContent(
|
||||
homeVM: HomeVM,
|
||||
upperMiddleContent: @Composable () -> Unit,
|
||||
) {
|
||||
val showUpperMiddleLayer by
|
||||
homeVM.isReadyToRenderUpperMiddleContent.collectAsStateWithLifecycle()
|
||||
if (showUpperMiddleLayer) {
|
||||
LaunchedEffect(Unit) { homeVM.setIsReadyToRenderLowerMiddleContent(true) }
|
||||
upperMiddleContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LowerMiddleContent(
|
||||
homeVM: HomeVM,
|
||||
lowerMiddleContent: @Composable () -> Unit,
|
||||
) {
|
||||
val showLowerMiddleLayer by
|
||||
homeVM.isReadyToRenderLowerMiddleContent.collectAsStateWithLifecycle()
|
||||
if (showLowerMiddleLayer) {
|
||||
LaunchedEffect(Unit) { homeVM.setIsReadyToRenderLowerContent(true) }
|
||||
lowerMiddleContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LowerContent(
|
||||
homeVM: HomeVM,
|
||||
lowerContent: @Composable () -> Unit,
|
||||
) {
|
||||
val showLowerFrontLayer by homeVM.isReadyToRenderLowerContent.collectAsStateWithLifecycle()
|
||||
if (showLowerFrontLayer) {
|
||||
LaunchedEffect(Unit) {
|
||||
// Immediately remove content lottie loader
|
||||
homeVM.setIsShowContentLoader(false)
|
||||
homeVM.setIsReadyToInitiateResourceManager(true)
|
||||
}
|
||||
lowerContent()
|
||||
homeVM.logDnDataDisplayedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContentLoaderLottie(homeVM: HomeVM) {
|
||||
val isShowLoader by homeVM.isShowContentLoader.collectAsStateWithLifecycle()
|
||||
if (isShowLoader) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 100.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val composition by
|
||||
rememberLottieComposition(
|
||||
spec = LottieCompositionSpec.RawRes(R.raw.cta_loader_purple),
|
||||
)
|
||||
LottieAnimation(composition = composition, iterations = LottieConstants.IterateForever)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderUiTronContent(
|
||||
elementList: List<WidgetId>,
|
||||
homeVM: HomeVM,
|
||||
elementList: List<AlchemistWidgetModelDefinition<UiTronResponse>>,
|
||||
homeVM: HomeViewModel,
|
||||
hpStates: () -> HpStates,
|
||||
homeScrollState: () -> ScrollState
|
||||
) {
|
||||
elementList.forEach { element ->
|
||||
key(element) {
|
||||
val widgetUiMap = homeVM.getWidgetUiStateMap()
|
||||
val visible =
|
||||
remember(widgetUiMap[element]?.widgetUiState) {
|
||||
mutableStateOf(widgetUiMap[element]?.widgetUiState == WidgetUiState.VISIBLE)
|
||||
element.widgetId?.let { widgetId ->
|
||||
key(widgetId) {
|
||||
val visible = remember {
|
||||
mutableStateOf(element.widgetRenderState == WidgetRenderState.VISIBLE)
|
||||
}
|
||||
LaunchedEffect(widgetUiMap[element]?.widgetUiState) {
|
||||
if (widgetUiMap[element]?.widgetUiState == WidgetUiState.NEWLY_ADDED) {
|
||||
visible.value = true
|
||||
widgetUiMap[element].apply { this?.widgetUiState = WidgetUiState.VISIBLE }
|
||||
|
||||
LaunchedEffect(element.widgetRenderState) {
|
||||
when (element.widgetRenderState) {
|
||||
WidgetRenderState.NEWLY_ADDED -> {
|
||||
visible.value = true
|
||||
homeVM.sendEvent(
|
||||
HpEvents.UpdateScreenContentWidgetRenderState(
|
||||
widgetId,
|
||||
WidgetRenderState.VISIBLE
|
||||
)
|
||||
)
|
||||
}
|
||||
WidgetRenderState.NOT_VISIBLE -> {
|
||||
visible.value = false
|
||||
}
|
||||
WidgetRenderState.VISIBLE -> {
|
||||
visible.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedContainerForWidgets(visible) {
|
||||
val response = element.widgetData
|
||||
HomeWidgetRenderer(
|
||||
widgetData = response?.data,
|
||||
viewModel = homeVM,
|
||||
composeView = response?.parentComposeView.orEmpty(),
|
||||
homeScrollState = homeScrollState
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedContainerForWidgets(isVisible = visible) {
|
||||
val response = homeVM.getWidgetUiStateMap()[element]?.widgetData
|
||||
HomeWidgetRenderer(
|
||||
widgetData = response?.data,
|
||||
viewModel = homeVM,
|
||||
composeView = response?.parentComposeView.orEmpty(),
|
||||
homeScrollState = homeScrollState
|
||||
)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(hpStates().renderingFirstTime) {
|
||||
if (hpStates().renderingFirstTime) {
|
||||
homeVM.sendEffect(CoroutineScope(Dispatchers.Main)) {
|
||||
HpEffects.OnPrioritySectionRendered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,44 +17,57 @@ import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.navi.common.alchemist.model.AlchemistDialogStructure
|
||||
import com.navi.common.utils.Constants.DIALOG_VISIBILITY
|
||||
import com.navi.common.utils.Constants.HIDE
|
||||
import com.navi.uitron.render.UiTronRenderer
|
||||
import com.naviapp.home.model.HpDialogStateHolder
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@Composable
|
||||
fun HomeScreenDialog(viewModel: HomeVM) {
|
||||
fun HomeScreenDialog(homeVM: () -> HomeViewModel, hpStates: () -> HpStates) {
|
||||
|
||||
val dialogStateHolder by viewModel.dialogStateHolder.collectAsStateWithLifecycle()
|
||||
val dialogStateHolder = hpStates().hpDialogStateHolder
|
||||
|
||||
LaunchedEffect(dialogStateHolder.state) {
|
||||
if (dialogStateHolder.state == HpDialogStateHolder.HpDialogState.Hidden) {
|
||||
dialogStateHolder.dialogUIContent?.onDismissAction?.let { viewModel.handleActions(it) }
|
||||
dialogStateHolder.dialogUIContent?.onDismissAction?.let {
|
||||
homeVM().sendEffect(CoroutineScope(Dispatchers.Default)) {
|
||||
HpEffects.OnActionData(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.handle.getStateFlow<String?>(DIALOG_VISIBILITY, null).collect {
|
||||
homeVM().handle.getStateFlow<String?>(DIALOG_VISIBILITY, null).collect {
|
||||
if (it == HIDE) {
|
||||
viewModel.updateDialogState(HpDialogStateHolder.HpDialogState.Hidden)
|
||||
viewModel.handle[DIALOG_VISIBILITY] = null
|
||||
homeVM()
|
||||
.sendEvent(
|
||||
HpEvents.UpdateHpDialogStateHolder(HpDialogStateHolder.HpDialogState.Hidden)
|
||||
)
|
||||
homeVM().handle[DIALOG_VISIBILITY] = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dialogStateHolder.state == HpDialogStateHolder.HpDialogState.Visible) {
|
||||
dialogStateHolder.dialogUIContent?.let {
|
||||
DialogContentRenderer(it, viewModel) {
|
||||
viewModel.updateDialogState(HpDialogStateHolder.HpDialogState.Hidden)
|
||||
DialogContentRenderer(it, homeVM) {
|
||||
homeVM()
|
||||
.sendEvent(
|
||||
HpEvents.UpdateHpDialogStateHolder(HpDialogStateHolder.HpDialogState.Hidden)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +76,7 @@ fun HomeScreenDialog(viewModel: HomeVM) {
|
||||
@Composable
|
||||
private fun DialogContentRenderer(
|
||||
data: AlchemistDialogStructure,
|
||||
viewModel: HomeVM,
|
||||
homeVM: () -> HomeViewModel,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
when (data.type) {
|
||||
@@ -86,7 +99,7 @@ private fun DialogContentRenderer(
|
||||
data.content?.widgets?.forEach {
|
||||
UiTronRenderer(
|
||||
dataMap = it.widgetData?.data,
|
||||
viewModel,
|
||||
homeVM(),
|
||||
)
|
||||
.Render(composeViews = it.widgetData?.parentComposeView ?: listOf())
|
||||
}
|
||||
|
||||
@@ -20,19 +20,18 @@ import com.naviapp.common.model.BottomStickyNudgeData
|
||||
import com.naviapp.common.viewmodel.BottomNavBarVM
|
||||
import com.naviapp.common.viewmodel.InAppUpdateVM
|
||||
import com.naviapp.dashboard.viewmodels.DashboardSharedVM
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.home.ui.footer.utils.HomeFooterEvents
|
||||
import com.naviapp.home.compose.home.ui.footer.utils.HomeFooterStates
|
||||
import com.naviapp.home.compose.home.ui.footer.utils.handleHomeFooterEvent
|
||||
import com.naviapp.home.model.BottomBarTabType
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
|
||||
@Composable
|
||||
fun HomeFooterRoot(
|
||||
modifier: Modifier,
|
||||
homeVM: HomeVM,
|
||||
homePageActivity: HomePageActivity,
|
||||
homeVM: () -> HomeViewModel,
|
||||
hpStates: () -> HpStates,
|
||||
inAppUpdateVM: InAppUpdateVM,
|
||||
dashboardSharedVM: DashboardSharedVM,
|
||||
bottomNavBarVM: BottomNavBarVM,
|
||||
@@ -40,11 +39,8 @@ fun HomeFooterRoot(
|
||||
navController: NavHostController,
|
||||
selectedTabId: String
|
||||
) {
|
||||
val showSnackBar by homeVM.showHomeScreenSnackBar.collectAsStateWithLifecycle()
|
||||
val isFabButtonVisible by homeVM.fabButtonVisibility.collectAsStateWithLifecycle()
|
||||
val bottomStickyNudgeData by bottomNavBarVM.bottomStickyNudgeData.collectAsStateWithLifecycle()
|
||||
val bottomNavBarState by sharedVM.bottomNavBarStateHolder.collectAsStateWithLifecycle()
|
||||
val isHomeTabSelected = selectedTabId == BottomBarTabType.HOME.name
|
||||
|
||||
HomeFooter(
|
||||
modifier = modifier,
|
||||
@@ -52,8 +48,7 @@ fun HomeFooterRoot(
|
||||
bottomNudgeData = bottomStickyNudgeData,
|
||||
state =
|
||||
HomeFooterStates(
|
||||
isFabButtonVisible = isHomeTabSelected && isFabButtonVisible,
|
||||
showSnackBar = showSnackBar,
|
||||
showSnackBar = hpStates().homeScreenSnackBarState,
|
||||
bottomNavBarState = bottomNavBarState
|
||||
),
|
||||
onEvent = { homeFooterEvent ->
|
||||
@@ -64,7 +59,6 @@ fun HomeFooterRoot(
|
||||
sharedVM = sharedVM,
|
||||
dashboardSharedVM = dashboardSharedVM,
|
||||
inAppUpdateVM = inAppUpdateVM,
|
||||
homePageActivity = homePageActivity,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
@@ -83,12 +77,6 @@ fun HomeFooter(
|
||||
if (state.showSnackBar) {
|
||||
AppInstallSnackBar { onEvent(HomeFooterEvents.SnackBarOnClick) }
|
||||
}
|
||||
if (state.isFabButtonVisible) {
|
||||
UpiFloatingActionButton(
|
||||
onLaunchEvent = { onEvent(HomeFooterEvents.FabButtonOnLaunchEvent) },
|
||||
onClickEvent = { onEvent(HomeFooterEvents.FabButtonOnClick) }
|
||||
)
|
||||
}
|
||||
bottomNudgeData?.let {
|
||||
if (it.visible) {
|
||||
BottomStickyNudgeUI(
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.compose.home.ui.footer
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.navi.design.font.FontWeightEnum
|
||||
import com.navi.design.theme.getFontWeight
|
||||
import com.navi.design.theme.ttComposeFontFamily
|
||||
import com.navi.naviwidgets.R
|
||||
import com.navi.naviwidgets.extensions.NaviText
|
||||
import com.navi.pay.utils.noRippleClickable
|
||||
import com.naviapp.home.utils.DrawIcon
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.UpiFloatingActionButton(onLaunchEvent: () -> Unit, onClickEvent: () -> Unit) {
|
||||
LaunchedEffect(Unit) { onLaunchEvent() }
|
||||
FloatingActionButton(
|
||||
onClick = onClickEvent,
|
||||
modifier =
|
||||
Modifier.align(Alignment.End)
|
||||
.padding(end = 16.dp, bottom = 16.dp)
|
||||
.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
|
||||
.noRippleClickable {},
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
containerColor = Color(0xFF1F002A)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
DrawIcon(
|
||||
drawableIconId = R.drawable.scan_pay,
|
||||
content = stringResource(id = com.naviapp.R.string.scan_pay)
|
||||
)
|
||||
NaviText(
|
||||
text = stringResource(id = com.naviapp.R.string.scan_pay),
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
fontSize = 14.sp,
|
||||
color = Color.White,
|
||||
style =
|
||||
TextStyle(
|
||||
fontFamily = ttComposeFontFamily,
|
||||
fontWeight = getFontWeight(FontWeightEnum.TT_SEMI_BOLD),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,34 +12,26 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import com.navi.analytics.utils.NaviTrackEvent
|
||||
import com.navi.base.model.ActionData
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.commoncomposables.utils.resetScrollToTop
|
||||
import com.navi.common.model.common.AppUpdateData
|
||||
import com.navi.common.utils.Constants.APP_UPDATE_DATA
|
||||
import com.navi.common.utils.Constants.CARD_NAME
|
||||
import com.navi.common.utils.Constants.HOME_BOTTOM_NAV_BAR_DATA
|
||||
import com.navi.naviwidgets.utils.IN_APP_UPDATE
|
||||
import com.naviapp.app.NaviApplication
|
||||
import com.naviapp.common.navigator.NaviDeepLinkNavigator
|
||||
import com.naviapp.common.viewmodel.InAppUpdateVM
|
||||
import com.naviapp.dashboard.viewmodels.DashboardSharedVM
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.model.BottomStickyNudgeState
|
||||
import com.naviapp.home.listener.StickyBottomNudgeListener
|
||||
import com.naviapp.home.model.BottomBarTabType
|
||||
import com.naviapp.home.model.BottomNavBarStateHolder
|
||||
import com.naviapp.home.model.HomeBottomNavBarData
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.utils.Constants
|
||||
import com.naviapp.utils.Constants.BOTTOM_TABS
|
||||
|
||||
sealed interface HomeFooterEvents {
|
||||
data object FabButtonOnClick : HomeFooterEvents
|
||||
|
||||
data object FabButtonOnLaunchEvent : HomeFooterEvents
|
||||
|
||||
data object SnackBarOnClick : HomeFooterEvents
|
||||
|
||||
data class BottomNudgeOnClick(val actionData: ActionData) : HomeFooterEvents
|
||||
@@ -49,7 +41,6 @@ sealed interface HomeFooterEvents {
|
||||
|
||||
@Stable
|
||||
data class HomeFooterStates(
|
||||
val isFabButtonVisible: Boolean,
|
||||
val showSnackBar: Boolean,
|
||||
val bottomNavBarState: BottomNavBarStateHolder
|
||||
)
|
||||
@@ -57,27 +48,13 @@ data class HomeFooterStates(
|
||||
fun handleHomeFooterEvent(
|
||||
homeFooterEvents: HomeFooterEvents,
|
||||
selectedTabId: String,
|
||||
homeVM: HomeVM,
|
||||
homePageActivity: HomePageActivity,
|
||||
homeVM: () -> HomeViewModel,
|
||||
inAppUpdateVM: InAppUpdateVM,
|
||||
navController: NavHostController,
|
||||
sharedVM: SharedVM,
|
||||
dashboardSharedVM: DashboardSharedVM
|
||||
) {
|
||||
when (homeFooterEvents) {
|
||||
is HomeFooterEvents.FabButtonOnClick -> {
|
||||
handleFabOnClick(
|
||||
selectedTabId = selectedTabId,
|
||||
homeVM = homeVM,
|
||||
homePageActivity = homePageActivity
|
||||
)
|
||||
}
|
||||
is HomeFooterEvents.FabButtonOnLaunchEvent -> {
|
||||
NaviTrackEvent.trackEventOnClickStream(
|
||||
"NaviPay_fab_button_viewed",
|
||||
mapOf(BOTTOM_TABS to selectedTabId, CARD_NAME to homeVM.getFabButtonCardId())
|
||||
)
|
||||
}
|
||||
is HomeFooterEvents.BottomBarOnTabClick -> {
|
||||
onTabClick(
|
||||
selectedTabId = selectedTabId,
|
||||
@@ -102,22 +79,11 @@ fun handleHomeFooterEvent(
|
||||
inAppUpdateVM.inAppUpdateSnackBarClick()
|
||||
inAppUpdateVM.registerAppUpdateSuccessAnalytics()
|
||||
inAppUpdateVM.completeUpdate()
|
||||
homeVM.updateHomeScreenSnackBarState(showSnackBar = false)
|
||||
homeVM().sendEvent(HpEvents.UpdateSnackBarState(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleFabOnClick(selectedTabId: String, homeVM: HomeVM, homePageActivity: HomePageActivity) {
|
||||
NaviDeepLinkNavigator.navigate(
|
||||
activity = homePageActivity,
|
||||
ctaData = CtaData(url = "naviPay/NAVI_PAY_QR_SCANNER_SCREEN", needsResult = true)
|
||||
)
|
||||
NaviTrackEvent.trackEventOnClickStream(
|
||||
"NaviPay_fab_button_clicked",
|
||||
mapOf(BOTTOM_TABS to selectedTabId, CARD_NAME to homeVM.getFabButtonCardId())
|
||||
)
|
||||
}
|
||||
|
||||
private fun onTabClick(
|
||||
selectedTabId: String,
|
||||
navController: NavController,
|
||||
|
||||
@@ -27,70 +27,34 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.navi.common.alchemist.model.AlchemistCollapsingToolbar
|
||||
import com.navi.common.extensions.conditional
|
||||
import com.navi.naviwidgets.R as WidgetR
|
||||
import com.naviapp.R
|
||||
import com.naviapp.home.compose.extension.bottomShadow
|
||||
import com.naviapp.home.compose.widgetfactory.HomeWidgetRenderer
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
|
||||
@Composable
|
||||
fun HomeTopBarRoot(
|
||||
modifier: Modifier,
|
||||
appBarHeight: Dp,
|
||||
statusBarHeight: Dp,
|
||||
homeVM: HomeVM,
|
||||
topBarData: AlchemistCollapsingToolbar?,
|
||||
homeScrollState: () -> ScrollState
|
||||
) {
|
||||
val isScrollingUp by remember { derivedStateOf { homeScrollState().value.dp < appBarHeight } }
|
||||
val renderTopBar by homeVM.isReadyToRenderBackLayerAndTopAppBar.collectAsStateWithLifecycle()
|
||||
HomeTopBar(
|
||||
modifier = modifier,
|
||||
statusBarHeight = statusBarHeight,
|
||||
appBarHeight = appBarHeight,
|
||||
isScrollingUp = { isScrollingUp },
|
||||
renderTopBar = { renderTopBar },
|
||||
topBarContent = {
|
||||
topBarData?.toolBarNav?.uiTronResponse?.let {
|
||||
HomeWidgetRenderer(
|
||||
widgetData = it.data,
|
||||
viewModel = homeVM,
|
||||
composeView = it.parentComposeView,
|
||||
homeScrollState = homeScrollState
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeTopBar(
|
||||
modifier: Modifier,
|
||||
appBarHeight: Dp,
|
||||
statusBarHeight: Dp,
|
||||
isScrollingUp: () -> Boolean,
|
||||
renderTopBar: () -> Boolean,
|
||||
topBarContent: @Composable () -> Unit
|
||||
topBarContent: @Composable () -> Unit,
|
||||
homeScrollState: () -> ScrollState
|
||||
) {
|
||||
val isScrollingUp by remember { derivedStateOf { homeScrollState().value.dp < appBarHeight } }
|
||||
// Set the modifier for the top bar based on scrolling direction
|
||||
val topBarModifier =
|
||||
remember(isScrollingUp()) {
|
||||
remember(isScrollingUp) {
|
||||
modifier
|
||||
.conditional(!isScrollingUp()) { requiredHeight(appBarHeight + 4.dp) }
|
||||
.bottomShadow(showShadow = isScrollingUp().not(), elevation = 8f)
|
||||
.conditional(!isScrollingUp) { requiredHeight(appBarHeight + 4.dp) }
|
||||
.bottomShadow(showShadow = isScrollingUp.not(), elevation = 8f)
|
||||
.drawBehind {
|
||||
drawRect(color = if (isScrollingUp()) Color.Transparent else Color.White)
|
||||
drawRect(color = if (isScrollingUp) Color.Transparent else Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
// Render the top bar
|
||||
Row(modifier = topBarModifier, verticalAlignment = Alignment.Top) {
|
||||
Row(Modifier.padding(top = statusBarHeight)) {
|
||||
if (renderTopBar()) topBarContent() else DefaultTopBar()
|
||||
}
|
||||
Row(Modifier.padding(top = statusBarHeight)) { topBarContent() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -48,16 +47,19 @@ import com.naviapp.home.compose.home.navigation.HomePageNavHost
|
||||
import com.naviapp.home.compose.home.ui.dialog.HomeScreenDialog
|
||||
import com.naviapp.home.compose.home.ui.footer.HomeFooterRoot
|
||||
import com.naviapp.home.compose.home.ui.footer.utils.updateTabSelection
|
||||
import com.naviapp.home.compose.home.utils.handleCtaActionEvents
|
||||
import com.naviapp.home.compose.home.utils.onFetchHomeApiCall
|
||||
import com.naviapp.home.compose.model.InitiatePaymentFromComposeData
|
||||
import com.naviapp.home.compose.profile.ProfileScreen
|
||||
import com.naviapp.home.compose.profile.ProfileScreenShimmer
|
||||
import com.naviapp.home.model.HomeCtaTypes
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.ProfileVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.payment.viewmodel.PaymentVM
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun HomePageActivityMainScreen(
|
||||
@@ -66,7 +68,7 @@ fun HomePageActivityMainScreen(
|
||||
dashboardSharedVM: DashboardSharedVM,
|
||||
inAppUpdateVM: InAppUpdateVM,
|
||||
profileVM: ProfileVM,
|
||||
homeVM: HomeVM,
|
||||
homeVM: HomeViewModel,
|
||||
paymentVM: PaymentVM,
|
||||
notificationVM: NotificationVM,
|
||||
sharedVM: SharedVM,
|
||||
@@ -75,24 +77,52 @@ fun HomePageActivityMainScreen(
|
||||
initiatePayment: (paymentData: InitiatePaymentFromComposeData) -> Unit,
|
||||
hopper: Hopper
|
||||
) {
|
||||
val hpStates by homeVM.state.collectAsStateWithLifecycle()
|
||||
val navController = rememberNavController()
|
||||
val selectedTabId by sharedVM.selectedTabId.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
homeVM.effect.collect { hpEffect ->
|
||||
homePageActivity.homeEffectHandler
|
||||
.get()
|
||||
.handleEffects(
|
||||
effects = hpEffect,
|
||||
onPaymentInitiated = initiatePayment,
|
||||
onFetchHomeApiCall = {
|
||||
onFetchHomeApiCall(homeVM, homePageActivity, paymentVM)
|
||||
},
|
||||
homeVM = homeVM,
|
||||
handleCtaAction = {
|
||||
handleCtaActionEvents(
|
||||
it,
|
||||
homeVM,
|
||||
hpStates,
|
||||
sharedVM,
|
||||
homePageActivity,
|
||||
paymentVM
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
InitActionsHandler(
|
||||
viewModel = homeVM,
|
||||
activity = homePageActivity,
|
||||
naviAnalyticsEventTracker = naviHomeAnalytics,
|
||||
isPaymentLoaderShowing = paymentVM.isPaymentLoaderShowing()
|
||||
)
|
||||
|
||||
LaunchedEffect(key1 = selectedTabId) {
|
||||
LaunchedEffect(selectedTabId) {
|
||||
onTabSelected.invoke(selectedTabId)
|
||||
homeVM.checkForOnHiddenChanged(
|
||||
selectedTabId = selectedTabId,
|
||||
bottomNavBarVM = bottomNavBarVM,
|
||||
inAppUpdateVM = inAppUpdateVM
|
||||
homeVM.sendEvent(
|
||||
HpEvents.HandleAppUpdateNudgeVisibility(
|
||||
selectedTab = selectedTabId,
|
||||
bottomNavBarVM = bottomNavBarVM,
|
||||
inAppUpdateVM = inAppUpdateVM
|
||||
)
|
||||
)
|
||||
}
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
LaunchedEffect(Unit) {
|
||||
bottomNavBarVM.updateTabSelection.collect { updateTabSelection ->
|
||||
bottomNavBarVM.clearUpdateTabSelectionReplay()
|
||||
updateTabSelection(
|
||||
@@ -104,10 +134,6 @@ fun HomePageActivityMainScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
homeVM.initiatePaymentFromComposeScreen.collect { initiatePayment.invoke(it) }
|
||||
}
|
||||
|
||||
InitHomeActivityScreen(
|
||||
bottomNavBarVM = bottomNavBarVM,
|
||||
inAppUpdateVM = inAppUpdateVM,
|
||||
@@ -115,13 +141,15 @@ fun HomePageActivityMainScreen(
|
||||
naviHomeAnalytics = naviHomeAnalytics,
|
||||
selectedTabId = selectedTabId,
|
||||
profileVM = profileVM,
|
||||
homeVM = homeVM,
|
||||
hpStates = { hpStates },
|
||||
paymentVM = paymentVM,
|
||||
homeVM = { homeVM },
|
||||
dashboardSharedVM = dashboardSharedVM,
|
||||
notificationVM = notificationVM,
|
||||
sharedVM = sharedVM,
|
||||
navController = navController,
|
||||
hopper = hopper
|
||||
hopper = hopper,
|
||||
onHomeScreenEvent = { homeVM.sendEvent(it) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -133,30 +161,31 @@ private fun InitHomeActivityScreen(
|
||||
inAppUpdateVM: InAppUpdateVM,
|
||||
naviHomeAnalytics: NaviAnalytics.Home,
|
||||
selectedTabId: String,
|
||||
homeVM: () -> HomeViewModel,
|
||||
profileVM: ProfileVM,
|
||||
homeVM: HomeVM,
|
||||
hpStates: () -> HpStates,
|
||||
paymentVM: PaymentVM,
|
||||
dashboardSharedVM: DashboardSharedVM,
|
||||
notificationVM: NotificationVM,
|
||||
sharedVM: SharedVM,
|
||||
navController: NavHostController,
|
||||
hopper: Hopper
|
||||
hopper: Hopper,
|
||||
onHomeScreenEvent: (event: HpEvents) -> Unit = {}
|
||||
) {
|
||||
val drawerState = rememberNaviDrawerState(NaviDrawerValue.Closed)
|
||||
val investmentListState = rememberLazyListState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
ResetProfileScroll(homeVM, profileVM) { drawerState }
|
||||
ResetProfileScroll(
|
||||
profileVM = profileVM,
|
||||
drawerState = { drawerState },
|
||||
onHomeScreenEvent = onHomeScreenEvent
|
||||
)
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
homeVM.profileDrawerState.collect { openDrawer ->
|
||||
coroutineScope.launch {
|
||||
if (openDrawer) {
|
||||
if (drawerState.isClosed) drawerState.open()
|
||||
} else {
|
||||
if (drawerState.isOpen) drawerState.close()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(hpStates().profileDrawerState) {
|
||||
if (hpStates().profileDrawerState) {
|
||||
if (drawerState.isClosed) drawerState.open()
|
||||
} else {
|
||||
if (drawerState.isOpen) drawerState.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,16 +193,14 @@ private fun InitHomeActivityScreen(
|
||||
handleBottomSheetAction(action = action, activity = homePageActivity)
|
||||
}
|
||||
|
||||
HomeScreenDialog(homeVM)
|
||||
HomeScreenDialog(homeVM = homeVM, hpStates = hpStates)
|
||||
|
||||
NaviModalNavigationDrawer(
|
||||
drawerState = { drawerState },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
drawerContent = {
|
||||
// Will show shimmer until the home screen is rendered
|
||||
val isReadyToRenderProfileScreen by
|
||||
homeVM.isReadyToRenderProfileScreen.collectAsStateWithLifecycle()
|
||||
if (isReadyToRenderProfileScreen) {
|
||||
if (hpStates().renderingFirstTime.not()) {
|
||||
ProfileScreen(
|
||||
profileVM = profileVM,
|
||||
drawerState = { drawerState },
|
||||
@@ -198,7 +225,8 @@ private fun InitHomeActivityScreen(
|
||||
sharedVM = sharedVM,
|
||||
naviHomeAnalytics = naviHomeAnalytics,
|
||||
selectedTabId = selectedTabId,
|
||||
hopper = hopper
|
||||
hopper = hopper,
|
||||
hpStates = hpStates
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -238,13 +266,13 @@ private fun handleBottomSheetAction(action: UiTronAction?, activity: HomePageAct
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun ResetProfileScroll(
|
||||
homeVM: HomeVM,
|
||||
profileVM: ProfileVM,
|
||||
drawerState: () -> DrawerState
|
||||
drawerState: () -> DrawerState,
|
||||
onHomeScreenEvent: (event: HpEvents) -> Unit
|
||||
) {
|
||||
LaunchedEffect(drawerState().currentValue) {
|
||||
val isDrawerOpen = drawerState().currentValue == NaviDrawerValue.Open
|
||||
homeVM.isProfileDrawerOpen = isDrawerOpen
|
||||
onHomeScreenEvent(HpEvents.UpdateProfileDrawerState(isDrawerOpen))
|
||||
if (!isDrawerOpen) {
|
||||
profileVM.resetProfileScrollToTop(reset = true)
|
||||
}
|
||||
@@ -258,7 +286,8 @@ private fun ScreenContent(
|
||||
bottomNavBarVM: BottomNavBarVM,
|
||||
inAppUpdateVM: InAppUpdateVM,
|
||||
investmentListState: () -> LazyListState,
|
||||
homeVM: HomeVM,
|
||||
homeVM: () -> HomeViewModel,
|
||||
hpStates: () -> HpStates,
|
||||
dashboardSharedVM: DashboardSharedVM,
|
||||
paymentVM: PaymentVM,
|
||||
notificationVM: NotificationVM,
|
||||
@@ -273,7 +302,8 @@ private fun ScreenContent(
|
||||
homePageActivity = homePageActivity,
|
||||
navController = navController,
|
||||
dashboardSharedVM = dashboardSharedVM,
|
||||
homeVM = homeVM,
|
||||
hpStates = hpStates,
|
||||
homeViewModel = homeVM,
|
||||
notificationVM = notificationVM,
|
||||
sharedVM = sharedVM,
|
||||
paymentVM = paymentVM,
|
||||
@@ -286,8 +316,8 @@ private fun ScreenContent(
|
||||
HomeFooterRoot(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
homeVM = homeVM,
|
||||
hpStates = hpStates,
|
||||
inAppUpdateVM = inAppUpdateVM,
|
||||
homePageActivity = homePageActivity,
|
||||
bottomNavBarVM = bottomNavBarVM,
|
||||
dashboardSharedVM = dashboardSharedVM,
|
||||
sharedVM = sharedVM,
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
package com.naviapp.home.compose.home.ui.screen
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -22,7 +22,6 @@ import androidx.compose.material.BackdropValue
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.rememberBackdropScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -31,28 +30,28 @@ import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.ui.errorview.FullScreenErrorComposeView
|
||||
import com.navi.common.utils.getStatusBarHeight
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.common.viewmodel.InAppUpdateVM
|
||||
import com.naviapp.dashboard.viewmodels.DashboardSharedVM
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.home.ui.content.BackLayerContent
|
||||
import com.naviapp.home.compose.home.ui.content.FrontLayerContent
|
||||
import com.naviapp.home.compose.home.ui.header.HomeTopBarRoot
|
||||
import com.naviapp.home.compose.home.ui.header.DefaultTopBar
|
||||
import com.naviapp.home.compose.home.ui.header.HomeTopBar
|
||||
import com.naviapp.home.compose.home.utils.InitHomeScreenComponents
|
||||
import com.naviapp.home.compose.home.utils.TopNaviMiddlePillAnimation
|
||||
import com.naviapp.home.compose.home.utils.handleHomeFooterRendering
|
||||
import com.naviapp.home.compose.home.utils.retryHomePageApi
|
||||
import com.naviapp.home.compose.listener.HomeScreenCallbackListener
|
||||
import com.naviapp.home.compose.widgetfactory.HomeWidgetRenderer
|
||||
import com.naviapp.home.listener.StickyBottomNudgeListener
|
||||
import com.naviapp.home.ui.state.HomeScreenState
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.payment.viewmodel.PaymentVM
|
||||
import com.naviapp.utils.Constants.HomePageConstants.HOME_APP_BAR_HEIGHT
|
||||
import com.naviapp.utils.Constants.HomePageConstants.HOME_BACK_LAYER_BANNER_HEIGHT
|
||||
import com.naviapp.utils.Constants.HomePageConstants.HOME_FRONT_LAYER_ELEVATION
|
||||
@@ -62,15 +61,15 @@ import com.naviapp.utils.Constants.HomePageConstants.HOME_SHAPE_CURVATURE
|
||||
fun HomeScreen(
|
||||
activity: HomePageActivity,
|
||||
dashboardSharedVM: DashboardSharedVM,
|
||||
homeVM: HomeVM,
|
||||
paymentVM: PaymentVM,
|
||||
homeVM: () -> HomeViewModel,
|
||||
hpStates: HpStates,
|
||||
onHomeScreenEvent: (event: HpEvents) -> Unit,
|
||||
sharedVM: SharedVM,
|
||||
notificationVM: NotificationVM,
|
||||
naviAnalyticsEventTracker: NaviAnalytics.Home,
|
||||
inAppUpdateVM: InAppUpdateVM,
|
||||
callBackToActivityScreen: (callback: HomeScreenCallbackListener) -> Unit
|
||||
) {
|
||||
val homeScreenData by homeVM.homeScreenState.collectAsStateWithLifecycle()
|
||||
val stickyBottomNudgeListener: StickyBottomNudgeListener = remember { activity }
|
||||
val homeScrollState = rememberScrollState()
|
||||
|
||||
@@ -78,7 +77,6 @@ fun HomeScreen(
|
||||
activity = activity,
|
||||
dashboardSharedVM = dashboardSharedVM,
|
||||
homeVM = homeVM,
|
||||
paymentVM = paymentVM,
|
||||
sharedVM = sharedVM,
|
||||
notificationVM = notificationVM,
|
||||
naviAnalyticsEventTracker = naviAnalyticsEventTracker,
|
||||
@@ -88,35 +86,51 @@ fun HomeScreen(
|
||||
homeScrollState = { homeScrollState },
|
||||
)
|
||||
|
||||
when (homeScreenData) {
|
||||
HomeScreenState.Loading -> {
|
||||
HomeScreenScaffoldRoot(
|
||||
screenDefinition = AlchemistScreenDefinition(),
|
||||
homeVM = homeVM,
|
||||
homeScrollState = { homeScrollState },
|
||||
) {
|
||||
true
|
||||
when (hpStates.isError.not()) {
|
||||
true -> {
|
||||
if (hpStates.isLoading) {
|
||||
onHomeScreenEvent(HpEvents.UpdateShadowOnFrontLayer(true))
|
||||
HomeScreenScaffoldRoot(
|
||||
homeWidgetRenderer = {
|
||||
HomeWidgetRenderer(
|
||||
widgetData = it?.data,
|
||||
viewModel = homeVM(),
|
||||
composeView = it?.parentComposeView,
|
||||
homeScrollState = { homeScrollState }
|
||||
)
|
||||
},
|
||||
homeScrollState = { homeScrollState },
|
||||
hpStates = hpStates,
|
||||
homeVM = homeVM
|
||||
)
|
||||
} else {
|
||||
onHomeScreenEvent(HpEvents.UpdateShadowOnFrontLayer(false))
|
||||
hpStates.screenDefinition?.let {
|
||||
HomeScreenScaffoldRoot(
|
||||
homeWidgetRenderer = { widget ->
|
||||
HomeWidgetRenderer(
|
||||
widgetData = widget?.data,
|
||||
viewModel = homeVM(),
|
||||
composeView = widget?.parentComposeView,
|
||||
homeScrollState = { homeScrollState }
|
||||
)
|
||||
},
|
||||
homeScrollState = { homeScrollState },
|
||||
hpStates = hpStates,
|
||||
homeVM = homeVM
|
||||
)
|
||||
handleHomeFooterRendering(
|
||||
screenDefinition = it,
|
||||
sharedVM = sharedVM,
|
||||
stickyBottomNudgeListener = stickyBottomNudgeListener
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is HomeScreenState.Success -> {
|
||||
val screenDefinition = (homeScreenData as HomeScreenState.Success).data
|
||||
HomeScreenScaffoldRoot(
|
||||
screenDefinition = screenDefinition,
|
||||
homeVM = homeVM,
|
||||
homeScrollState = { homeScrollState }
|
||||
) {
|
||||
false
|
||||
}
|
||||
handleHomeFooterRendering(
|
||||
screenDefinition = screenDefinition,
|
||||
sharedVM = sharedVM,
|
||||
stickyBottomNudgeListener = stickyBottomNudgeListener
|
||||
)
|
||||
}
|
||||
is HomeScreenState.Error -> {
|
||||
false -> {
|
||||
activity.hideLoader()
|
||||
FullScreenErrorComposeView(
|
||||
error = (homeScreenData as HomeScreenState.Error).error,
|
||||
error = hpStates.error,
|
||||
onRetryClick = {
|
||||
retryHomePageApi(
|
||||
homeVM = homeVM,
|
||||
@@ -131,12 +145,11 @@ fun HomeScreen(
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun HomeScreenScaffoldRoot(
|
||||
screenDefinition: AlchemistScreenDefinition,
|
||||
homeVM: HomeVM,
|
||||
hpStates: HpStates,
|
||||
homeScrollState: () -> ScrollState,
|
||||
showShadowOnFrontLayer: () -> Boolean,
|
||||
homeVM: () -> HomeViewModel,
|
||||
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit
|
||||
) {
|
||||
|
||||
val density = LocalDensity.current
|
||||
val statusBarHeight = remember { with(density) { getStatusBarHeight().toDp() } }
|
||||
val appBarHeight = remember { HOME_APP_BAR_HEIGHT + statusBarHeight }
|
||||
@@ -144,38 +157,46 @@ private fun HomeScreenScaffoldRoot(
|
||||
val frontLayerShape = remember {
|
||||
RoundedCornerShape(topStart = HOME_SHAPE_CURVATURE, topEnd = HOME_SHAPE_CURVATURE)
|
||||
}
|
||||
val homePageState = rememberBackdropScaffoldState(initialValue = BackdropValue.Concealed)
|
||||
val backdropScaffoldState =
|
||||
rememberBackdropScaffoldState(initialValue = BackdropValue.Concealed)
|
||||
|
||||
HomeBackdropScaffold(
|
||||
state = { homePageState },
|
||||
state = { backdropScaffoldState },
|
||||
appBarHeight = appBarHeight,
|
||||
backLayerHeight = backLayerHeight,
|
||||
frontLayerShape = frontLayerShape,
|
||||
showShadowOnFrontLayer = showShadowOnFrontLayer,
|
||||
showShadowOnFrontLayer = hpStates.showShadowOnFrontLayer,
|
||||
appBar = {
|
||||
HomeTopBarRoot(
|
||||
HomeTopBar(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
appBarHeight = appBarHeight,
|
||||
statusBarHeight = statusBarHeight,
|
||||
topBarData = screenDefinition.screenStructure?.collapsingToolbar,
|
||||
homeVM = homeVM,
|
||||
topBarContent = {
|
||||
(hpStates.screenDefinition)
|
||||
?.screenStructure
|
||||
?.collapsingToolbar
|
||||
?.toolBarNav
|
||||
?.uiTronResponse
|
||||
?.let { homeWidgetRenderer(it) } ?: DefaultTopBar()
|
||||
},
|
||||
homeScrollState = homeScrollState
|
||||
)
|
||||
},
|
||||
backLayer = {
|
||||
BackLayerContent(
|
||||
modifier = Modifier.requiredHeight(backLayerHeight + HOME_SHAPE_CURVATURE),
|
||||
backLayerData = screenDefinition.screenStructure?.collapsingToolbar,
|
||||
homeVM = homeVM,
|
||||
homeScrollState = homeScrollState
|
||||
)
|
||||
Column(Modifier.requiredHeight(backLayerHeight + HOME_SHAPE_CURVATURE).fillMaxWidth()) {
|
||||
(hpStates.screenDefinition)
|
||||
?.screenStructure
|
||||
?.collapsingToolbar
|
||||
?.collapsingTopNav
|
||||
?.uiTronResponse
|
||||
?.let { homeWidgetRenderer(it) }
|
||||
}
|
||||
},
|
||||
frontLayer = {
|
||||
FrontLayerContent(
|
||||
modifier = Modifier.fillMaxHeight().background(Color.White, frontLayerShape),
|
||||
isShimmerVisible =
|
||||
screenDefinition.screenStructure?.content?.widgets.isNullOrEmpty(),
|
||||
homeVM = homeVM,
|
||||
homeVM = homeVM(),
|
||||
frontLayerShape = frontLayerShape,
|
||||
hpStates = { hpStates },
|
||||
homeScrollState = homeScrollState
|
||||
)
|
||||
}
|
||||
@@ -185,7 +206,7 @@ private fun HomeScreenScaffoldRoot(
|
||||
appBarHeight = appBarHeight,
|
||||
backLayerHeight = backLayerHeight - HOME_SHAPE_CURVATURE,
|
||||
homeVM = homeVM,
|
||||
naviHomeScaffoldState = { homePageState }
|
||||
backdropState = { backdropScaffoldState }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -196,7 +217,7 @@ fun HomeBackdropScaffold(
|
||||
appBarHeight: Dp,
|
||||
backLayerHeight: Dp,
|
||||
frontLayerShape: Shape,
|
||||
showShadowOnFrontLayer: () -> Boolean,
|
||||
showShadowOnFrontLayer: Boolean,
|
||||
appBar: @Composable BoxScope.() -> Unit,
|
||||
backLayer: @Composable () -> Unit,
|
||||
frontLayer: @Composable () -> Unit
|
||||
@@ -212,8 +233,7 @@ fun HomeBackdropScaffold(
|
||||
frontLayerShape = frontLayerShape,
|
||||
frontLayerScrimColor = Color.Unspecified,
|
||||
backLayerContent = backLayer,
|
||||
frontLayerElevation =
|
||||
if (showShadowOnFrontLayer()) HOME_FRONT_LAYER_ELEVATION else 0.dp,
|
||||
frontLayerElevation = if (showShadowOnFrontLayer) HOME_FRONT_LAYER_ELEVATION else 0.dp,
|
||||
frontLayerBackgroundColor = Color.White,
|
||||
backLayerBackgroundColor = Color.White,
|
||||
frontLayerContent = frontLayer,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.naviapp.home.compose.home.utils
|
||||
|
||||
import com.navi.common.utils.TemporaryStorageHelper
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.listener.HomeScreenCallbackListener
|
||||
import com.naviapp.home.compose.model.CtaActionEvent
|
||||
@@ -14,13 +15,50 @@ import com.naviapp.home.model.HpBottomSheetContent
|
||||
import com.naviapp.home.model.HpBottomSheetRenderType
|
||||
import com.naviapp.home.model.HpBottomSheetState
|
||||
import com.naviapp.home.model.HpDialogStateHolder
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.payment.viewmodel.PaymentVM
|
||||
import com.naviapp.utils.copyText
|
||||
|
||||
fun handleCtaAction(
|
||||
fun handleCtaActionEvents(
|
||||
it: CtaActionEvent,
|
||||
homeVM: HomeViewModel,
|
||||
hpStates: HpStates,
|
||||
sharedVM: SharedVM,
|
||||
homePageActivity: HomePageActivity,
|
||||
paymentVM: PaymentVM
|
||||
) {
|
||||
handleCtaAction(
|
||||
event = it,
|
||||
homeVM = { homeVM },
|
||||
hpStates = { hpStates },
|
||||
sharedVM = sharedVM,
|
||||
activity = homePageActivity
|
||||
) { callback ->
|
||||
when (callback) {
|
||||
HomeScreenCallbackListener.ApiCallingWithCondition -> {
|
||||
TemporaryStorageHelper.updateViewVisibility(TemporaryStorageHelper.HOME, false)
|
||||
homeVM.loadHomeElements(
|
||||
activity = homePageActivity,
|
||||
isPaymentLoaderShowing = paymentVM.isPaymentLoaderShowing()
|
||||
)
|
||||
}
|
||||
is HomeScreenCallbackListener.InitiatePaymentOnHomePage -> {
|
||||
homeVM.sendEffect(homeVM.coroutineScope) {
|
||||
HpEffects.InitiatePayment(callback.paymentData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCtaAction(
|
||||
event: CtaActionEvent,
|
||||
homeVM: HomeVM,
|
||||
homeVM: () -> HomeViewModel,
|
||||
hpStates: () -> HpStates,
|
||||
sharedVM: SharedVM,
|
||||
activity: HomePageActivity,
|
||||
callBackToActivityScreen: (callback: HomeScreenCallbackListener) -> Unit
|
||||
@@ -28,19 +66,20 @@ fun handleCtaAction(
|
||||
when (event) {
|
||||
is CtaActionEvent.RedirectToCta -> {
|
||||
event.ctaData?.let {
|
||||
homeVM.handleCtaData(
|
||||
naviClickAction = it,
|
||||
activity = activity,
|
||||
callBackToActivityScreen = callBackToActivityScreen,
|
||||
)
|
||||
homeVM()
|
||||
.handleCtaData(
|
||||
naviClickAction = it,
|
||||
activity = activity,
|
||||
callBackToActivityScreen = callBackToActivityScreen,
|
||||
)
|
||||
}
|
||||
}
|
||||
is CtaActionEvent.CopyToClipboard -> {
|
||||
copyText(context = activity, text = event.value)
|
||||
}
|
||||
is CtaActionEvent.ShowBottomSheet -> {
|
||||
homeVM
|
||||
.getHomeScreenData()
|
||||
hpStates()
|
||||
.screenDefinition
|
||||
?.screenStructure
|
||||
?.bottomSheets
|
||||
?.firstOrNull { event.bottomSheetId == it.screenId }
|
||||
@@ -57,10 +96,16 @@ fun handleCtaAction(
|
||||
}
|
||||
is CtaActionEvent.ShowDialog -> {
|
||||
val dialogData =
|
||||
homeVM.getHomeScreenData()?.screenStructure?.dialogs?.find {
|
||||
hpStates().screenDefinition?.screenStructure?.dialogs?.find {
|
||||
it.dialogId == event.dialogId
|
||||
}
|
||||
homeVM.updateDialogState(HpDialogStateHolder.HpDialogState.Visible, dialogData)
|
||||
homeVM()
|
||||
.sendEvent(
|
||||
HpEvents.UpdateHpDialogStateHolder(
|
||||
HpDialogStateHolder.HpDialogState.Visible,
|
||||
dialogData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,46 +7,54 @@
|
||||
|
||||
package com.naviapp.home.compose.home.utils
|
||||
|
||||
//noinspection UsingMaterialAndMaterial3Libraries
|
||||
import androidx.compose.material.BackdropScaffoldState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.navi.uitron.utils.toPx
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.utils.Constants.HomePageConstants.SCROLL_FADE
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun TopNaviMiddlePillAnimation(
|
||||
appBarHeight: Dp,
|
||||
backLayerHeight: Dp,
|
||||
homeVM: HomeVM,
|
||||
naviHomeScaffoldState: () -> BackdropScaffoldState
|
||||
homeVM: () -> HomeViewModel,
|
||||
backdropState: () -> BackdropScaffoldState
|
||||
) {
|
||||
val offsetValue by homeVM.topNavOffsetValue.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(naviHomeScaffoldState().currentValue) {
|
||||
if (naviHomeScaffoldState().isRevealed) {
|
||||
homeVM.triggerCollapsedEvent()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(naviHomeScaffoldState().progress) {
|
||||
homeVM.updateOffsetValue(naviHomeScaffoldState().offset.value)
|
||||
}
|
||||
UpdateTopNavOffset(homeVM, backdropState)
|
||||
|
||||
val normalizedOffset =
|
||||
normalizeOffset(
|
||||
offsetValue,
|
||||
offset = homeVM().backdropScaffoldProgress.collectAsStateWithLifecycle().value,
|
||||
minOffset = appBarHeight.toPx(),
|
||||
maxOffset = backLayerHeight.toPx()
|
||||
)
|
||||
|
||||
homeVM.handle[SCROLL_FADE] = (1 - normalizedOffset).coerceIn(0f, 1f)
|
||||
homeVM().handle[SCROLL_FADE] = (1 - normalizedOffset).coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
fun normalizeOffset(offset: Float, minOffset: Float, maxOffset: Float): Float {
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
private fun UpdateTopNavOffset(
|
||||
homeVM: () -> HomeViewModel,
|
||||
backdropState: () -> BackdropScaffoldState
|
||||
) {
|
||||
LaunchedEffect(backdropState().progress) {
|
||||
homeVM().viewModelScope.launch {
|
||||
homeVM().updateBackdropScaffoldProgress(backdropState().offset.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeOffset(offset: Float, minOffset: Float, maxOffset: Float): Float {
|
||||
val offsetRange = maxOffset - minOffset
|
||||
if (offsetRange <= 0) {
|
||||
error("Invalid offset range. minOffset must be less than maxOffset.")
|
||||
|
||||
@@ -13,49 +13,63 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import com.navi.alfred.AlfredManager
|
||||
import com.navi.base.sharedpref.PreferenceManager
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
|
||||
import com.navi.common.alchemist.model.WidgetRenderState
|
||||
import com.navi.common.utils.CommonUtils.getDynamicModulePrefix
|
||||
import com.navi.common.utils.getDensityName
|
||||
import com.navi.common.utils.getInstalledDynamicModulesCommaSeparated
|
||||
import com.navi.common.utils.getNetworkType
|
||||
import com.navi.common.utils.installDynamicModules
|
||||
import com.navi.common.utils.isDynamicModuleInstalled
|
||||
import com.navi.naviwidgets.utils.CURRENT_VERSION_IN_STORE
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.home.ui.footer.utils.handleAppUpdateNudge
|
||||
import com.naviapp.home.compose.home.ui.footer.utils.handleBottomBarData
|
||||
import com.naviapp.home.compose.listener.HomeScreenCallbackListener
|
||||
import com.naviapp.home.listener.StickyBottomNudgeListener
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.payment.viewmodel.PaymentVM
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@Composable
|
||||
fun InitLifecycleListener(
|
||||
homeVM: HomeVM,
|
||||
homeVM: () -> HomeViewModel,
|
||||
activity: HomePageActivity,
|
||||
callBackToActivityScreen: (callback: HomeScreenCallbackListener) -> Unit
|
||||
) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
DisposableEffect(key1 = lifecycleOwner) {
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_CREATE -> {
|
||||
AlfredManager.setCurrentScreenName(screenName = activity.screenName)
|
||||
homeVM.updateUPIVpaObserver()
|
||||
homeVM()
|
||||
.observeUPIVpa(
|
||||
onAction = {
|
||||
homeVM().sendEffect(CoroutineScope(Dispatchers.Default)) {
|
||||
HpEffects.OnUitronAction(it)
|
||||
}
|
||||
},
|
||||
onActionData = {
|
||||
homeVM().sendEffect(CoroutineScope(Dispatchers.Default)) {
|
||||
HpEffects.OnActionData(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Lifecycle.Event.ON_RESUME -> {
|
||||
if (homeVM.shouldMakeAPICall()) {
|
||||
homeVM.triggerHideBalanceAction()
|
||||
if (homeVM().shouldRefreshHomeApi) {
|
||||
homeVM().sendEffect(homeVM().coroutineScope) { HpEffects.OnRenderActions }
|
||||
callBackToActivityScreen.invoke(
|
||||
HomeScreenCallbackListener.ApiCallingWithCondition
|
||||
)
|
||||
}
|
||||
homeVM.updateShouldMakeAPICallState(shouldRefresh = true)
|
||||
homeVM.updateUpiLiteBalance()
|
||||
homeVM.updateUPILiteBalanceV2()
|
||||
homeVM.updateUPIVpa()
|
||||
homeVM().setHomeApiRefreshFlag(true)
|
||||
homeVM().handleUpiAdaptations()
|
||||
AlfredManager.setCurrentScreenName(screenName = activity.screenName)
|
||||
}
|
||||
else -> {}
|
||||
@@ -67,17 +81,11 @@ fun InitLifecycleListener(
|
||||
}
|
||||
|
||||
fun retryHomePageApi(
|
||||
homeVM: HomeVM,
|
||||
homeVM: () -> HomeViewModel,
|
||||
activity: HomePageActivity,
|
||||
) {
|
||||
homeVM.fetchHomeItems(
|
||||
density = getDensityName(context = activity).orEmpty(),
|
||||
connectivityType = getNetworkType(context = activity),
|
||||
availableAppVersionCode = PreferenceManager.getIntPreferenceApp(CURRENT_VERSION_IN_STORE),
|
||||
installedModules = getInstalledDynamicModulesCommaSeparated(),
|
||||
clearReferralPopupPreferences = false
|
||||
)
|
||||
homeVM.resetHomeScreenDataStateToLoading()
|
||||
homeVM().loadHomeElements(activity)
|
||||
homeVM().sendEvent(HpEvents.TriggerLoadingState)
|
||||
}
|
||||
|
||||
fun handleHomeFooterRendering(
|
||||
@@ -89,6 +97,18 @@ fun handleHomeFooterRendering(
|
||||
handleBottomBarData(screenDefinition, sharedVM)
|
||||
}
|
||||
|
||||
fun onFetchHomeApiCall(
|
||||
homeVM: HomeViewModel,
|
||||
homePageActivity: HomePageActivity,
|
||||
paymentVM: PaymentVM
|
||||
) {
|
||||
homeVM.naviAnalyticsEventTracker.onHomePageApiCalledFromUiTronAction()
|
||||
homeVM.loadHomeElements(
|
||||
activity = homePageActivity,
|
||||
isPaymentLoaderShowing = paymentVM.isPaymentLoaderShowing()
|
||||
)
|
||||
}
|
||||
|
||||
fun checkForModulesInstall(
|
||||
dynamicModulesList: List<String?>,
|
||||
screenName: String,
|
||||
@@ -114,3 +134,41 @@ fun checkForModulesInstall(
|
||||
naviAnalyticsEventTracker.moduleAlreadyInstalled(module)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateScreenContent(
|
||||
state: HpStates,
|
||||
newWidgets: List<AlchemistWidgetModelDefinition<UiTronResponse>>,
|
||||
currentWidgets: List<AlchemistWidgetModelDefinition<UiTronResponse>>
|
||||
): List<AlchemistWidgetModelDefinition<UiTronResponse>> {
|
||||
val updatedList = mutableListOf<AlchemistWidgetModelDefinition<UiTronResponse>>()
|
||||
val seenWidgetIds = mutableSetOf<String>()
|
||||
|
||||
newWidgets.forEach { widget ->
|
||||
val widgetId = widget.widgetId
|
||||
if (!widgetId.isNullOrBlank()) {
|
||||
seenWidgetIds.add(widgetId)
|
||||
val uiState =
|
||||
if (currentWidgets.any { it.widgetId == widgetId } || state.renderingFirstTime) {
|
||||
WidgetRenderState.VISIBLE
|
||||
} else {
|
||||
WidgetRenderState.NEWLY_ADDED
|
||||
}
|
||||
updatedList.add(widget.copy(widgetRenderState = uiState))
|
||||
}
|
||||
}
|
||||
|
||||
currentWidgets.forEachIndexed { index, widget ->
|
||||
val widgetId = widget.widgetId
|
||||
if (!widgetId.isNullOrBlank() && widgetId !in seenWidgetIds) {
|
||||
updatedList.getOrNull(index)?.let {
|
||||
updatedList.add(
|
||||
index,
|
||||
widget.copy(widgetRenderState = WidgetRenderState.NOT_VISIBLE)
|
||||
)
|
||||
} ?: updatedList.add(widget.copy(widgetRenderState = WidgetRenderState.NOT_VISIBLE))
|
||||
updatedList.add(widget.copy(widgetRenderState = WidgetRenderState.NOT_VISIBLE))
|
||||
}
|
||||
}
|
||||
|
||||
return updatedList
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.navi.common.commoncomposables.utils.ScrollToTopListener
|
||||
import com.navi.common.utils.TemporaryStorageHelper
|
||||
import com.navi.insurance.util.observeNonNull
|
||||
import com.navi.naviwidgets.utils.toCtaData
|
||||
import com.naviapp.R
|
||||
@@ -24,18 +23,19 @@ import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.listener.HomeScreenCallbackListener
|
||||
import com.naviapp.home.listener.StickyBottomNudgeListener
|
||||
import com.naviapp.home.model.BottomBarTabType
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.payment.viewmodel.PaymentVM
|
||||
import com.naviapp.utils.toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@Composable
|
||||
fun InitHomeScreenComponents(
|
||||
activity: HomePageActivity,
|
||||
dashboardSharedVM: DashboardSharedVM,
|
||||
homeVM: HomeVM,
|
||||
paymentVM: PaymentVM,
|
||||
homeVM: () -> HomeViewModel,
|
||||
sharedVM: SharedVM,
|
||||
notificationVM: NotificationVM,
|
||||
naviAnalyticsEventTracker: NaviAnalytics.Home,
|
||||
@@ -53,20 +53,6 @@ fun InitHomeScreenComponents(
|
||||
|
||||
LaunchedEffect(Unit) { naviAnalyticsEventTracker.onHomePageLand() }
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
homeVM.shouldFetchHomeApi.collect { shouldFetchHomeApi ->
|
||||
if (shouldFetchHomeApi) {
|
||||
naviAnalyticsEventTracker.onHomePageApiCalledFromUiTronAction()
|
||||
homeVM.fetchCards(
|
||||
showLoader = true,
|
||||
naviAnalyticsEventTracker = naviAnalyticsEventTracker,
|
||||
activity = activity,
|
||||
isPaymentLoaderShowing = paymentVM.isPaymentLoaderShowing(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
dashboardSharedVM.ctaData.collect { actionData ->
|
||||
if (actionData.url == NaviDeepLinkNavigator.WEB_APP_UPDATE) {
|
||||
@@ -79,17 +65,12 @@ fun InitHomeScreenComponents(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
homeVM.cachedResponse.collect {
|
||||
TemporaryStorageHelper.updateApiTs(TemporaryStorageHelper.HOME)
|
||||
activity.hideLoader()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
sharedVM.uiTronActionHandler.collect { uiTronActionHandler ->
|
||||
uiTronActionHandler?.let {
|
||||
homeVM.updateHandleActionsFromJson(it)
|
||||
homeVM().sendEffect(CoroutineScope(Dispatchers.Default)) {
|
||||
HpEffects.OnActionsFromJson(it)
|
||||
}
|
||||
sharedVM.updateUiTronAction(uiTronActionHandler = null)
|
||||
}
|
||||
}
|
||||
@@ -104,13 +85,9 @@ fun InitHomeScreenComponents(
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
notificationVM.unReadNotificationCount.collect { count ->
|
||||
if (count > 0) naviAnalyticsEventTracker.onInAppNotificationsCountUpdate(count)
|
||||
homeVM.updateNotificationCount(count)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
homeVM.ctaActionEvent.collect { event ->
|
||||
handleCtaAction(event, homeVM, sharedVM, activity, callBackToActivityScreen)
|
||||
homeVM().sendEffect(CoroutineScope(Dispatchers.Default)) {
|
||||
HpEffects.OnNotificationUpdatedCount(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.compose.model
|
||||
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.home.model.WidgetUiState
|
||||
|
||||
data class FrontLayerWidget(
|
||||
val id: String,
|
||||
val state: WidgetUiState = WidgetUiState.VISIBLE,
|
||||
val data: UiTronResponse? = null
|
||||
)
|
||||
@@ -35,7 +35,7 @@ import com.navi.uitron.utils.setWidth
|
||||
import com.navi.uitron.utils.setWidthRange
|
||||
import com.navi.uitron.viewmodel.UiTronViewModel
|
||||
import com.naviapp.home.compose.uiTron.model.viewProperties.RotatingViewProperty
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.utils.Constants.HomeCustomUitronWidgetConstants.ROTATING_VIEW_ANIMATION
|
||||
|
||||
class RotatingViewRenderer(
|
||||
@@ -84,7 +84,7 @@ class RotatingViewRenderer(
|
||||
RotatingView(
|
||||
modifier = viewModifier,
|
||||
indexValue = {
|
||||
(uiTronViewModel as HomeVM)
|
||||
(uiTronViewModel as HomeViewModel)
|
||||
.rotatingViewHelper
|
||||
.get()
|
||||
.currentIndexToDisplay(
|
||||
|
||||
@@ -13,13 +13,13 @@ import com.navi.uitron.model.data.UiTronData
|
||||
import com.navi.uitron.model.ui.UiTronView
|
||||
import com.navi.uitron.render.UiTronRenderer
|
||||
import com.naviapp.home.compose.uiTron.renderer.HomeCustomUiTronRenderer
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
|
||||
@Composable
|
||||
fun HomeWidgetRenderer(
|
||||
widgetData: MutableMap<String, UiTronData?>?,
|
||||
composeView: List<UiTronView>?,
|
||||
viewModel: HomeVM,
|
||||
viewModel: HomeViewModel,
|
||||
homeScrollState: () -> ScrollState
|
||||
) {
|
||||
UiTronRenderer(
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.reducer
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.navi.common.alchemist.model.AlchemistDialogStructure
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.alchemist.model.AlchemistScreenStructure
|
||||
import com.navi.common.alchemist.model.AlchemistWidgetGroup
|
||||
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
|
||||
import com.navi.common.alchemist.model.WidgetRenderState
|
||||
import com.navi.common.basemvi.BaseReducer
|
||||
import com.navi.common.basemvi.UiEffect
|
||||
import com.navi.common.basemvi.UiEvent
|
||||
import com.navi.common.basemvi.UiState
|
||||
import com.navi.common.managers.PermissionsManager
|
||||
import com.navi.common.model.common.AppUpdateData
|
||||
import com.navi.common.network.models.ErrorMessage
|
||||
import com.navi.common.network.models.GenericErrorResponse
|
||||
import com.navi.common.utils.Constants.APP_UPDATE_DATA
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.navi.uitron.model.data.UiTronActionData
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.common.model.UiTronActionHandler
|
||||
import com.naviapp.common.viewmodel.BottomNavBarVM
|
||||
import com.naviapp.common.viewmodel.InAppUpdateVM
|
||||
import com.naviapp.home.compose.home.utils.updateScreenContent
|
||||
import com.naviapp.home.compose.model.BottomStickyNudgeState
|
||||
import com.naviapp.home.compose.model.CtaActionEvent
|
||||
import com.naviapp.home.compose.model.InitiatePaymentFromComposeData
|
||||
import com.naviapp.home.model.BottomBarTabType
|
||||
import com.naviapp.home.model.HpDialogStateHolder
|
||||
import com.naviapp.home.model.HpDialogStateHolder.HpDialogState
|
||||
import com.naviapp.models.response.HomeFeatureResponse
|
||||
|
||||
class HomeReducer : BaseReducer<HpStates, HpEvents, HpEffects> {
|
||||
|
||||
override fun reduce(previousState: HpStates, event: HpEvents): HpStates {
|
||||
return when (event) {
|
||||
is HpEvents.UpdateHpDialogStateHolder -> {
|
||||
previousState.copy(
|
||||
hpDialogStateHolder = HpDialogStateHolder(event.state, event.content)
|
||||
)
|
||||
}
|
||||
is HpEvents.UpdateLastSelectedTab -> {
|
||||
previousState.copy(lastSelectedTab = event.tab)
|
||||
}
|
||||
is HpEvents.UpdateProfileDrawerState -> {
|
||||
previousState.copy(profileDrawerState = event.state)
|
||||
}
|
||||
is HpEvents.OnProfileIconClicked -> {
|
||||
previousState.copy(profileDrawerState = true)
|
||||
}
|
||||
is HpEvents.UpdateSnackBarState -> {
|
||||
previousState.copy(homeScreenSnackBarState = event.show)
|
||||
}
|
||||
is HpEvents.UpdateCurrentLoadedFragmentName -> {
|
||||
previousState.copy(currentLoadedFragmentScreenName = event.screenName)
|
||||
}
|
||||
is HpEvents.TriggerLoadingState -> {
|
||||
previousState.copy(isLoading = true, isError = false, error = null)
|
||||
}
|
||||
is HpEvents.UpdateError -> {
|
||||
previousState.copy(error = event.error)
|
||||
}
|
||||
is HpEvents.FirstLoadCompleted -> {
|
||||
previousState.copy(renderingFirstTime = false)
|
||||
}
|
||||
is HpEvents.UpdateShadowOnFrontLayer -> {
|
||||
previousState.copy(showShadowOnFrontLayer = event.showShadowOnFrontLayer)
|
||||
}
|
||||
is HpEvents.UpdateHpFeatures -> {
|
||||
previousState.copy(homeFeatures = event.homeFeatures)
|
||||
}
|
||||
is HpEvents.TriggerErrorState -> {
|
||||
previousState.copy(isError = true)
|
||||
}
|
||||
is HpEvents.HandleAppUpdateNudgeVisibility -> {
|
||||
if (previousState.lastSelectedTab != event.selectedTab) {
|
||||
val isHomeTab = event.selectedTab == BottomBarTabType.HOME.name
|
||||
val appUpdateData =
|
||||
previousState.screenDefinition
|
||||
?.screenStructure
|
||||
?.footer
|
||||
?.widgets
|
||||
?.firstOrNull { it.widgetData?.data?.get(APP_UPDATE_DATA) != null }
|
||||
?.widgetData
|
||||
?.data
|
||||
?.get(APP_UPDATE_DATA) as? AppUpdateData
|
||||
|
||||
appUpdateData?.let {
|
||||
event.bottomNavBarVM.setBottomNudge(
|
||||
isHomeTab && event.inAppUpdateVM.showAppUpdateStrip.value != false,
|
||||
BottomStickyNudgeState.AppUpdateNudgeState(it)
|
||||
)
|
||||
}
|
||||
previousState.copy(lastSelectedTab = event.selectedTab)
|
||||
} else previousState
|
||||
}
|
||||
is HpEvents.AddScreenContent -> {
|
||||
val newList =
|
||||
(previousState.screenDefinition?.screenStructure?.content?.widgets
|
||||
?: emptyList()) + event.content
|
||||
previousState.copy(
|
||||
screenDefinition =
|
||||
AlchemistScreenDefinition(
|
||||
screenStructure =
|
||||
AlchemistScreenStructure(
|
||||
content = AlchemistWidgetGroup(widgets = newList)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is HpEvents.UpdateScreen -> {
|
||||
val newWidgets = event.data.screenStructure?.content?.widgets ?: emptyList()
|
||||
val oldWidgets =
|
||||
previousState.screenDefinition?.screenStructure?.content?.widgets ?: emptyList()
|
||||
val updatedList = updateScreenContent(previousState, newWidgets, oldWidgets)
|
||||
previousState.copy(
|
||||
screenDefinition =
|
||||
event.data.copy(
|
||||
screenStructure =
|
||||
event.data.screenStructure?.copy(
|
||||
content =
|
||||
event.data.screenStructure
|
||||
?.content
|
||||
?.copy(widgets = updatedList)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is HpEvents.UpdateScreenContentWidgetRenderState -> {
|
||||
val newContent = previousState.screenDefinition?.screenStructure?.content?.widgets
|
||||
newContent?.map {
|
||||
if (it.widgetId == event.id) {
|
||||
it.copy(widgetRenderState = event.state)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
previousState.copy(
|
||||
screenDefinition =
|
||||
previousState.screenDefinition?.copy(
|
||||
screenStructure =
|
||||
previousState.screenDefinition.screenStructure?.copy(
|
||||
content =
|
||||
previousState.screenDefinition.screenStructure
|
||||
?.content
|
||||
?.copy(widgets = newContent)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is HpEvents.ClearScreenContent -> {
|
||||
previousState.copy(screenDefinition = null)
|
||||
}
|
||||
is HpEvents.PrioritySectionProcessed -> {
|
||||
previousState.copy(processPrioritySection = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface HpEvents : UiEvent {
|
||||
|
||||
data class UpdateHpDialogStateHolder(
|
||||
val state: HpDialogState,
|
||||
val content: AlchemistDialogStructure? = null
|
||||
) : HpEvents
|
||||
|
||||
data class UpdateLastSelectedTab(val tab: String) : HpEvents
|
||||
|
||||
data class UpdateProfileDrawerState(val state: Boolean) : HpEvents
|
||||
|
||||
data object OnProfileIconClicked : HpEvents
|
||||
|
||||
data class UpdateSnackBarState(val show: Boolean) : HpEvents
|
||||
|
||||
data class UpdateCurrentLoadedFragmentName(val screenName: String) : HpEvents
|
||||
|
||||
data object TriggerLoadingState : HpEvents
|
||||
|
||||
data class UpdateError(val error: GenericErrorResponse? = null) : HpEvents
|
||||
|
||||
data object FirstLoadCompleted : HpEvents
|
||||
|
||||
data class UpdateShadowOnFrontLayer(val showShadowOnFrontLayer: Boolean) : HpEvents
|
||||
|
||||
data class UpdateHpFeatures(val homeFeatures: HomeFeatureResponse?) : HpEvents
|
||||
|
||||
data object TriggerErrorState : HpEvents
|
||||
|
||||
data class HandleAppUpdateNudgeVisibility(
|
||||
val selectedTab: String,
|
||||
val bottomNavBarVM: BottomNavBarVM,
|
||||
val inAppUpdateVM: InAppUpdateVM
|
||||
) : HpEvents
|
||||
|
||||
data class AddScreenContent(val content: List<AlchemistWidgetModelDefinition<UiTronResponse>>) :
|
||||
HpEvents
|
||||
|
||||
data class UpdateScreen(val data: AlchemistScreenDefinition) : HpEvents
|
||||
|
||||
data class UpdateScreenContentWidgetRenderState(val id: String, val state: WidgetRenderState) :
|
||||
HpEvents
|
||||
|
||||
data object ClearScreenContent : HpEvents
|
||||
|
||||
data object PrioritySectionProcessed : HpEvents
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class HpStates(
|
||||
val hpDialogStateHolder: HpDialogStateHolder = HpDialogStateHolder(HpDialogState.Hidden),
|
||||
val lastSelectedTab: String = BottomBarTabType.HOME.name,
|
||||
val profileDrawerState: Boolean = false,
|
||||
val homeScreenSnackBarState: Boolean = false,
|
||||
val currentLoadedFragmentScreenName: String = NaviAnalytics.NEW_HOME,
|
||||
val homeFeatures: HomeFeatureResponse? = null,
|
||||
val screenDefinition: AlchemistScreenDefinition? = null,
|
||||
val showShadowOnFrontLayer: Boolean = false,
|
||||
val isLoading: Boolean = true,
|
||||
val renderingFirstTime: Boolean = true,
|
||||
val processPrioritySection: Boolean = true,
|
||||
val isError: Boolean = false,
|
||||
val error: GenericErrorResponse? = null
|
||||
) : UiState
|
||||
|
||||
@Immutable
|
||||
sealed interface HpEffects : UiEffect {
|
||||
data class OnActionData(val actionData: UiTronActionData?) : HpEffects
|
||||
|
||||
data class OnUitronAction(val action: UiTronAction) : HpEffects
|
||||
|
||||
data class OnActionsFromJson(val handler: UiTronActionHandler?) : HpEffects
|
||||
|
||||
data object TrackBottomBarEvents : HpEffects
|
||||
|
||||
data class ShowNotificationPermissions(val manager: PermissionsManager) : HpEffects
|
||||
|
||||
data class OnNotificationUpdatedCount(val count: Int) : HpEffects
|
||||
|
||||
data object OnRenderActions : HpEffects
|
||||
|
||||
data class OnApiFailure(
|
||||
val errorMessage: ErrorMessage?,
|
||||
val errors: List<GenericErrorResponse>?
|
||||
) : HpEffects
|
||||
|
||||
data class LogAppLaunchTime(val destination: String) : HpEffects
|
||||
|
||||
data object OnHideBalanceAction : HpEffects
|
||||
|
||||
data object OnPrioritySectionRendered : HpEffects
|
||||
|
||||
data class InitiatePayment(val data: InitiatePaymentFromComposeData) : HpEffects
|
||||
|
||||
data object FetchHomeApi : HpEffects
|
||||
|
||||
data class HandleCtaActionEvents(val event: CtaActionEvent) : HpEffects
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.ui.state
|
||||
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.network.models.GenericErrorResponse
|
||||
|
||||
sealed interface HomeScreenState {
|
||||
data object Loading : HomeScreenState
|
||||
|
||||
data class Success(val data: AlchemistScreenDefinition) : HomeScreenState
|
||||
|
||||
data class Error(
|
||||
val error: GenericErrorResponse? = null,
|
||||
) : HomeScreenState
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.usecase
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.navi.base.cache.model.NaviCacheEntity
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.network.di.DataDeserializers
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
class DeserializationUseCase
|
||||
@Inject
|
||||
constructor(@DataDeserializers private val deserializer: Gson) {
|
||||
private val widgetTypeToken =
|
||||
object : TypeToken<AlchemistWidgetModelDefinition<UiTronResponse>>() {}.type
|
||||
|
||||
suspend fun deserializePriorityContent(
|
||||
cacheEntity: NaviCacheEntity,
|
||||
startIndex: Int = 0,
|
||||
endIndex: Int? = null,
|
||||
onWidgetsDeserialized:
|
||||
suspend (List<AlchemistWidgetModelDefinition<UiTronResponse>>) -> Unit
|
||||
) =
|
||||
withContext(Dispatchers.Default) {
|
||||
val jsonArray = extractFrontLayerJsonArray(cacheEntity)
|
||||
jsonArray?.let {
|
||||
val finalEndIndex = endIndex?.coerceAtMost(jsonArray.length()) ?: jsonArray.length()
|
||||
val widgets =
|
||||
(startIndex until finalEndIndex)
|
||||
.map { jsonArray.getString(it) }
|
||||
.map {
|
||||
deserializer.fromJson<AlchemistWidgetModelDefinition<UiTronResponse>>(
|
||||
it,
|
||||
widgetTypeToken
|
||||
)
|
||||
}
|
||||
onWidgetsDeserialized(widgets)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deserializeScreen(
|
||||
cacheEntity: NaviCacheEntity,
|
||||
onScreenDeserialized: (AlchemistScreenDefinition) -> Unit
|
||||
) =
|
||||
withContext(Dispatchers.Default) {
|
||||
val screenDefinition =
|
||||
deserializer.fromJson(cacheEntity.value, AlchemistScreenDefinition::class.java)
|
||||
onScreenDeserialized(screenDefinition)
|
||||
}
|
||||
|
||||
private fun extractFrontLayerJsonArray(cacheEntity: NaviCacheEntity?): JSONArray? {
|
||||
val screenJson = cacheEntity?.value?.let { JSONObject(it) }
|
||||
return screenJson
|
||||
?.getJSONObject("screenStructure")
|
||||
?.getJSONObject("content")
|
||||
?.getJSONArray("widgets")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.usecase
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.navi.base.AppServiceManager
|
||||
import com.navi.base.cache.model.NaviCacheAltSourceEntity
|
||||
import com.navi.base.utils.ConnectivityObserver
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.alchemist.model.AlchemistScreenRequest
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
|
||||
import com.navi.common.network.models.ErrorMessage
|
||||
import com.navi.common.network.models.GenericErrorResponse
|
||||
import com.navi.common.network.models.RepoResult
|
||||
import com.naviapp.BuildConfig
|
||||
import com.naviapp.app.NaviApplication
|
||||
import com.naviapp.home.respository.HomeRepository
|
||||
import com.naviapp.network.di.DataSerializers
|
||||
import com.naviapp.utils.Constants.HOME_SCREEN_IN_CAPS
|
||||
import javax.inject.Inject
|
||||
|
||||
class FetchHomeItemsUseCase
|
||||
@Inject
|
||||
constructor(
|
||||
@DataSerializers private val dataSerializers: Gson,
|
||||
private val homeRepository: HomeRepository
|
||||
) {
|
||||
suspend fun fetchHomeItemFromAPI(
|
||||
density: String? = null,
|
||||
connectivityType: String? = null,
|
||||
availableAppVersionCode: Int?,
|
||||
connectivityObserver: ConnectivityObserver,
|
||||
installedModules: String,
|
||||
screenHash: String? = null,
|
||||
noInternetCallback: () -> Unit,
|
||||
onFailure:
|
||||
suspend (apiErrorMessage: ErrorMessage?, errors: List<GenericErrorResponse>?) -> Unit,
|
||||
): NaviCacheAltSourceEntity {
|
||||
|
||||
// Query Map Adaptations
|
||||
val queryMap = HashMap<String, String>()
|
||||
val shouldSendScreenHashInApi =
|
||||
FirebaseRemoteConfigHelper.getBoolean(
|
||||
FirebaseRemoteConfigHelper.SEND_SCREEN_HASH_IN_HOME_SCREEN_API
|
||||
)
|
||||
val screenHashForApi = if (shouldSendScreenHashInApi) screenHash else null
|
||||
|
||||
val deviceInfoDetails =
|
||||
(AppServiceManager.application as NaviApplication)
|
||||
.naviPayManager
|
||||
.get()
|
||||
.deviceInfoDetails()
|
||||
|
||||
val response =
|
||||
homeRepository.fetchHomeItems(
|
||||
density = density,
|
||||
connectivityType = connectivityType,
|
||||
availableAppVersionCode = availableAppVersionCode,
|
||||
installedModules = installedModules,
|
||||
naviUpiDeviceFingerprint = deviceInfoDetails.deviceFingerPrint,
|
||||
ssid = deviceInfoDetails.provider.ssid,
|
||||
request =
|
||||
AlchemistScreenRequest(
|
||||
screenName = HOME_SCREEN_IN_CAPS,
|
||||
screenHash = screenHashForApi,
|
||||
inputMap = queryMap
|
||||
)
|
||||
)
|
||||
|
||||
return handleResponse(connectivityObserver, response, noInternetCallback, onFailure)
|
||||
}
|
||||
|
||||
private suspend fun handleResponse(
|
||||
connectivityObserver: ConnectivityObserver,
|
||||
response: RepoResult<AlchemistScreenDefinition>,
|
||||
noInternetCallback: () -> Unit,
|
||||
onFailure:
|
||||
suspend (apiErrorMessage: ErrorMessage?, errors: List<GenericErrorResponse>?) -> Unit
|
||||
): NaviCacheAltSourceEntity {
|
||||
return if (connectivityObserver.isInternetConnected()) {
|
||||
response.data?.screenStructure?.let {
|
||||
NaviCacheAltSourceEntity(
|
||||
value = dataSerializers.toJson(response.data),
|
||||
version = BuildConfig.VERSION_CODE,
|
||||
isSuccess = true
|
||||
)
|
||||
} ?: NaviCacheAltSourceEntity(isSuccess = false)
|
||||
} else {
|
||||
noInternetCallback()
|
||||
onFailure(response.error, response.errors)
|
||||
NaviCacheAltSourceEntity(isSuccess = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.usecase
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.navi.amc.common.activity.CheckerActivity
|
||||
import com.navi.amc.common.taskProcessor.AmcTaskManager
|
||||
import com.navi.amc.navigator.NaviAmcDeeplinkNavigator
|
||||
import com.navi.amc.utils.Constant
|
||||
import com.navi.amc.utils.TempStorageHelper
|
||||
import com.navi.analytics.utils.NaviTrackEvent
|
||||
import com.navi.base.model.ActionData
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.base.model.CtaType
|
||||
import com.navi.base.utils.EMPTY
|
||||
import com.navi.base.utils.orFalse
|
||||
import com.navi.common.uitron.model.action.CtaAction
|
||||
import com.navi.common.uitron.model.action.LambdaApiAction
|
||||
import com.navi.common.utils.toActionData
|
||||
import com.navi.uitron.model.action.AnalyticsAction
|
||||
import com.navi.uitron.model.action.TriggerApiAction
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.common.fragment.LoanRepaymentBottomSheet
|
||||
import com.naviapp.common.navigator.NaviDeepLinkNavigator
|
||||
import com.naviapp.home.activity.InAppNotificationActivity
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.listener.HomeScreenCallbackListener
|
||||
import com.naviapp.home.compose.model.CtaActionEvent
|
||||
import com.naviapp.home.compose.model.InitiatePaymentFromComposeData
|
||||
import com.naviapp.home.model.HomeCtaTypes
|
||||
import com.naviapp.part_prepayment.PartPrePaymentActivity
|
||||
import com.naviapp.payment.activities.NaviPaymentActivity
|
||||
import com.naviapp.payment.fragments.PaymentType
|
||||
import com.naviapp.payment.models.Amount
|
||||
import com.naviapp.utils.Constants
|
||||
import com.naviapp.utils.Constants.FETCH_HOME_ITEMS
|
||||
import com.naviapp.utils.LOAN_ACCOUNT_NUMBER
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class HandleCtaUseCase @Inject constructor() {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
fun onActionTriggered(
|
||||
uiTronAction: UiTronAction?,
|
||||
shouldFetchHomeApi: () -> Unit,
|
||||
onCtaActionEvent: (CtaActionEvent) -> Unit
|
||||
) {
|
||||
when (uiTronAction) {
|
||||
is CtaAction -> {
|
||||
ctaActionCallbacks(ctaAction = uiTronAction, onCallback = onCtaActionEvent)
|
||||
}
|
||||
is AnalyticsAction -> {
|
||||
NaviTrackEvent.trackEvent(
|
||||
uiTronAction.eventName ?: "",
|
||||
uiTronAction.eventProperties
|
||||
)
|
||||
}
|
||||
is TriggerApiAction -> {
|
||||
if (
|
||||
uiTronAction is LambdaApiAction && uiTronAction.lambdaType == FETCH_HOME_ITEMS
|
||||
) {
|
||||
shouldFetchHomeApi()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
FirebaseCrashlytics.getInstance()
|
||||
.log("${uiTronAction?.type} Action not handled now")
|
||||
Timber.d("Action not handled now")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ctaActionCallbacks(
|
||||
ctaAction: CtaAction,
|
||||
onCallback: (CtaActionEvent) -> Unit,
|
||||
) {
|
||||
when (HomeCtaTypes.find(ctaAction.ctaData?.type)) {
|
||||
HomeCtaTypes.REDIRECTION_CTA -> {
|
||||
scope.launch { onCallback(CtaActionEvent.RedirectToCta(ctaAction.ctaData)) }
|
||||
}
|
||||
HomeCtaTypes.COPY_TO_CLIPBOARD -> {
|
||||
scope.launch {
|
||||
onCallback(CtaActionEvent.CopyToClipboard(ctaAction.ctaData?.title ?: EMPTY))
|
||||
}
|
||||
}
|
||||
HomeCtaTypes.DIALOG -> {
|
||||
scope.launch { onCallback(CtaActionEvent.ShowDialog(ctaAction.ctaData?.url)) }
|
||||
}
|
||||
HomeCtaTypes.BOTTOM_SHEET -> {
|
||||
scope.launch { onCallback(CtaActionEvent.ShowBottomSheet(ctaAction.ctaData?.url)) }
|
||||
}
|
||||
HomeCtaTypes.UNKNOWN -> {
|
||||
FirebaseCrashlytics.getInstance().log("Unknown cta Type clicked")
|
||||
}
|
||||
else -> {
|
||||
FirebaseCrashlytics.getInstance().log("cta not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleCtaData(
|
||||
naviClickAction: CtaData,
|
||||
activity: HomePageActivity,
|
||||
callBackToActivityScreen: (callback: HomeScreenCallbackListener) -> Unit,
|
||||
onProfileIconClicked: () -> Unit
|
||||
) {
|
||||
if (toOpenBottomSheet(naviClickAction.url)) {
|
||||
toShowBottomSheet(activity = activity, action = naviClickAction.toActionData())
|
||||
return
|
||||
} else if (
|
||||
naviClickAction.url == CtaType.PAYMENT.name ||
|
||||
naviClickAction.url == CtaType.PAYMENT_PL.name
|
||||
) {
|
||||
callBackToActivityScreen(
|
||||
HomeScreenCallbackListener.InitiatePaymentOnHomePage(
|
||||
paymentData = handlePaymentCtaData(naviClickAction)
|
||||
)
|
||||
)
|
||||
} else if (naviClickAction.url == NaviDeepLinkNavigator.PROFILE) {
|
||||
onProfileIconClicked()
|
||||
} else if (naviClickAction.url == Constants.Notification.IN_APP_NOTIFICATION) {
|
||||
val intent = Intent(activity, InAppNotificationActivity::class.java)
|
||||
activity.startActivity(intent)
|
||||
} else {
|
||||
naviClickAction.analyticsEventProperties?.let { eventProperties ->
|
||||
eventProperties.name?.let { eventName ->
|
||||
NaviTrackEvent.trackEventOnClickStream(eventName, eventProperties.properties)
|
||||
}
|
||||
}
|
||||
if (
|
||||
naviClickAction.url
|
||||
?.contains(
|
||||
NaviAmcDeeplinkNavigator.AMC.plus(Constants.DIVIDER)
|
||||
.plus(NaviAmcDeeplinkNavigator.KYC),
|
||||
true
|
||||
)
|
||||
.orFalse() ||
|
||||
naviClickAction.url
|
||||
?.contains(CheckerActivity.HPC_PAN_REDIRECTION_PAGE)
|
||||
.orFalse() ||
|
||||
naviClickAction.url
|
||||
?.contains(CheckerActivity.HPC_NAME_REDIRECTION_PAGE)
|
||||
.orFalse()
|
||||
) {
|
||||
TempStorageHelper.kycSourceInfo =
|
||||
mapOf(Constant.KYC_SOURCE_SCREEN to NaviAnalytics.HOME)
|
||||
AmcTaskManager.requestParams[Constant.PREFETCH_SOURCE_SCREEN] = NaviAnalytics.HOME
|
||||
}
|
||||
NaviDeepLinkNavigator.navigate(
|
||||
activity,
|
||||
naviClickAction,
|
||||
needsResult = naviClickAction.needsResult,
|
||||
requestCode = naviClickAction.requestCode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toOpenBottomSheet(url: String?): Boolean {
|
||||
return url?.let { url.contains(Constants.SHOW_BOTTOMSHEET, true) } ?: run { false }
|
||||
}
|
||||
|
||||
private fun toShowBottomSheet(activity: HomePageActivity, action: ActionData?) {
|
||||
val splitDeepLink = action?.url?.split(Constants.SLASH)
|
||||
when (splitDeepLink?.getOrNull(1)) {
|
||||
NaviAnalytics.LOAN_REPAYMENT_BOTTOMSHEET -> {
|
||||
val bundle =
|
||||
Bundle().apply { putParcelable(LoanRepaymentBottomSheet.ACTION_DATA, action) }
|
||||
val tag = LoanRepaymentBottomSheet.TAG
|
||||
val fragment = LoanRepaymentBottomSheet.getInstance(bundle)
|
||||
activity.safelyShowBottomSheet(fragment, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePaymentCtaData(naviClickAction: CtaData): InitiatePaymentFromComposeData {
|
||||
val loanAccountNumber =
|
||||
naviClickAction.parameters?.firstOrNull { it.key == LOAN_ACCOUNT_NUMBER }?.value
|
||||
val amountData =
|
||||
naviClickAction.parameters
|
||||
?.firstOrNull { it.key == NaviPaymentActivity.AMOUNT_DATA }
|
||||
?.value
|
||||
val currency =
|
||||
naviClickAction.parameters
|
||||
?.firstOrNull { it.key == PartPrePaymentActivity.CURRENCY }
|
||||
?.value
|
||||
val symbol =
|
||||
naviClickAction.parameters
|
||||
?.firstOrNull { it.key == PartPrePaymentActivity.SYMBOL }
|
||||
?.value
|
||||
val repaymentType =
|
||||
naviClickAction.parameters
|
||||
?.firstOrNull { it.key == PartPrePaymentActivity.REPAYMENT_TYPE }
|
||||
?.value
|
||||
val paymentType: PaymentType =
|
||||
PaymentType.get(
|
||||
naviClickAction.parameters?.firstOrNull { it.key == Constants.PAYMENT_TYPE }?.value
|
||||
)
|
||||
val isPreClosure: Boolean =
|
||||
(repaymentType == PaymentType.SCHEDULED_PRE_CLOSURE.name ||
|
||||
repaymentType == PaymentType.PRE_CLOSURE.name)
|
||||
val loanType =
|
||||
naviClickAction.parameters?.firstOrNull { it.key == Constants.LOAN_TYPE }?.value
|
||||
val updatePaymentType =
|
||||
if (isPreClosure) {
|
||||
PaymentType.PRE_LOAN_CLOSURE
|
||||
} else {
|
||||
paymentType
|
||||
}
|
||||
return InitiatePaymentFromComposeData(
|
||||
amount = Amount(amountData?.toDoubleOrNull(), currency = currency, symbol = symbol),
|
||||
isPreClosure = isPreClosure,
|
||||
paymentType = updatePaymentType.name,
|
||||
repaymentType = repaymentType,
|
||||
loanType = loanType,
|
||||
loanAccountNumber = loanAccountNumber
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.usecase
|
||||
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.common.uitron.model.action.CtaAction
|
||||
import com.navi.pay.common.setup.NaviPayManager
|
||||
import com.navi.uitron.model.action.UpdateDataAction
|
||||
import com.navi.uitron.model.action.UpdateViewStateAction
|
||||
import com.navi.uitron.model.data.RowData
|
||||
import com.navi.uitron.model.data.TextData
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.navi.uitron.model.data.UiTronActionData
|
||||
import com.naviapp.home.model.HomeCtaTypes
|
||||
import com.naviapp.utils.Constants
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class HandleUpiUseCase
|
||||
@Inject
|
||||
constructor(
|
||||
val naviPayManager: NaviPayManager,
|
||||
) {
|
||||
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
fun updateUPI(
|
||||
onAction: (UiTronAction) -> Unit,
|
||||
onActionData: (UiTronActionData) -> Unit,
|
||||
) {
|
||||
updateUPILiteBalanceV2(onAction)
|
||||
updateUPIVpa(onAction, onActionData)
|
||||
}
|
||||
|
||||
private fun updateUPILiteBalanceV2(
|
||||
onAction: (UiTronAction) -> Unit,
|
||||
) {
|
||||
val liteBalance = naviPayManager.getUpiLiteBalance()
|
||||
if (liteBalance.isNotEmpty()) {
|
||||
onAction(
|
||||
UpdateDataAction(
|
||||
viewDataList =
|
||||
listOf(
|
||||
UpdateDataAction.ViewData(
|
||||
layoutId = Constants.UPI_LITE_BALANCE_TEXT_V2,
|
||||
data = TextData(text = liteBalance)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
onAction(
|
||||
UpdateViewStateAction(
|
||||
viewStates =
|
||||
mapOf(
|
||||
Constants.UPI_LITE_BALANCE_TEXT_V2 to Constants.SHOW,
|
||||
Constants.UPI_LITE_SUBTITLE_TEXT to Constants.HIDE
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
onAction(
|
||||
UpdateViewStateAction(
|
||||
viewStates =
|
||||
mapOf(
|
||||
Constants.UPI_LITE_BALANCE_TEXT_V2 to Constants.HIDE,
|
||||
Constants.UPI_LITE_SUBTITLE_TEXT to Constants.SHOW
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUPIVpa(
|
||||
onAction: (UiTronAction) -> Unit,
|
||||
onActionData: (UiTronActionData) -> Unit
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val upiId = naviPayManager.getVpaOfPrimaryAccount().firstOrNull()
|
||||
handleVpa(upiId, onAction, onActionData)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleVpa(
|
||||
upiId: String?,
|
||||
onAction: (UiTronAction) -> Unit,
|
||||
onActionData: (UiTronActionData) -> Unit
|
||||
) {
|
||||
if (upiId.isNullOrBlank()) {
|
||||
handleVpaNotAvailable(onAction)
|
||||
} else {
|
||||
handleVpaAvailable(upiId, onActionData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleVpaAvailable(upiId: String, onActionData: (UiTronActionData) -> Unit) {
|
||||
val actionData =
|
||||
UiTronActionData(
|
||||
actions =
|
||||
listOf(
|
||||
UpdateViewStateAction(
|
||||
viewStates =
|
||||
mapOf(
|
||||
Constants.GET_UPI_ID_CONTAINER to Constants.INVISIBLE,
|
||||
Constants.UPI_ID_CONTAINER to Constants.VISIBLE
|
||||
)
|
||||
),
|
||||
UpdateDataAction(
|
||||
listOf(
|
||||
UpdateDataAction.ViewData(
|
||||
layoutId = Constants.UPI_ID_TEXT,
|
||||
data = TextData(text = "UPI ID: $upiId")
|
||||
),
|
||||
UpdateDataAction.ViewData(
|
||||
layoutId = Constants.UPI_ID_CONTAINER,
|
||||
data =
|
||||
RowData().apply {
|
||||
onClick = getUpiIdRowClickActionData(upiId)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
onActionData(actionData)
|
||||
}
|
||||
|
||||
private fun getUpiIdRowClickActionData(upiId: String): UiTronActionData {
|
||||
return UiTronActionData(
|
||||
actions =
|
||||
listOf(
|
||||
CtaAction(
|
||||
ctaData = CtaData(type = HomeCtaTypes.COPY_TO_CLIPBOARD.name, title = upiId)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleVpaNotAvailable(onAction: (UiTronAction) -> Unit) {
|
||||
onAction(
|
||||
UpdateViewStateAction(
|
||||
viewStates =
|
||||
mapOf(
|
||||
Constants.GET_UPI_ID_CONTAINER to Constants.VISIBLE,
|
||||
Constants.UPI_ID_CONTAINER to Constants.INVISIBLE
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import com.navi.common.utils.Constants.SCREEN_ID
|
||||
import com.navi.common.utils.Constants.UPI_NUX_SCREEN
|
||||
import com.naviapp.common.navigator.NaviDeepLinkNavigator
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.registration.RegistrationActivity
|
||||
import com.naviapp.utils.Constants.SOURCE
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
@@ -40,11 +40,11 @@ constructor(@ActivityContext private val context: Context) {
|
||||
return extras?.getString(REDIRECTION_URL) == UPI_NUX_SCREEN
|
||||
}
|
||||
|
||||
fun redirectToDestination(activity: HomePageActivity, homeVM: HomeVM) {
|
||||
fun redirectToDestination(homeVM: HomeViewModel) {
|
||||
if (BaseUtils.isUserLoggedIn().not()) {
|
||||
navigateToLogin(activity)
|
||||
navigateToLogin(context as HomePageActivity)
|
||||
}
|
||||
val extras = activity.intent.extras
|
||||
val extras = (context as HomePageActivity).intent.extras
|
||||
when (extras?.getString(REDIRECTION_URL)) {
|
||||
UPI_NUX_SCREEN -> {
|
||||
if (homeVM.nuxHandler.isUserEligibleForNux(UPI_NUX_SCREEN)) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.home.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.navi.base.cache.model.NaviCacheAltSourceEntity
|
||||
import com.navi.base.cache.model.NaviCacheEntity
|
||||
import com.navi.base.cache.model.NaviCacheEntityInfo
|
||||
import com.navi.base.cache.repository.NaviCacheRepositoryImpl
|
||||
import com.navi.base.cache.util.NaviSharedDbKeys
|
||||
import com.navi.base.deeplink.util.DeeplinkConstants
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.base.sharedpref.PreferenceManager
|
||||
import com.navi.base.utils.BaseUtils
|
||||
import com.navi.base.utils.ConnectivityObserver
|
||||
import com.navi.common.basemvi.BaseMviViewModel
|
||||
import com.navi.common.uitron.helper.VideoViewHelper
|
||||
import com.navi.common.utils.Constants.SCREEN_HASH
|
||||
import com.navi.common.utils.Constants.UPI_NUX_SCREEN
|
||||
import com.navi.common.utils.TemporaryStorageHelper
|
||||
import com.navi.common.utils.getDensityName
|
||||
import com.navi.common.utils.getInstalledDynamicModulesCommaSeparated
|
||||
import com.navi.common.utils.getNetworkType
|
||||
import com.navi.naviwidgets.utils.CURRENT_VERSION_IN_STORE
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.navi.uitron.model.data.UiTronActionData
|
||||
import com.naviapp.BuildConfig
|
||||
import com.naviapp.analytics.utils.NaviAnalytics
|
||||
import com.naviapp.common.navigator.NaviDeepLinkNavigator
|
||||
import com.naviapp.home.common.handler.SelectiveRefreshHandler
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.compose.listener.HomeScreenCallbackListener
|
||||
import com.naviapp.home.compose.uiTron.helper.RotatingViewHelper
|
||||
import com.naviapp.home.reducer.HomeReducer
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.reducer.HpEvents
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.respository.HomeRepository
|
||||
import com.naviapp.home.usecase.DeserializationUseCase
|
||||
import com.naviapp.home.usecase.FetchHomeItemsUseCase
|
||||
import com.naviapp.home.usecase.HandleCtaUseCase
|
||||
import com.naviapp.home.usecase.HandleUpiUseCase
|
||||
import com.naviapp.nux.handler.NewUserExperienceHandler
|
||||
import com.naviapp.utils.Constants.HomePageConstants.FETCH_HOME_ITEMS_TIMEOUT
|
||||
import dagger.Lazy
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
|
||||
@HiltViewModel
|
||||
class HomeViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val homeRepository: HomeRepository,
|
||||
private val naviCacheRepository: NaviCacheRepositoryImpl,
|
||||
private val deserializationUseCase: DeserializationUseCase,
|
||||
private val fetchHomeItemsUseCase: FetchHomeItemsUseCase,
|
||||
private val selectiveRefreshHandler: SelectiveRefreshHandler,
|
||||
private val ctaHandler: HandleCtaUseCase,
|
||||
private val connectivityObserver: ConnectivityObserver,
|
||||
val nuxHandler: NewUserExperienceHandler,
|
||||
private val upiUseCase: HandleUpiUseCase,
|
||||
val rotatingViewHelper: Lazy<RotatingViewHelper>,
|
||||
val videoViewHelper: Lazy<VideoViewHelper>,
|
||||
) :
|
||||
BaseMviViewModel<HpStates, HpEvents, HpEffects>(
|
||||
initialState = HpStates(),
|
||||
reducer = HomeReducer()
|
||||
) {
|
||||
|
||||
var shouldRefreshHomeApi: Boolean = false
|
||||
var homeTabLastUpdateTimestamp: Long = System.currentTimeMillis()
|
||||
private var homePageRefreshJob: Job? = null
|
||||
val naviAnalyticsEventTracker by lazy { NaviAnalytics.naviAnalytics.Home() }
|
||||
private var analyticsStartTs = System.currentTimeMillis()
|
||||
|
||||
// Backdrop Scaffold Progress used for updating the alpha of the top nav
|
||||
private val _backdropScaffoldProgress = MutableStateFlow(0f)
|
||||
val backdropScaffoldProgress: StateFlow<Float> = _backdropScaffoldProgress
|
||||
|
||||
// Internet Connectivity
|
||||
private val _internetConnectivity = MutableSharedFlow<ConnectivityObserver.Status>()
|
||||
val internetConnectivity: SharedFlow<ConnectivityObserver.Status> = _internetConnectivity
|
||||
|
||||
private fun vmScope(context: CoroutineContext = Dispatchers.IO) =
|
||||
CoroutineScope(context + SupervisorJob())
|
||||
|
||||
init {
|
||||
vmScope().safeLaunch { observeActionCallback() }
|
||||
vmScope().safeLaunch { observeInternetConnectivity() }
|
||||
}
|
||||
|
||||
private suspend fun observeActionCallback() {
|
||||
getActionCallback().collect { action ->
|
||||
ctaHandler.onActionTriggered(
|
||||
uiTronAction = action,
|
||||
shouldFetchHomeApi = { sendEffect(vmScope()) { HpEffects.FetchHomeApi } },
|
||||
onCtaActionEvent = { sendEffect(vmScope()) { HpEffects.HandleCtaActionEvents(it) } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun observeInternetConnectivity() {
|
||||
connectivityObserver.observe().collectLatest { _internetConnectivity.emit(it) }
|
||||
}
|
||||
|
||||
fun updateBackdropScaffoldProgress(offset: Float) {
|
||||
viewModelScope.launch { _backdropScaffoldProgress.update { offset } }
|
||||
}
|
||||
|
||||
fun setHomeApiRefreshFlag(shouldRefresh: Boolean) {
|
||||
this.shouldRefreshHomeApi = shouldRefresh
|
||||
}
|
||||
|
||||
suspend fun handleRemainingItemsForFirstLoad() {
|
||||
val cacheEntity = naviCacheRepository.get(NaviSharedDbKeys.HOME_TAB.name)
|
||||
cacheEntity?.let {
|
||||
updateHomeScreen(it)
|
||||
sendEvent(HpEvents.FirstLoadCompleted)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchHomeDataWithTimeout(context: Context) =
|
||||
viewModelScope.safeLaunch(Dispatchers.IO) {
|
||||
try {
|
||||
withTimeout(FETCH_HOME_ITEMS_TIMEOUT) {
|
||||
val homeData = fetchHomeDataFromApi(context)
|
||||
saveHomeDataToCache(homeData)
|
||||
}
|
||||
} catch (e: TimeoutCancellationException) {
|
||||
naviAnalyticsEventTracker.onLoginHomePageApiCallTimeout()
|
||||
sendEvent(HpEvents.TriggerErrorState)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchHomeDataFromApi(context: Context): NaviCacheAltSourceEntity {
|
||||
return fetchHomeItemsUseCase.fetchHomeItemFromAPI(
|
||||
connectivityObserver = connectivityObserver,
|
||||
density = getDensityName(context),
|
||||
connectivityType = getNetworkType(context),
|
||||
availableAppVersionCode =
|
||||
PreferenceManager.getIntPreferenceApp(CURRENT_VERSION_IN_STORE),
|
||||
installedModules = getInstalledDynamicModulesCommaSeparated(),
|
||||
screenHash = null,
|
||||
onFailure = { errorMessage, errors ->
|
||||
sendEffect(vmScope()) { HpEffects.OnApiFailure(errorMessage, errors) }
|
||||
},
|
||||
noInternetCallback = {
|
||||
viewModelScope.launch {
|
||||
_internetConnectivity.emit(ConnectivityObserver.Status.Unavailable)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun saveHomeDataToCache(homeData: NaviCacheAltSourceEntity) {
|
||||
naviCacheRepository.save(
|
||||
NaviCacheEntity(
|
||||
key = NaviSharedDbKeys.HOME_TAB.name,
|
||||
value = homeData.value.orEmpty(),
|
||||
version = BuildConfig.VERSION_CODE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun loadHomeElements(activity: HomePageActivity, isPaymentLoaderShowing: Boolean = false) {
|
||||
viewModelScope.safeLaunch(Dispatchers.IO) {
|
||||
if (BaseUtils.isUserLoggedIn()) {
|
||||
handleLoggedInUser(activity, isPaymentLoaderShowing)
|
||||
} else {
|
||||
handleLoggedOutUser(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoggedInUser(activity: HomePageActivity, isPaymentLoaderShowing: Boolean) {
|
||||
if (isPaymentLoaderShowing || state.value.profileDrawerState) {
|
||||
val duration = System.currentTimeMillis() - analyticsStartTs
|
||||
naviAnalyticsEventTracker.onHomePageInit(duration)
|
||||
}
|
||||
naviAnalyticsEventTracker.onHomePageApiCall()
|
||||
analyticsStartTs = System.currentTimeMillis()
|
||||
fetchHomeDataFromCache(
|
||||
density = getDensityName(activity),
|
||||
connectivityType = getNetworkType(activity),
|
||||
availableAppVersionCode =
|
||||
PreferenceManager.getIntPreferenceApp(CURRENT_VERSION_IN_STORE),
|
||||
installedModules = getInstalledDynamicModulesCommaSeparated()
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleLoggedOutUser(activity: HomePageActivity) {
|
||||
naviAnalyticsEventTracker.onFetchHomeCardsForLogoutUser(NaviAnalytics.NEW_HOME)
|
||||
NaviDeepLinkNavigator.navigate(
|
||||
activity,
|
||||
CtaData(url = DeeplinkConstants.REGISTRATION),
|
||||
clearTask = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun fetchHomeDataFromCache(
|
||||
density: String? = null,
|
||||
connectivityType: String? = null,
|
||||
availableAppVersionCode: Int?,
|
||||
installedModules: String
|
||||
) {
|
||||
if (homePageRefreshJob?.isActive == true) return
|
||||
homePageRefreshJob =
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
handleDataModification()
|
||||
val screenHash = state.value.screenDefinition?.screenMetaData?.get(SCREEN_HASH)
|
||||
fetchAndHandleCacheData(
|
||||
density,
|
||||
connectivityType,
|
||||
availableAppVersionCode,
|
||||
installedModules,
|
||||
screenHash
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDataModification() {
|
||||
if (TemporaryStorageHelper.isDataModified(TemporaryStorageHelper.HOME)) {
|
||||
selectiveRefreshHandler.handleLoadingState(this@HomeViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchAndHandleCacheData(
|
||||
density: String?,
|
||||
connectivityType: String?,
|
||||
availableAppVersionCode: Int?,
|
||||
installedModules: String,
|
||||
screenHash: String?
|
||||
) {
|
||||
naviCacheRepository
|
||||
.getDataAndFetchFromAltSourceWithSimilarityCheck(
|
||||
key = NaviSharedDbKeys.HOME_TAB.name,
|
||||
version = BuildConfig.VERSION_CODE.toLong(),
|
||||
getDataFromAltSource = {
|
||||
fetchHomeItemsUseCase.fetchHomeItemFromAPI(
|
||||
connectivityObserver = connectivityObserver,
|
||||
density = density,
|
||||
connectivityType = connectivityType,
|
||||
availableAppVersionCode = availableAppVersionCode,
|
||||
installedModules = installedModules,
|
||||
screenHash = screenHash,
|
||||
onFailure = { errorMessage, errors ->
|
||||
sendEffect(vmScope()) { HpEffects.OnApiFailure(errorMessage, errors) }
|
||||
},
|
||||
noInternetCallback = {
|
||||
viewModelScope.launch {
|
||||
_internetConnectivity.emit(ConnectivityObserver.Status.Unavailable)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
.collect { handleHomeCacheResponse(it) }
|
||||
}
|
||||
|
||||
private suspend fun handleHomeCacheResponse(response: NaviCacheEntityInfo) {
|
||||
when {
|
||||
response.isError -> sendEvent(HpEvents.TriggerErrorState)
|
||||
response.data != null -> processHomeData(response.data!!)
|
||||
else -> sendEvent(HpEvents.TriggerErrorState)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processHomeData(data: NaviCacheEntity) {
|
||||
when {
|
||||
state.value.renderingFirstTime && state.value.processPrioritySection ->
|
||||
processPriorityContent(data)
|
||||
else -> updateHomeScreen(data)
|
||||
}
|
||||
|
||||
finishProcessing(data)
|
||||
}
|
||||
|
||||
private suspend fun processPriorityContent(data: NaviCacheEntity) {
|
||||
deserializationUseCase.deserializePriorityContent(cacheEntity = data, endIndex = 4) {
|
||||
sendEvent(HpEvents.AddScreenContent(it))
|
||||
}
|
||||
sendEvent(HpEvents.PrioritySectionProcessed)
|
||||
}
|
||||
|
||||
private suspend fun updateHomeScreen(data: NaviCacheEntity) {
|
||||
deserializationUseCase.deserializeScreen(data) { sendEvent(HpEvents.UpdateScreen(it)) }
|
||||
}
|
||||
|
||||
private fun finishProcessing(data: NaviCacheEntity) {
|
||||
sendEffect(vmScope(Dispatchers.Default)) { HpEffects.OnRenderActions }
|
||||
updateHomeApiTimestamp()
|
||||
data.updatedAt.let { homeTabLastUpdateTimestamp = it }
|
||||
selectiveRefreshHandler.handleSuccessState(this@HomeViewModel)
|
||||
}
|
||||
|
||||
private fun updateHomeApiTimestamp() {
|
||||
TemporaryStorageHelper.updateApiTs(TemporaryStorageHelper.HOME)
|
||||
naviAnalyticsEventTracker.onHomePageApiResponse(
|
||||
System.currentTimeMillis() - analyticsStartTs
|
||||
)
|
||||
}
|
||||
|
||||
fun handleCtaData(
|
||||
naviClickAction: CtaData,
|
||||
activity: HomePageActivity,
|
||||
callBackToActivityScreen: (callback: HomeScreenCallbackListener) -> Unit
|
||||
) {
|
||||
ctaHandler.handleCtaData(naviClickAction, activity, callBackToActivityScreen) {
|
||||
sendEvent(HpEvents.OnProfileIconClicked)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchNuxScreenDataForEligibleUsers(navigateToNuxScreen: () -> Unit) {
|
||||
vmScope().launch {
|
||||
nuxHandler.fetchNuxScreenDataForEligibleUsers(UPI_NUX_SCREEN, navigateToNuxScreen)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchHomeFeature(type: String) {
|
||||
viewModelScope.launch {
|
||||
val response = homeRepository.fetchHomeFeature(type)
|
||||
if (response.error == null && response.errors.isNullOrEmpty()) {
|
||||
sendEvent(HpEvents.UpdateHpFeatures(response.data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleUpiAdaptations() {
|
||||
upiUseCase.updateUPI(
|
||||
onAction = {
|
||||
sendEffect(vmScope(Dispatchers.Default)) { HpEffects.OnUitronAction(it) }
|
||||
},
|
||||
onActionData = {
|
||||
sendEffect(vmScope(Dispatchers.Default)) { HpEffects.OnActionData(it) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun observeUPIVpa(onAction: (UiTronAction) -> Unit, onActionData: (UiTronActionData) -> Unit) {
|
||||
vmScope().launch {
|
||||
upiUseCase.naviPayManager.getVpaOfPrimaryAccount().collect { upiId ->
|
||||
upiUseCase.handleVpa(upiId, onAction, onActionData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
videoViewHelper.get().clear()
|
||||
}
|
||||
}
|
||||
@@ -7,26 +7,24 @@
|
||||
|
||||
package com.naviapp.nux.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.navi.common.alchemist.model.AlchemistScreenDefinition
|
||||
import com.navi.common.basemvi.UiEffect
|
||||
import com.navi.common.basemvi.UiEvent
|
||||
import com.navi.common.basemvi.UiState
|
||||
|
||||
sealed class NuxGenericScreenUiEvent : UiEvent {
|
||||
data class RenderUI(val response: AlchemistScreenDefinition) : NuxGenericScreenUiEvent()
|
||||
|
||||
data object ApiFailed : NuxGenericScreenUiEvent()
|
||||
|
||||
data object BackPressed : NuxGenericScreenUiEvent()
|
||||
}
|
||||
|
||||
@Stable
|
||||
@Immutable
|
||||
data class MainScreenUiState(
|
||||
val isLoading: Boolean,
|
||||
val isLoading: Boolean = true,
|
||||
val screenDefinition: AlchemistScreenDefinition? = null,
|
||||
) : UiState
|
||||
|
||||
@Immutable
|
||||
sealed class NuxGenericScreenUiEvent : UiEvent {
|
||||
data class RenderUI(val response: AlchemistScreenDefinition) : NuxGenericScreenUiEvent()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class MainScreenUiEffect : UiEffect {
|
||||
sealed class Navigation : MainScreenUiEffect() {
|
||||
data object Back : Navigation()
|
||||
|
||||
@@ -8,25 +8,20 @@
|
||||
package com.naviapp.nux.reducer
|
||||
|
||||
import com.navi.common.basemvi.BaseReducer
|
||||
import com.navi.common.basemvi.UiEvent
|
||||
import com.naviapp.nux.model.MainScreenUiEffect
|
||||
import com.naviapp.nux.model.MainScreenUiState
|
||||
import com.naviapp.nux.model.NuxGenericScreenUiEvent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class NuxGenericScreenReducer(val coroutineScope: CoroutineScope, initialState: MainScreenUiState) :
|
||||
BaseReducer<MainScreenUiState, MainScreenUiEffect>(initialState) {
|
||||
class NuxGenericScreenReducer :
|
||||
BaseReducer<MainScreenUiState, NuxGenericScreenUiEvent, MainScreenUiEffect> {
|
||||
|
||||
override fun reduce(oldState: MainScreenUiState, event: UiEvent) {
|
||||
when (event) {
|
||||
override fun reduce(
|
||||
previousState: MainScreenUiState,
|
||||
event: NuxGenericScreenUiEvent
|
||||
): MainScreenUiState {
|
||||
return when (event) {
|
||||
is NuxGenericScreenUiEvent.RenderUI -> {
|
||||
setState { oldState.copy(isLoading = false, screenDefinition = event.response) }
|
||||
}
|
||||
is NuxGenericScreenUiEvent.BackPressed -> {
|
||||
setEffect(coroutineScope) { MainScreenUiEffect.Navigation.Back }
|
||||
}
|
||||
is NuxGenericScreenUiEvent.ApiFailed -> {
|
||||
setEffect(coroutineScope) { MainScreenUiEffect.Navigation.Back }
|
||||
previousState.copy(isLoading = false, screenDefinition = event.response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import com.navi.ap.common.ui.composables.GenericShimmerLoader
|
||||
import com.navi.common.alchemist.model.AlchemistScreenStructure
|
||||
import com.naviapp.nux.model.MainScreenUiEffect
|
||||
import com.naviapp.nux.model.MainScreenUiState
|
||||
import com.naviapp.nux.model.NuxGenericScreenUiEvent
|
||||
import com.naviapp.nux.viewmodel.NuxViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
@@ -28,7 +27,6 @@ fun NuxGenericScreen(
|
||||
state: MainScreenUiState,
|
||||
effectFlow: Flow<MainScreenUiEffect>?,
|
||||
getViewModel: () -> NuxViewModel,
|
||||
onEventSent: (event: NuxGenericScreenUiEvent) -> Unit,
|
||||
onNavigationRequested: (MainScreenUiEffect.Navigation) -> Unit
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -44,7 +42,11 @@ fun NuxGenericScreen(
|
||||
}
|
||||
LaunchedEffect(Unit) { getViewModel.invoke().getScreenDefinition() }
|
||||
|
||||
BackHandler { onEventSent(NuxGenericScreenUiEvent.BackPressed) }
|
||||
BackHandler {
|
||||
getViewModel().sendEffect(getViewModel().coroutineScope) {
|
||||
MainScreenUiEffect.Navigation.Back
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.navigationBarsPadding()) {
|
||||
if (state.isLoading) {
|
||||
|
||||
@@ -27,7 +27,6 @@ fun NuxGenericScreenDestination(bundle: Bundle?, viewModel: NuxViewModel = hiltV
|
||||
state = state,
|
||||
effectFlow = viewModel.effect,
|
||||
getViewModel = { viewModel },
|
||||
onEventSent = { event -> viewModel.sendEvent(event) },
|
||||
onNavigationRequested = { navigationEffect ->
|
||||
if (navigationEffect is MainScreenUiEffect.Navigation.Back) {
|
||||
viewModel.state.value.screenDefinition?.screenStructure?.systemBackCta?.let {
|
||||
|
||||
@@ -19,31 +19,22 @@ import com.naviapp.nux.reducer.NuxGenericScreenReducer
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@HiltViewModel
|
||||
class NuxViewModel @Inject constructor(val nuxHandler: NewUserExperienceHandler) :
|
||||
BaseMviViewModel<NuxGenericScreenUiEvent, MainScreenUiState, MainScreenUiEffect>() {
|
||||
BaseMviViewModel<MainScreenUiState, NuxGenericScreenUiEvent, MainScreenUiEffect>(
|
||||
initialState = MainScreenUiState(),
|
||||
reducer = NuxGenericScreenReducer()
|
||||
) {
|
||||
|
||||
private var queryMap: MutableMap<String, String?> = mutableMapOf()
|
||||
|
||||
override val reducer = NuxGenericScreenReducer(viewModelScope, setInitialState())
|
||||
|
||||
override val state: StateFlow<MainScreenUiState>
|
||||
get() = reducer.state
|
||||
|
||||
override val effect: Flow<MainScreenUiEffect>
|
||||
get() = reducer.effect
|
||||
|
||||
override fun setInitialState() = MainScreenUiState(isLoading = true, screenDefinition = null)
|
||||
|
||||
fun getScreenDefinition() {
|
||||
viewModelScope.safeLaunch(Dispatchers.IO) {
|
||||
nuxHandler.getNuxScreenDefinition(
|
||||
queryMap = queryMap,
|
||||
onSuccess = { reducer.sendEvent(NuxGenericScreenUiEvent.RenderUI(it)) },
|
||||
onFailure = { reducer.sendEvent(NuxGenericScreenUiEvent.ApiFailed) }
|
||||
onSuccess = { sendEvent(NuxGenericScreenUiEvent.RenderUI(it)) },
|
||||
onFailure = { sendEffect(coroutineScope) { MainScreenUiEffect.Navigation.Back } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,8 @@ import com.naviapp.appupdate.activities.UpdateAppActivity
|
||||
import com.naviapp.dashboard.listeners.FragmentInteractionListener
|
||||
import com.naviapp.databinding.RegistrationActivityBinding
|
||||
import com.naviapp.deeplinkmanagement.vm.DeeplinkManagementViewModel
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.reducer.HpEffects
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.models.TruecallerAuthData
|
||||
import com.naviapp.models.request.AuthDetails
|
||||
import com.naviapp.models.request.LoginType
|
||||
@@ -96,7 +97,7 @@ class RegistrationActivity :
|
||||
LoginListener {
|
||||
private lateinit var binding: RegistrationActivityBinding
|
||||
private val registrationVM by lazy { ViewModelProvider(this)[RegistrationVM::class.java] }
|
||||
private val homeVM by lazy { ViewModelProvider(this)[HomeVM::class.java] }
|
||||
private val homeVM: HomeViewModel by viewModels()
|
||||
private val otpReceiver by lazy { SmsAutoReadReceiver() }
|
||||
private val registrationSharedVM by lazy {
|
||||
ViewModelProvider(this)[RegistrationSharedVM::class.java]
|
||||
@@ -195,7 +196,9 @@ class RegistrationActivity :
|
||||
content.viewTreeObserver.addOnPreDrawListener(
|
||||
object : ViewTreeObserver.OnPreDrawListener {
|
||||
override fun onPreDraw(): Boolean {
|
||||
homeVM.logAppLaunchTimeEvent(LOGIN_SCREEN)
|
||||
homeVM.sendEffect(homeVM.coroutineScope) {
|
||||
HpEffects.LogAppLaunchTime(LOGIN_SCREEN)
|
||||
}
|
||||
content.viewTreeObserver.removeOnPreDrawListener(this)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import com.naviapp.common.navigator.NaviDeepLinkNavigator.REGISTRATION
|
||||
import com.naviapp.deeplinkmanagement.analytics.NaviDeeplinkAnalytics
|
||||
import com.naviapp.deeplinkmanagement.usecase.PlayStoreReferralResolverUseCase
|
||||
import com.naviapp.deeplinkmanagement.vm.DeeplinkManagementViewModel
|
||||
import com.naviapp.home.viewmodel.HomeVM
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.models.DeeplinkData
|
||||
import com.naviapp.models.request.DeeplinkRequestData
|
||||
import com.naviapp.models.request.OnboardingActionRequest
|
||||
@@ -50,7 +50,7 @@ class LoginDeeplinkAndRedirectionHelper
|
||||
constructor(
|
||||
private val deferredActionUseCase: Lazy<DeferredActionUseCase>,
|
||||
private val playStoreReferralResolverUseCase: Lazy<PlayStoreReferralResolverUseCase>,
|
||||
private val deeplinkAnalytics: Lazy<NaviDeeplinkAnalytics>,
|
||||
private val deeplinkAnalytics: Lazy<NaviDeeplinkAnalytics>
|
||||
) {
|
||||
|
||||
private var deepLinkBundle: Bundle? = null
|
||||
@@ -145,10 +145,10 @@ constructor(
|
||||
fun redirectToNextScreen(
|
||||
loginType: String?,
|
||||
activity: RegistrationActivity,
|
||||
homeVM: HomeVM,
|
||||
homeVM: HomeViewModel,
|
||||
registrationVM: RegistrationVM,
|
||||
) {
|
||||
val homePageResponseFetchJob = homeVM.fetchAndSaveHomeTabWithTimeOut(context = activity)
|
||||
val homePageResponseFetchJob = homeVM.fetchHomeDataWithTimeout(context = activity)
|
||||
if (deferredActionUseCase.get().isJobStarted()) {
|
||||
deferredActionUseCase.get().invokeOnJobCompletion {
|
||||
analyticsTracker.loginSuccess(loginType, activity.source)
|
||||
@@ -200,7 +200,7 @@ constructor(
|
||||
private fun asyncHomePageResponseFetchAndRedirect(
|
||||
homePageResponseFetchJob: Job,
|
||||
activity: RegistrationActivity,
|
||||
homeVM: HomeVM,
|
||||
homeVM: HomeViewModel,
|
||||
nextCta: CtaData? = null,
|
||||
) {
|
||||
activity.lifecycleScope.launch {
|
||||
@@ -213,7 +213,11 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun goToNextScreen(nextCta: CtaData, activity: RegistrationActivity, homeVM: HomeVM) {
|
||||
private fun goToNextScreen(
|
||||
nextCta: CtaData,
|
||||
activity: RegistrationActivity,
|
||||
homeVM: HomeViewModel
|
||||
) {
|
||||
if (isHomePageUrl(nextCta) && homeVM.nuxHandler.canRedirectUserToNux()) {
|
||||
navigate(homeVM.nuxHandler.addUpiNuxCtaParams(nextCta, REGISTRATION), activity, homeVM)
|
||||
return
|
||||
@@ -221,7 +225,7 @@ constructor(
|
||||
navigate(nextCta, activity = activity, homeVM)
|
||||
}
|
||||
|
||||
private fun navigate(cta: CtaData, activity: RegistrationActivity, homeVM: HomeVM) {
|
||||
private fun navigate(cta: CtaData, activity: RegistrationActivity, homeVM: HomeViewModel) {
|
||||
val ctaSource = cta.parameters?.firstOrNull { it.key == SOURCE }?.value
|
||||
val updatedCta =
|
||||
if (homeVM.nuxHandler.canRedirectUserToNux() || ctaSource.isNotNullAndNotEmpty()) {
|
||||
|
||||
@@ -526,6 +526,11 @@ object Constants {
|
||||
}
|
||||
|
||||
object HomePageConstants {
|
||||
const val FETCH_HOME_ITEMS_TIMEOUT = 5000L
|
||||
const val NAVI_APP_NAV_HOME_PAGE_VIEWED = "NaviApp_Nav_HomePage_Viewed"
|
||||
const val NAVI_APP_NAV_INVESTMENT_PAGE_VIEWED = "NaviApp_Nav_Investment_Viewed"
|
||||
const val NAVI_APP_NAV_LOAN_PAGE_VIEWED = "NaviApp_Nav_Loan_Viewed"
|
||||
const val NAVI_APP_NAV_INSURANCE_PAGE_VIEWED = "NaviApp_Nav_Insurance_Viewed"
|
||||
val HOME_BACK_LAYER_BANNER_HEIGHT = 264.dp
|
||||
val HOME_APP_BAR_HEIGHT = 60.dp
|
||||
val HOME_SHAPE_CURVATURE = 8.dp
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
package com.navi.base.cache.model
|
||||
|
||||
data class NaviCacheEntityDetails(
|
||||
data class NaviCacheEntityInfo(
|
||||
val isError: Boolean = false,
|
||||
val data: NaviCacheEntity? = null,
|
||||
val isComingFromAltSource: Boolean? = null,
|
||||
val isCurrentAndAltDataSame: Boolean? = null
|
||||
val isComingFromAltSource: Boolean? = null
|
||||
)
|
||||
@@ -10,7 +10,7 @@ package com.navi.base.cache.repository
|
||||
import com.navi.base.cache.dao.NaviCacheDao
|
||||
import com.navi.base.cache.model.NaviCacheAltSourceEntity
|
||||
import com.navi.base.cache.model.NaviCacheEntity
|
||||
import com.navi.base.cache.model.NaviCacheEntityDetails
|
||||
import com.navi.base.cache.model.NaviCacheEntityInfo
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -53,7 +53,7 @@ interface NaviCacheRepository {
|
||||
emitMultipleValues: Boolean = false
|
||||
): Flow<NaviCacheEntity?>
|
||||
|
||||
fun getDataAndFetchFromAltSourceWithDetails(
|
||||
fun getDataAndFetchFromAltSourceWithSimilarityCheck(
|
||||
key: String,
|
||||
version: Long? = null,
|
||||
getDataFromAltSource: suspend () -> NaviCacheAltSourceEntity,
|
||||
@@ -63,11 +63,9 @@ interface NaviCacheRepository {
|
||||
val currentDataValue = currentData?.value ?: return false
|
||||
val altDataValue = altData.value ?: return false
|
||||
|
||||
return currentData.version == altData.version &&
|
||||
currentDataValue.hashCode() == altDataValue.hashCode()
|
||||
},
|
||||
emitMultipleValues: Boolean = false
|
||||
): Flow<NaviCacheEntityDetails?>
|
||||
return currentData.version == altData.version && currentDataValue == altDataValue
|
||||
}
|
||||
): Flow<NaviCacheEntityInfo?>
|
||||
}
|
||||
|
||||
class NaviCacheRepositoryImpl @Inject constructor(private val naviCacheDao: NaviCacheDao) :
|
||||
@@ -208,59 +206,58 @@ class NaviCacheRepositoryImpl @Inject constructor(private val naviCacheDao: Navi
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDataAndFetchFromAltSourceWithDetails(
|
||||
override fun getDataAndFetchFromAltSourceWithSimilarityCheck(
|
||||
key: String,
|
||||
version: Long?,
|
||||
getDataFromAltSource: suspend () -> NaviCacheAltSourceEntity,
|
||||
isCurrentAndAltDataSame:
|
||||
(currentData: NaviCacheEntity?, altData: NaviCacheAltSourceEntity) -> Boolean,
|
||||
emitMultipleValues: Boolean
|
||||
(currentData: NaviCacheEntity?, altData: NaviCacheAltSourceEntity) -> Boolean
|
||||
) = flow {
|
||||
var isValueEmitted = false
|
||||
|
||||
val currentValueInDB = get(key = key)
|
||||
|
||||
if (currentValueInDB != null) { // Its a valid value
|
||||
emit(NaviCacheEntityDetails(data = currentValueInDB, isComingFromAltSource = false))
|
||||
isValueEmitted = true
|
||||
emit(
|
||||
NaviCacheEntityInfo(
|
||||
data = currentValueInDB,
|
||||
isComingFromAltSource = false,
|
||||
isError = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val naviCacheValueEntityFromAltSource = getDataFromAltSource.invoke()
|
||||
|
||||
if (
|
||||
!naviCacheValueEntityFromAltSource.isSuccess ||
|
||||
naviCacheValueEntityFromAltSource.value == null
|
||||
) { // alternate source data invalid
|
||||
return@flow
|
||||
}
|
||||
|
||||
val naviCacheEntity =
|
||||
NaviCacheEntity(
|
||||
key = key,
|
||||
value = naviCacheValueEntityFromAltSource.value,
|
||||
version = naviCacheValueEntityFromAltSource.version ?: 1,
|
||||
ttl = naviCacheValueEntityFromAltSource.ttl,
|
||||
clearOnLogout = naviCacheValueEntityFromAltSource.clearOnLogout,
|
||||
metaData = naviCacheValueEntityFromAltSource.metaData
|
||||
)
|
||||
|
||||
val isDataSame =
|
||||
isCurrentAndAltDataSame(currentValueInDB, naviCacheValueEntityFromAltSource)
|
||||
|
||||
if (isDataSame.not()) {
|
||||
// Data from Alt source is different
|
||||
save(naviCacheEntity = naviCacheEntity)
|
||||
}
|
||||
|
||||
// If multiple values are required or no value is emitted yet then emit latest value
|
||||
if (emitMultipleValues || !isValueEmitted) {
|
||||
emit(
|
||||
NaviCacheEntityDetails(
|
||||
data = naviCacheEntity,
|
||||
isComingFromAltSource = true,
|
||||
isCurrentAndAltDataSame = isDataSame
|
||||
naviCacheValueEntityFromAltSource.value.isNullOrEmpty()
|
||||
) {
|
||||
if (currentValueInDB?.value.isNullOrEmpty()) {
|
||||
emit(NaviCacheEntityInfo(isError = true, data = null))
|
||||
}
|
||||
} else {
|
||||
val naviCacheEntity =
|
||||
NaviCacheEntity(
|
||||
key = key,
|
||||
value = naviCacheValueEntityFromAltSource.value,
|
||||
version = naviCacheValueEntityFromAltSource.version ?: 1,
|
||||
ttl = naviCacheValueEntityFromAltSource.ttl,
|
||||
clearOnLogout = naviCacheValueEntityFromAltSource.clearOnLogout,
|
||||
metaData = naviCacheValueEntityFromAltSource.metaData
|
||||
)
|
||||
)
|
||||
|
||||
val isDataSame =
|
||||
isCurrentAndAltDataSame(currentValueInDB, naviCacheValueEntityFromAltSource)
|
||||
|
||||
if (isDataSame.not()) {
|
||||
save(naviCacheEntity = naviCacheEntity)
|
||||
emit(
|
||||
NaviCacheEntityInfo(
|
||||
data = naviCacheEntity,
|
||||
isError = false,
|
||||
isComingFromAltSource = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ data class AlchemistWidgetModelDefinition<T : Any?>(
|
||||
val widgetId: String? = null,
|
||||
val widgetName: String? = null,
|
||||
val widgetData: T? = null,
|
||||
val widgetRenderState: WidgetRenderState = WidgetRenderState.VISIBLE,
|
||||
val widgetStates: List<AlchemistWidgetState>? = null,
|
||||
val widgetOutput: List<AlchemistWidgetOutput>? = null,
|
||||
val widgetRenderActions: AlchemistRenderActions? = null,
|
||||
@@ -29,6 +30,12 @@ data class AlchemistWidgetState(
|
||||
val eventName: String? = null
|
||||
)
|
||||
|
||||
enum class WidgetRenderState {
|
||||
VISIBLE,
|
||||
NOT_VISIBLE,
|
||||
NEWLY_ADDED
|
||||
}
|
||||
|
||||
data class AlchemistWidgetOutput(
|
||||
val fieldName: String? = null,
|
||||
val layoutId: String? = null,
|
||||
|
||||
@@ -8,20 +8,40 @@
|
||||
package com.navi.common.basemvi
|
||||
|
||||
import com.navi.common.viewmodel.BaseVM
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
abstract class BaseMviViewModel<Event : UiEvent, ViewState : UiState, Effect : UiEffect> :
|
||||
BaseVM() {
|
||||
abstract class BaseMviViewModel<State : UiState, Event : UiEvent, Effect : UiEffect>(
|
||||
initialState: State,
|
||||
private val reducer: BaseReducer<State, Event, Effect>
|
||||
) : BaseVM() {
|
||||
|
||||
abstract fun setInitialState(): ViewState
|
||||
private val _state: MutableStateFlow<State> = MutableStateFlow(initialState)
|
||||
val state: StateFlow<State>
|
||||
get() = _state.asStateFlow()
|
||||
|
||||
abstract val state: Flow<ViewState>
|
||||
private val _event: MutableSharedFlow<Event> = MutableSharedFlow()
|
||||
val event: SharedFlow<Event>
|
||||
get() = _event.asSharedFlow()
|
||||
|
||||
abstract val effect: Flow<Effect>
|
||||
private val _effects = Channel<Effect>(capacity = Channel.CONFLATED)
|
||||
val effect = _effects.receiveAsFlow()
|
||||
|
||||
abstract val reducer: BaseReducer<ViewState, Effect>
|
||||
fun sendEffect(scope: CoroutineScope, effect: () -> Effect) {
|
||||
scope.launch { _effects.send(effect()) }
|
||||
}
|
||||
|
||||
fun sendEvent(event: Event) {
|
||||
reducer.sendEvent(event)
|
||||
val newState = reducer.reduce(_state.value, event)
|
||||
_state.update { newState }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,38 +7,9 @@
|
||||
|
||||
package com.navi.common.basemvi
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
interface BaseReducer<State : UiState, Event : UiEvent, Effect : UiEffect> {
|
||||
|
||||
abstract class BaseReducer<State : UiState, Effect : UiEffect>(initialState: State) {
|
||||
|
||||
private val _state: MutableStateFlow<State> = MutableStateFlow(initialState)
|
||||
val state: StateFlow<State>
|
||||
get() = _state
|
||||
|
||||
private val _effect: Channel<Effect> = Channel()
|
||||
val effect = _effect.receiveAsFlow()
|
||||
|
||||
fun sendEvent(event: UiEvent) {
|
||||
reduce(_state.value, event)
|
||||
}
|
||||
|
||||
protected fun setState(reducer: State.() -> State) {
|
||||
val newState = _state.value.reducer()
|
||||
_state.update { newState }
|
||||
}
|
||||
|
||||
protected fun setEffect(scope: CoroutineScope, builder: () -> Effect) {
|
||||
val effectValue = builder()
|
||||
scope.launch { _effect.send(effectValue) }
|
||||
}
|
||||
|
||||
abstract fun reduce(oldState: State, event: UiEvent)
|
||||
fun reduce(previousState: State, event: Event): State
|
||||
}
|
||||
|
||||
interface UiEvent
|
||||
|
||||
Reference in New Issue
Block a user