NTP-71293 | Naman Khurmi | Improved chunked rendering with delay (#16553)

Co-authored-by: Venkat Praneeth Reddy <venkat.praneeth@navi.com>
This commit is contained in:
Naman Khurmi
2025-06-19 23:48:24 +05:30
committed by GitHub
parent db5b8b25b9
commit d251c89fc0
15 changed files with 547 additions and 400 deletions

View File

@@ -180,9 +180,8 @@ import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@AndroidEntryPoint
class HomePageActivity :
@@ -306,7 +305,6 @@ class HomePageActivity :
}
}
initObservers()
homeVM.fetchDelayedRenderingConfig(this@HomePageActivity)
handleInitialTouchEventState()
initErrors()
fetchHomeData()
@@ -323,15 +321,10 @@ class HomePageActivity :
}
private fun handleInitialTouchEventState() {
lifecycleScope.launch {
combine(homeVM.delayedRenderingConfig, homeVM.isClickEnabled) { config, isEnabled ->
val isInitialTouchDisabled = config?.isInitialTouchDisabled.orFalse()
val hasLowerRamThanThreshold = config?.hasLowerRamThanThreshold.orFalse()
Pair(isInitialTouchDisabled && hasLowerRamThanThreshold, isEnabled)
}
.flowOn(Dispatchers.IO)
.collect { (isTouchDisabled, isEnabled) ->
if (isTouchDisabled) {
lifecycleScope.launch(Dispatchers.IO) {
homeVM.isClickEnabled.collect { isEnabled ->
if (homeVM.isInitialTouchDisabled()) {
withContext(Dispatchers.Main) {
when {
!isEnabled -> {
window.setFlags(
@@ -348,6 +341,7 @@ class HomePageActivity :
}
}
}
}
}
}

View File

@@ -7,43 +7,24 @@
package com.naviapp.home.compose.home.ui.content
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.LocalOverscrollFactory
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
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.layout.onGloballyPositioned
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -53,23 +34,17 @@ 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.base.utils.orFalse
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
import com.navi.common.alchemist.model.WidgetRenderState
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.common.model.common.MastheadWidgetData
import com.navi.naviwidgets.R
import com.navi.pay.utils.conditional
import com.navi.uitron.model.UiTronResponse
import com.naviapp.home.compose.home.ui.renderer.ChunkedWidgetRenderer
import com.naviapp.home.compose.home.ui.renderer.HomeWidgetRenderer
import com.naviapp.home.compose.home.ui.renderer.MastheadWidgetRenderer
import com.naviapp.home.reducer.HpEffects
import com.naviapp.home.reducer.HpEvents
import com.naviapp.home.utils.getHomeWidgetAnimationSpec
import com.naviapp.home.utils.shouldRenderWidget
import com.naviapp.home.viewmodel.HomeViewModel
import com.naviapp.utils.Constants.HOME_SCREEN_IN_CAPS
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
@Composable
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@@ -77,7 +52,7 @@ fun FrontLayerContent(
modifier: Modifier,
frontLayerShape: Shape,
widgets: List<AlchemistWidgetModelDefinition<UiTronResponse>>?,
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
uitronRenderer: @Composable (UiTronResponse?) -> Unit,
isRenderingFirstTime: Boolean,
homeScrollState: () -> ScrollState,
homeVM: () -> HomeViewModel,
@@ -88,7 +63,7 @@ fun FrontLayerContent(
isMastheadEnabled: Boolean,
appBarHeight: Dp,
) {
CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
CompositionLocalProvider(LocalOverscrollFactory provides null) {
Column(
modifier
.background(Color.White, frontLayerShape)
@@ -98,35 +73,38 @@ fun FrontLayerContent(
) {
if (!widgets.isNullOrEmpty()) {
Column {
if (isMastheadEnabled) {
MastheadWidget(homeWidgetRenderer, mastheadWidget, appBarHeight)
} else {
TopNotchUI()
}
MastheadWidgetRenderer(
isMastheadEnabled = isMastheadEnabled,
uitronRenderer = uitronRenderer,
mastheadWidget = mastheadWidget,
appBarHeight = appBarHeight,
)
staticNudgeContainer()
if (isDelayedRenderingEnabled(homeVM)) {
if (isRenderingFirstTime && homeVM().isDelayedRenderingEnabled()) {
ChunkedWidgetRenderer(
elementList = widgets,
homeWidgetRenderer = homeWidgetRenderer,
homeWidgetRenderer = uitronRenderer,
homeVM = homeVM,
onEvent = onEvent,
)
} else {
RenderUiTronContent(
elementList = widgets,
homeWidgetRenderer = homeWidgetRenderer,
homeVM = homeVM,
onEvent = onEvent,
)
widgets.forEach { element ->
HomeWidgetRenderer(
element = element,
uitronRenderer = uitronRenderer,
homeVM = homeVM,
onEvent = onEvent,
)
}
}
ContentLoaderLottie(isRenderingFirstTime)
SideEffect {
if (isRenderingFirstTime.not()) {
onEffect(HpEffects.HomePageUiRenderedEvent)
onEvent(HpEvents.UpdateIsHomePageRendered(true))
} else {
onEffect(HpEffects.LogAppLaunchTime(HOME_SCREEN_IN_CAPS))
}
}
SideEffect {
if (isRenderingFirstTime.not()) {
onEffect(HpEffects.HomePageUiRenderedEvent)
onEvent(HpEvents.UpdateIsHomePageRendered(true))
} else {
onEffect(HpEffects.LogAppLaunchTime(HOME_SCREEN_IN_CAPS))
}
}
} else HomePageContentShimmer()
@@ -134,205 +112,16 @@ fun FrontLayerContent(
}
}
@Composable
fun isDelayedRenderingEnabled(homeVM: () -> HomeViewModel): Boolean {
val renderingConfig by homeVM().delayedRenderingConfig.collectAsState()
val featureEnabled = renderingConfig?.isDelayedRenderingEnabled.orFalse()
val isRamBelowThreshold = renderingConfig?.hasLowerRamThanThreshold.orFalse()
return featureEnabled && isRamBelowThreshold
}
@Composable
private fun MastheadWidget(
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
mastheadWidget: MastheadWidgetData?,
appBarHeight: Dp,
) {
val shouldRender =
mastheadWidget?.let { shouldRenderWidget(it.renderActions?.preRenderAction) } ?: false
if (!shouldRender) {
Box(modifier = Modifier.padding(top = appBarHeight, bottom = 16.dp).fillMaxWidth())
return
}
mastheadWidget?.let {
Box(
modifier =
Modifier.height(IntrinsicSize.Max)
.padding(bottom = it.widgetBottomPadding.dp)
.fillMaxWidth()
) {
Box(modifier = Modifier.matchParentSize()) {
homeWidgetRenderer(it.backgroundIllustration?.uiTronResponse)
}
Box(modifier = Modifier.padding(top = appBarHeight).fillMaxWidth()) {
homeWidgetRenderer(it.widgetData?.uiTronResponse)
}
}
}
}
@Composable
fun TopNotchUI() {
Column(
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier =
Modifier.padding(vertical = 8.dp)
.width(40.dp)
.height(4.dp)
.background(color = Color(0xFFE3E5E5), shape = RoundedCornerShape(16.dp))
)
}
}
@Composable
private fun ContentLoaderLottie(isPrioritySectionRendered: Boolean) {
if (isPrioritySectionRendered) {
val composition by
rememberLottieComposition(spec = LottieCompositionSpec.RawRes(R.raw.cta_loader_purple))
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)
}
}
}
@Composable
private fun AnimatedContainerForWidgets(
isVisible: MutableState<Boolean>,
widget: AlchemistWidgetModelDefinition<UiTronResponse>,
homeVM: HomeViewModel,
homeWidget: @Composable () -> Unit,
) {
val setImpressionTracker =
widget.widgetRenderActions?.onImpressionAction?.actions.isNullOrEmpty().not()
AnimatedVisibility(
visible = isVisible.value,
modifier =
Modifier.conditional(setImpressionTracker) {
onGloballyPositioned { layoutCoordinates ->
homeVM.sectionVisibilityTracker.updateSectionImpressionState(
widget,
layoutCoordinates,
) { onImpressionAction ->
homeVM.handleActions(onImpressionAction)
}
}
},
enter =
expandVertically(
expandFrom = Alignment.Top,
clip = true,
animationSpec = getHomeWidgetAnimationSpec(),
) {
0
} + fadeIn(animationSpec = getHomeWidgetAnimationSpec()),
exit =
shrinkVertically(
shrinkTowards = Alignment.Bottom,
clip = true,
animationSpec = getHomeWidgetAnimationSpec(),
) {
0
} + fadeOut(animationSpec = getHomeWidgetAnimationSpec()),
) {
homeWidget()
}
}
@Composable
private fun ChunkedWidgetRenderer(
elementList: List<AlchemistWidgetModelDefinition<UiTronResponse>>,
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
homeVM: () -> HomeViewModel,
onEvent: (event: HpEvents) -> Unit,
firstChunkSize: Int = 2,
) {
if (elementList.isEmpty()) return
val displayedCount = homeVM().widgetsDisplayed.coerceAtMost(elementList.size)
val delayBetweenChunks = remember {
FirebaseRemoteConfigHelper.getLong(
FirebaseRemoteConfigHelper.HOMEPAGE_WIDGET_RENDER_DELAY_MS,
140L,
)
}
LaunchedEffect(elementList) {
if (displayedCount < elementList.size) {
withContext(Dispatchers.Default) {
var nextChunk = if (displayedCount == 0) firstChunkSize else 1
while (displayedCount < elementList.size) {
homeVM()
.updateDisplayedWidgetCount(
(displayedCount + nextChunk).coerceAtMost(elementList.size)
)
nextChunk += 1
delay(delayBetweenChunks)
}
}
} else {
homeVM().updateDisplayedWidgetCount(elementList.size)
}
}
RenderUiTronContent(
elementList = elementList.take(displayedCount),
homeWidgetRenderer = homeWidgetRenderer,
homeVM = homeVM,
onEvent = onEvent,
)
}
@Composable
private fun RenderUiTronContent(
elementList: List<AlchemistWidgetModelDefinition<UiTronResponse>>,
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
homeVM: () -> HomeViewModel,
onEvent: (event: HpEvents) -> Unit,
) {
elementList.forEach { element ->
element.widgetId?.let { widgetId ->
key(widgetId) {
val visible = remember {
mutableStateOf(element.widgetRenderState == WidgetRenderState.VISIBLE)
}
LaunchedEffect(element.widgetRenderState) {
when (element.widgetRenderState) {
WidgetRenderState.NEWLY_ADDED -> {
visible.value = true
onEvent(
HpEvents.UpdateScreenContentWidgetRenderState(
widgetId,
WidgetRenderState.VISIBLE,
)
)
}
WidgetRenderState.NOT_VISIBLE -> {
visible.value = false
}
WidgetRenderState.VISIBLE -> {
visible.value = true
}
}
}
AnimatedContainerForWidgets(visible, element, homeVM()) {
homeWidgetRenderer(element.widgetData)
}
}
}
}
}

View File

@@ -44,7 +44,7 @@ fun HomeTopBar(
appBarHeight: Dp,
statusBarHeight: Dp,
topBarContent: UiTronResponse?,
homeWidgetRenderer: @Composable (UiTronResponse) -> Unit,
uitronRenderer: @Composable (UiTronResponse) -> Unit,
homeScrollState: () -> ScrollState,
) {
val isScrollingUp by remember { derivedStateOf { homeScrollState().value.dp < appBarHeight } }
@@ -60,7 +60,7 @@ fun HomeTopBar(
}
// Render the top bar
ToolbarRender(topBarModifier, statusBarHeight, topBarContent, homeWidgetRenderer)
ToolbarRender(topBarModifier, statusBarHeight, topBarContent, uitronRenderer)
}
@Composable
@@ -103,7 +103,7 @@ fun MastheadTopBar(
modifier = toolbarModifier,
statusBarHeight = statusBarHeight,
toolbarContent = toolbarConfig?.toolBarNav?.uiTronResponse,
homeWidgetRenderer = renderWidget,
uitronRenderer = renderWidget,
mastheadEnable = true,
)
}
@@ -174,12 +174,12 @@ private fun ToolbarRender(
modifier: Modifier,
statusBarHeight: Dp,
toolbarContent: UiTronResponse?,
homeWidgetRenderer: @Composable (UiTronResponse) -> Unit,
uitronRenderer: @Composable (UiTronResponse) -> Unit,
mastheadEnable: Boolean = false,
) {
Row(modifier = modifier, verticalAlignment = Alignment.Top) {
Row(Modifier.padding(top = statusBarHeight)) {
toolbarContent?.let { homeWidgetRenderer(it) } ?: DefaultTopBar(mastheadEnable)
toolbarContent?.let { uitronRenderer(it) } ?: DefaultTopBar(mastheadEnable)
}
}
}

View File

@@ -0,0 +1,75 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.naviapp.home.compose.home.ui.renderer
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.withFrameNanos
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.uitron.model.UiTronResponse
import com.naviapp.home.reducer.HpEvents
import com.naviapp.home.viewmodel.HomeViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
@Composable
fun ChunkedWidgetRenderer(
elementList: List<AlchemistWidgetModelDefinition<UiTronResponse>>?,
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
homeVM: () -> HomeViewModel,
onEvent: (event: HpEvents) -> Unit,
chunkSize: Int = 1,
) {
if (elementList.isNullOrEmpty()) return
val delayBetweenWidgets = remember {
FirebaseRemoteConfigHelper.getLong(
FirebaseRemoteConfigHelper.HOMEPAGE_WIDGET_RENDER_DELAY_MS,
140L,
)
}
val widgetsToShow = remember(elementList) { mutableIntStateOf(0) }
Column {
elementList.take(widgetsToShow.intValue).forEach { element ->
key(element.widgetId) {
HomeWidgetRenderer(
element = element,
uitronRenderer = homeWidgetRenderer,
homeVM = homeVM,
onEvent = onEvent,
)
}
}
}
LaunchedEffect(elementList) {
widgetsToShow.intValue = 0
elementList.indices
.chunked(chunkSize)
.asFlow()
.onEach { slice ->
val nextCount = slice.last() + 1
widgetsToShow.intValue = nextCount
withFrameNanos {}
if (slice.last() < elementList.lastIndex) {
delay(delayBetweenWidgets)
}
}
.collect()
onEvent(HpEvents.RenderedFirstTime)
}
}

View File

@@ -0,0 +1,142 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.naviapp.home.compose.home.ui.renderer
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
import com.navi.common.alchemist.model.WidgetRenderState
import com.navi.pay.utils.conditional
import com.navi.uitron.model.UiTronResponse
import com.naviapp.home.reducer.HpEvents
import com.naviapp.home.utils.getHomeWidgetAnimationSpec
import com.naviapp.home.viewmodel.HomeViewModel
@Composable
fun HomeWidgetRenderer(
element: AlchemistWidgetModelDefinition<UiTronResponse>,
uitronRenderer: @Composable (UiTronResponse?) -> Unit,
homeVM: () -> HomeViewModel,
onEvent: (event: HpEvents) -> Unit,
onRenderComplete: () -> Unit = {},
) {
val widgetId = element.widgetId
if (widgetId == null) {
onRenderComplete()
return
}
val visible = remember {
mutableStateOf(element.widgetRenderState == WidgetRenderState.VISIBLE)
}
key(widgetId) {
HandleWidgetVisibility(
widgetId = widgetId,
elementState = element.widgetRenderState,
onVisibilityChange = { visible.value = it },
onEvent = onEvent,
)
AnimatedContainerForWidgets(
isVisible = visible,
widget = element,
homeVM = homeVM,
homeWidget = { uitronRenderer(element.widgetData) },
)
SideEffect { onRenderComplete() }
}
}
@Composable
private fun HandleWidgetVisibility(
widgetId: String,
elementState: WidgetRenderState,
onVisibilityChange: (Boolean) -> Unit,
onEvent: (event: HpEvents) -> Unit,
) {
LaunchedEffect(elementState) {
when (elementState) {
WidgetRenderState.NEWLY_ADDED -> {
onVisibilityChange(true)
onEvent(
HpEvents.UpdateScreenContentWidgetRenderState(
widgetId,
WidgetRenderState.VISIBLE,
)
)
}
WidgetRenderState.NOT_VISIBLE -> onVisibilityChange(false)
WidgetRenderState.VISIBLE -> onVisibilityChange(true)
}
}
}
@Composable
private fun AnimatedContainerForWidgets(
isVisible: MutableState<Boolean>,
widget: AlchemistWidgetModelDefinition<UiTronResponse>,
homeVM: () -> HomeViewModel,
homeWidget: @Composable () -> Unit,
) {
val setImpressionTracker =
widget.widgetRenderActions?.onImpressionAction?.actions.isNullOrEmpty().not()
AnimatedVisibility(
visible = isVisible.value,
modifier =
Modifier.conditional(setImpressionTracker) {
onGloballyPositioned { layoutCoordinates ->
homeVM().sectionVisibilityTracker.updateSectionImpressionState(
widget,
layoutCoordinates,
) { onImpressionAction ->
homeVM().handleActions(onImpressionAction)
}
}
},
enter = enterTransitionForAnimatedContainer(),
exit = exitTransitionForAnimatedContainer(),
content = { homeWidget() },
)
}
@Composable
private fun exitTransitionForAnimatedContainer(): ExitTransition {
return shrinkVertically(
shrinkTowards = Alignment.Bottom,
clip = true,
animationSpec = getHomeWidgetAnimationSpec(),
) {
0
} + fadeOut(animationSpec = getHomeWidgetAnimationSpec())
}
@Composable
private fun enterTransitionForAnimatedContainer(): EnterTransition {
return expandVertically(
expandFrom = Alignment.Top,
clip = true,
animationSpec = getHomeWidgetAnimationSpec(),
) {
0
} + fadeIn(animationSpec = getHomeWidgetAnimationSpec())
}

View File

@@ -0,0 +1,90 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.naviapp.home.compose.home.ui.renderer
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.navi.common.model.common.MastheadWidgetData
import com.navi.uitron.model.UiTronResponse
import com.naviapp.home.utils.shouldRenderWidget
@Composable
fun MastheadWidgetRenderer(
isMastheadEnabled: Boolean,
uitronRenderer: @Composable (UiTronResponse?) -> Unit,
mastheadWidget: MastheadWidgetData?,
appBarHeight: Dp,
) {
if (isMastheadEnabled) {
MastheadWidget(uitronRenderer, mastheadWidget, appBarHeight)
} else {
TopNotchUI()
}
}
@Composable
private fun MastheadWidget(
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
mastheadWidget: MastheadWidgetData?,
appBarHeight: Dp,
) {
val shouldRender =
mastheadWidget?.let { shouldRenderWidget(it.renderActions?.preRenderAction) } == true
if (!shouldRender) {
Box(modifier = Modifier.padding(top = appBarHeight, bottom = 16.dp).fillMaxWidth())
return
}
mastheadWidget.let {
Box(
modifier =
Modifier.height(IntrinsicSize.Max)
.padding(bottom = it.widgetBottomPadding.dp)
.fillMaxWidth()
) {
Box(modifier = Modifier.matchParentSize()) {
homeWidgetRenderer(it.backgroundIllustration?.uiTronResponse)
}
Box(modifier = Modifier.padding(top = appBarHeight).fillMaxWidth()) {
homeWidgetRenderer(it.widgetData?.uiTronResponse)
}
}
}
}
@Composable
private fun TopNotchUI() {
Column(
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier =
Modifier.padding(vertical = 8.dp)
.width(40.dp)
.height(4.dp)
.background(color = Color(0xFFE3E5E5), shape = RoundedCornerShape(16.dp))
)
}
}

View File

@@ -26,7 +26,7 @@ import com.naviapp.home.compose.activity.HomePageActivity
import com.naviapp.home.compose.home.ui.footer.utils.handleBottomBarData
import com.naviapp.home.compose.home.utils.InitHomeScreenComponents
import com.naviapp.home.compose.home.utils.retryHomePageApi
import com.naviapp.home.compose.widgetfactory.HomeWidgetRenderer
import com.naviapp.home.compose.widgetfactory.HomeUitronRenderer
import com.naviapp.home.reducer.HpEvents
import com.naviapp.home.reducer.HpStates
import com.naviapp.home.utils.WidgetRenderer
@@ -99,8 +99,8 @@ fun HomeScreen(
true -> {
if (hpStates().isLoading) {
HomeScreenScaffoldRoot(
homeWidgetRenderer = {
HomeWidgetRenderer(
uitronRenderer = {
HomeUitronRenderer(
widgetData = it?.data,
viewModel = homeVM(),
composeView = it?.parentComposeView,
@@ -135,8 +135,8 @@ fun HomeScreen(
)
} else {
HomeScreenScaffoldRoot(
homeWidgetRenderer = { widget ->
HomeWidgetRenderer(
uitronRenderer = { widget ->
HomeUitronRenderer(
widgetData = widget?.data,
viewModel = homeVM(),
composeView = widget?.parentComposeView,

View File

@@ -56,7 +56,7 @@ internal fun HomeScreenScaffoldRoot(
hpStates: () -> HpStates,
homeScrollState: () -> ScrollState,
homeVM: () -> HomeViewModel,
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
uitronRenderer: @Composable (UiTronResponse?) -> Unit,
staticNudgeContainer: @Composable () -> Unit,
uiController: SystemUiController,
) {
@@ -75,7 +75,7 @@ internal fun HomeScreenScaffoldRoot(
frontLayerShape = frontLayerShape,
homeScrollState = homeScrollState,
homeVM = homeVM,
homeWidgetRenderer = homeWidgetRenderer,
uitronRenderer = uitronRenderer,
onEffect = { homeVM().setEffect { it } },
onEvent = { homeVM().sendEvent(it) },
isRenderingFirstTime = hpStates().isRenderingFirstTime,
@@ -88,7 +88,7 @@ internal fun HomeScreenScaffoldRoot(
modifier = Modifier,
statusBarHeight = statusBarHeight,
toolbarConfig = (hpStates().collapsingToolbar),
renderWidget = homeWidgetRenderer,
renderWidget = uitronRenderer,
scrollStateProvider = homeScrollState,
onEffect = { homeVM().setEffect { it } },
uiController = uiController,
@@ -96,14 +96,14 @@ internal fun HomeScreenScaffoldRoot(
} else {
BackLayerContent(
backLayerHeight = backLayerHeight,
homeWidgetRenderer = homeWidgetRenderer,
homeWidgetRenderer = uitronRenderer,
backLayerData = (hpStates().collapsingToolbar?.collapsingTopNav)?.uiTronResponse,
)
FrontLayerWithBackDropScroll(
hpStates = hpStates,
homeScrollState = homeScrollState,
homeVM = homeVM,
homeWidgetRenderer = homeWidgetRenderer,
uitronRenderer = uitronRenderer,
frontLayerShape = frontLayerShape,
appBarHeight = appBarHeight,
backLayerHeight = backLayerHeight,
@@ -114,7 +114,7 @@ internal fun HomeScreenScaffoldRoot(
appBarHeight = appBarHeight,
statusBarHeight = statusBarHeight,
topBarContent = (hpStates().collapsingToolbar?.toolBarNav)?.uiTronResponse,
homeWidgetRenderer = homeWidgetRenderer,
uitronRenderer = uitronRenderer,
homeScrollState = homeScrollState,
)
}
@@ -139,7 +139,7 @@ private fun FrontLayerWithBackDropScroll(
hpStates: () -> HpStates,
homeScrollState: () -> ScrollState,
homeVM: () -> HomeViewModel,
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
uitronRenderer: @Composable (UiTronResponse?) -> Unit,
frontLayerShape: RoundedCornerShape,
appBarHeight: Dp,
backLayerHeight: Dp,
@@ -178,7 +178,7 @@ private fun FrontLayerWithBackDropScroll(
.padding(bottom = appBarHeight),
frontLayerShape = frontLayerShape,
widgets = (hpStates().frontLayerContent),
homeWidgetRenderer = homeWidgetRenderer,
uitronRenderer = uitronRenderer,
isRenderingFirstTime = hpStates().isRenderingFirstTime,
homeScrollState = homeScrollState,
homeVM = homeVM,

View File

@@ -16,7 +16,7 @@ import com.naviapp.home.compose.uiTron.renderer.HomeCustomUiTronRenderer
import com.naviapp.home.viewmodel.HomeViewModel
@Composable
fun HomeWidgetRenderer(
fun HomeUitronRenderer(
widgetData: MutableMap<String, UiTronData?>?,
composeView: List<UiTronView>?,
viewModel: HomeViewModel,

View File

@@ -1,14 +0,0 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.naviapp.home.model
data class RenderingConfig(
val isInitialTouchDisabled: Boolean = false,
val isDelayedRenderingEnabled: Boolean = false,
val hasLowerRamThanThreshold: Boolean = false,
)

View File

@@ -85,9 +85,10 @@ class HomeReducer : BaseReducer<HpStates, HpEvents> {
previousState.copy(frontLayerContent = newContent)
}
is HpEvents.UpdateFrontLayerContent -> {
val newFirst = previousState.isRenderingFirstTime && event.isRenderingFirstTime
val updatedList =
updateScreenContent(
renderingFirstTime = event.isRenderingFirstTime,
renderingFirstTime = newFirst,
newWidgets = event.content,
oldWidgets = previousState.frontLayerContent,
)
@@ -95,6 +96,7 @@ class HomeReducer : BaseReducer<HpStates, HpEvents> {
isLoading = false,
isError = false,
frontLayerContent = updatedList,
isRenderingFirstTime = newFirst,
)
}
is HpEvents.UpdatePrioritySectionData -> {

View File

@@ -68,6 +68,9 @@ constructor(
private const val VALUE_INCREMENTAL_SYNC = "incremental_sync"
private const val VALUE_NETWORK_SYNC = "network_sync"
private const val VALUE_ALT_SOURCE_FETCH = "alt_source_fetch"
// Lower ram device
private const val VALUE_SCREEN_UPDATED = "screen_updated_on_low_end_device"
}
private fun trackEvent(type: String, params: Map<String, String> = emptyMap()) {
@@ -77,9 +80,10 @@ constructor(
NaviTrackEvent.trackEvent(EVENT_NAME, eventParams)
}
context(CoroutineScope)
fun syncHomeContentData(
scope: CoroutineScope,
isFirstTimeRender: Boolean,
isDelayedRenderingEnabled: Boolean,
fetchHomeContent: suspend (shouldShowNae: Boolean) -> NaviCacheAltSourceEntity,
onContentSynchronized: () -> Unit,
onHomeFetchSuccessFromNetwork: () -> Unit,
@@ -88,7 +92,8 @@ constructor(
) {
trackEvent(TYPE_SYNC_START, mapOf(PARAM_IS_FIRST_TIME to isFirstTimeRender.toString()))
if (isFirstTimeRender) {
initializeFirstTimeContentSync(
scope.initializeFirstTimeContentSync(
isDelayedRenderingEnabled,
fetchHomeContent,
onContentSynchronized,
eventHandler,
@@ -96,7 +101,7 @@ constructor(
onHomeFetchSuccessFromNetwork,
)
} else {
performIncrementalContentSync(
scope.performIncrementalContentSync(
{ fetchHomeContent(false) },
onContentSynchronized,
eventHandler,
@@ -107,6 +112,7 @@ constructor(
}
private fun CoroutineScope.initializeFirstTimeContentSync(
isDelayedRenderingEnabled: Boolean,
fetchHomeContent: suspend (shouldShowNae: Boolean) -> NaviCacheAltSourceEntity,
onContentSynchronized: () -> Unit,
eventHandler: (HpEvents) -> Unit,
@@ -118,6 +124,7 @@ constructor(
val cacheLoadOperation =
initializeCacheDataLoad(
isDelayedRenderingEnabled = isDelayedRenderingEnabled,
eventHandler = eventHandler,
onContentSynchronized = onContentSynchronized,
onCacheLoaded = { cacheDataLoaded.value = it },
@@ -190,29 +197,37 @@ constructor(
}
private fun CoroutineScope.initializeCacheDataLoad(
isDelayedRenderingEnabled: Boolean,
eventHandler: (HpEvents) -> Unit,
onContentSynchronized: () -> Unit,
onCacheLoaded: (Boolean) -> Unit,
): Deferred<Unit?> {
trackEvent(TYPE_CACHE_LOAD)
return safeAsync(Dispatchers.IO) {
loadAndProcessCacheData(coroutineScope = this, eventHandler = eventHandler) {
cachedEntity ->
val cacheStatus = if (cachedEntity == null) VALUE_NOT_FOUND else VALUE_LOADED
trackEvent(TYPE_CACHE_PROCESS, mapOf(PARAM_CACHE_STATUS to cacheStatus))
onCacheLoaded(cachedEntity != null)
if (cachedEntity != null) {
onContentSynchronized()
eventHandler(HpEvents.RenderedFirstTime)
}
}
loadAndProcessCacheData(
isDelayedRenderingEnabled = isDelayedRenderingEnabled,
eventHandler = eventHandler,
onContentSynchronized = onContentSynchronized,
onCompletion = { cachedEntity ->
val cacheStatus = if (cachedEntity == null) VALUE_NOT_FOUND else VALUE_LOADED
trackEvent(TYPE_CACHE_PROCESS, mapOf(PARAM_CACHE_STATUS to cacheStatus))
onCacheLoaded(cachedEntity != null)
if (cachedEntity != null) {
onContentSynchronized()
eventHandler(HpEvents.RenderedFirstTime)
}
},
onCacheLoaded = onCacheLoaded,
)
}
}
private suspend fun loadAndProcessCacheData(
coroutineScope: CoroutineScope,
private suspend fun CoroutineScope.loadAndProcessCacheData(
isDelayedRenderingEnabled: Boolean,
eventHandler: (HpEvents) -> Unit,
onContentSynchronized: () -> Unit,
onCompletion: (NaviCacheEntity?) -> Unit,
onCacheLoaded: (Boolean) -> Unit,
) {
val cachedHomeContent = naviCacheRepository.get(NaviSharedDbKeys.HOME_TAB.name)
@@ -223,22 +238,30 @@ constructor(
deserializer.setCacheEntity(cachedHomeContent.value)
val prioritySection = deserializer.getPrioritySection()
val updatedPriorityContentSection =
HomePrioritySectionData(
content = getFilteredWidgets(prioritySection.content),
topNav = prioritySection.topNav,
if (isDelayedRenderingEnabled) {
val content = deserializer.getScreen(cachedHomeContent.value)
updateScreenContent(
screenContent = content,
eventHandler = eventHandler,
isFirstRender = true,
)
trackEvent(TYPE_CACHE_PROCESS, mapOf(PARAM_CONTENT_STATUS to VALUE_SCREEN_UPDATED))
onCacheLoaded(true)
onContentSynchronized()
} else {
val prioritySection = deserializer.getPrioritySection()
val updatedPriorityContentSection =
HomePrioritySectionData(
content = getFilteredWidgets(prioritySection.content),
topNav = prioritySection.topNav,
)
eventHandler(HpEvents.UpdatePrioritySectionData(updatedPriorityContentSection))
trackEvent(
TYPE_CACHE_PROCESS,
mapOf(PARAM_CONTENT_STATUS to VALUE_PRIORITY_SECTION_UPDATED),
)
eventHandler(HpEvents.UpdatePrioritySectionData(updatedPriorityContentSection))
trackEvent(
TYPE_CACHE_PROCESS,
mapOf(PARAM_CONTENT_STATUS to VALUE_PRIORITY_SECTION_UPDATED),
)
val screenLayoutDeferred =
coroutineScope.safeAsync {
val screenLayoutDeferred = safeAsync {
val screenLayout = deserializer.getScreenDefinitionWithoutContent()
screenLayout?.let {
eventHandler(HpEvents.UpdateScreenWithoutContent(screenLayout))
@@ -249,26 +272,25 @@ constructor(
}
}
coroutineScope
.safeAsync {
val remainingContent = deserializer.getRemainingContent()
remainingContent?.let { content ->
screenLayoutDeferred.await()
eventHandler(
HpEvents.UpdateFrontLayerContent(
content = getFilteredWidgets(content) ?: emptyList(),
isRenderingFirstTime = true,
safeAsync {
val remainingContent = deserializer.getRemainingContent()
remainingContent?.let { content ->
screenLayoutDeferred.await()
eventHandler(
HpEvents.UpdateFrontLayerContent(
content = getFilteredWidgets(content) ?: emptyList(),
isRenderingFirstTime = true,
)
)
)
trackEvent(
TYPE_CACHE_PROCESS,
mapOf(PARAM_CONTENT_STATUS to VALUE_REMAINING_CONTENT_UPDATED),
)
trackEvent(
TYPE_CACHE_PROCESS,
mapOf(PARAM_CONTENT_STATUS to VALUE_REMAINING_CONTENT_UPDATED),
)
}
}
}
.await()
onCompletion(cachedHomeContent)
.await()
onCompletion(cachedHomeContent)
}
}
private fun CoroutineScope.initiateNetworkSyncAndProcess(

View File

@@ -0,0 +1,85 @@
/*
*
* * Copyright © 2024-2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.naviapp.home.usecase
import android.content.Context
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.common.utils.getTotalRamMemory
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
/**
* Use case responsible for managing rendering configuration decisions based on device capabilities
* and remote configuration flags. This helps optimize the app's rendering behavior based on device
* constraints.
*/
class RenderingConfigurationUseCase
@Inject
constructor(@ApplicationContext private val context: Context) {
companion object {
private const val DEFAULT_TOUCH_DISABLED = false
private const val DEFAULT_DELAYED_RENDERING = false
}
/**
* The device RAM in GB, calculated once and cached for reuse. Returns 0.0 if RAM information
* cannot be determined.
*/
val deviceRamGb: Double by lazy { getTotalRamMemory(context)?.toDoubleOrNull() ?: 0.0 }
/** Flag indicating if the device has lower RAM than the threshold defined in remote config. */
private val isRamBelowThreshold by lazy {
val maxRamThreshold =
FirebaseRemoteConfigHelper.getDouble(
FirebaseRemoteConfigHelper.DISABLE_INITIAL_TOUCH_EVENT_MAX_RAM
)
deviceRamGb < maxRamThreshold
}
/** Flag indicating if delayed rendering is enabled in Firebase Remote Config. */
private val isDelayedRenderingFeatureEnabled by lazy {
FirebaseRemoteConfigHelper.getBoolean(
FirebaseRemoteConfigHelper.ENABLE_DELAYED_HP_WIDGETS_RENDERING,
DEFAULT_DELAYED_RENDERING,
)
}
/** Flag indicating if disabling initial touch events is enabled in Firebase Remote Config. */
private val isTouchDisabledFeatureEnabled by lazy {
FirebaseRemoteConfigHelper.getBoolean(
FirebaseRemoteConfigHelper.DISABLE_INITIAL_TOUCH_EVENT,
DEFAULT_TOUCH_DISABLED,
)
}
/** Cached result for delayed rendering decision. */
private val _isDelayedRenderingEnabled by lazy {
isDelayedRenderingFeatureEnabled && isRamBelowThreshold
}
/** Cached result for initial touch disabled decision. */
private val _isInitialTouchDisabled by lazy {
isTouchDisabledFeatureEnabled && isRamBelowThreshold
}
/**
* Returns whether delayed rendering should be enabled based on device capabilities and remote
* configuration.
*
* @return true if delayed rendering should be enabled, false otherwise
*/
fun isDelayedRenderingEnabled() = _isDelayedRenderingEnabled
/**
* Returns whether initial touch events should be disabled based on device capabilities and
* remote configuration.
*
* @return true if initial touch events should be disabled, false otherwise
*/
fun isInitialTouchDisabled() = _isInitialTouchDisabled
}

View File

@@ -9,7 +9,6 @@ package com.naviapp.home.viewmodel
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope
@@ -26,7 +25,6 @@ import com.navi.base.utils.ConnectivityObserver
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.base.utils.orFalse
import com.navi.common.basemvi.BaseMviViewModel
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.common.model.ModuleNameV2
import com.navi.common.model.NotificationSetting
import com.navi.common.model.SettingsMedium
@@ -35,7 +33,6 @@ 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.getTotalRamMemory
import com.navi.naviwidgets.utils.CURRENT_VERSION_IN_STORE
import com.navi.pay.common.model.view.NaviPayScreenType
import com.navi.pay.utils.NAVI_PAY_CTA_URL_PREFIX
@@ -49,7 +46,6 @@ import com.naviapp.home.common.handler.PostRenderTaskExecutor
import com.naviapp.home.compose.activity.HomePageActivity
import com.naviapp.home.compose.listener.HomeScreenCallbackListener
import com.naviapp.home.model.BottomBarTabType
import com.naviapp.home.model.RenderingConfig
import com.naviapp.home.reducer.HomeReducer
import com.naviapp.home.reducer.HpEffects
import com.naviapp.home.reducer.HpEvents
@@ -59,6 +55,7 @@ import com.naviapp.home.usecase.FetchHomeItemsUseCase
import com.naviapp.home.usecase.HandleCtaUseCase
import com.naviapp.home.usecase.HandleUpiUseCase
import com.naviapp.home.usecase.HomeContentProcessingUseCase
import com.naviapp.home.usecase.RenderingConfigurationUseCase
import com.naviapp.nux.handler.NewUserExperienceHandler
import com.naviapp.utils.Constants.EmailConstants.IS_USER_EMAIL_SUBMITTED
import com.naviapp.utils.Constants.HOME_SCREEN_SCREEN_HASH
@@ -75,7 +72,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
@@ -96,6 +92,7 @@ constructor(
val sectionVisibilityTracker: HomePageSectionImpressionTracker,
val postRenderTaskExecutor: PostRenderTaskExecutor,
private val homeContentProcessingUseCase: HomeContentProcessingUseCase,
private val renderingConfigurationUseCase: RenderingConfigurationUseCase,
@ApplicationContext val context: Context,
) :
BaseMviViewModel<HpStates, HpEvents, HpEffects>(
@@ -108,53 +105,19 @@ constructor(
private var analyticsStartTs = System.currentTimeMillis()
private var isHomeUIRenderedEventLogged = false
var isErrorNaeTriggered = false
private val totalRamMemory = getTotalRamMemory(context = context)?.toDoubleOrNull() ?: 100.0
private var postApiOneTimeActionTriggered by mutableStateOf(false)
// Internet Connectivity
private val _internetConnectivity = MutableSharedFlow<ConnectivityObserver.Status>()
val internetConnectivity: SharedFlow<ConnectivityObserver.Status> = _internetConnectivity
var widgetsDisplayed by mutableIntStateOf(0)
private set
fun isDelayedRenderingEnabled() = renderingConfigurationUseCase.isDelayedRenderingEnabled()
private var postApiOneTimeActionTriggered by mutableStateOf(false)
fun updateDisplayedWidgetCount(count: Int) {
widgetsDisplayed = count
}
private val _delayedRenderingConfig: MutableStateFlow<RenderingConfig?> = MutableStateFlow(null)
val delayedRenderingConfig = _delayedRenderingConfig.asStateFlow()
fun fetchDelayedRenderingConfig(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
val isTouchDisabled =
FirebaseRemoteConfigHelper.getBoolean(
FirebaseRemoteConfigHelper.DISABLE_INITIAL_TOUCH_EVENT,
false,
)
val isDelayedRenderingEnabled =
FirebaseRemoteConfigHelper.getBoolean(
FirebaseRemoteConfigHelper.ENABLE_DELAYED_HP_WIDGETS_RENDERING,
false,
)
val maxRamThreshold =
FirebaseRemoteConfigHelper.getDouble(
FirebaseRemoteConfigHelper.DISABLE_INITIAL_TOUCH_EVENT_MAX_RAM
)
val deviceRamGb = getTotalRamMemory(context)?.toDoubleOrNull() ?: 0.0
_delayedRenderingConfig.value =
RenderingConfig(
isInitialTouchDisabled = isTouchDisabled,
isDelayedRenderingEnabled = isDelayedRenderingEnabled,
hasLowerRamThanThreshold = deviceRamGb < maxRamThreshold,
)
}
}
fun isInitialTouchDisabled() = renderingConfigurationUseCase.isInitialTouchDisabled()
init {
viewModelScope.safeLaunch((Dispatchers.IO)) { observeActionCallback() }
viewModelScope.safeLaunch((Dispatchers.IO)) { observeInternetConnectivity() }
viewModelScope.safeLaunch(Dispatchers.IO) { observeActionCallback() }
viewModelScope.safeLaunch(Dispatchers.IO) { observeInternetConnectivity() }
}
private val _isClickEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -207,7 +170,7 @@ constructor(
screenHash = screenHash,
shouldShowNae = shouldShowNae.orFalse(),
naeScreenName = naeScreenName,
totalRam = totalRamMemory,
totalRam = renderingConfigurationUseCase.deviceRamGb,
onFailure = { errorMessage, errors ->
setEffect { HpEffects.OnApiFailure(errorMessage, errors) }
},
@@ -273,24 +236,24 @@ constructor(
screenHash.orEmpty(),
)
}
with(viewModelScope) {
homeContentProcessingUseCase.syncHomeContentData(
isFirstTimeRender = state.value.isRenderingFirstTime,
eventHandler = ::sendEvent,
onContentSynchronized = ::onContentSynchronized,
fetchHomeContent = {
fetchHomeDataFromApi(
naeScreenName = naeScreenName,
screenHash = screenHash,
shouldShowNae = it,
)
},
onHomeFetchSuccessFromNetwork = ::onHomeFetchSuccessFromNetwork,
onSelectiveRefreshStateChange = {
selectiveRefreshHandler.updateRefreshState(this@HomeViewModel, it)
},
)
}
homeContentProcessingUseCase.syncHomeContentData(
scope = this.viewModelScope,
isFirstTimeRender = state.value.isRenderingFirstTime,
isDelayedRenderingEnabled = isDelayedRenderingEnabled(),
eventHandler = ::sendEvent,
onContentSynchronized = ::onContentSynchronized,
fetchHomeContent = {
fetchHomeDataFromApi(
naeScreenName = naeScreenName,
screenHash = screenHash,
shouldShowNae = it,
)
},
onHomeFetchSuccessFromNetwork = ::onHomeFetchSuccessFromNetwork,
onSelectiveRefreshStateChange = {
selectiveRefreshHandler.updateRefreshState(this@HomeViewModel, it)
},
)
}
}

View File

@@ -41,7 +41,6 @@ import com.navi.rr.scratchcard.ui.compose.ScratchCardRendererV2
import com.navi.rr.utils.constants.ScratchCardAnimationConstants.SCRATCH_CARD_DEFAULT_POSITION
import com.navi.rr.utils.constants.ScratchCardAnimationConstants.SCRATCH_CARD_INITIAL_POSITION
import com.navi.rr.utils.ext.clickable
import com.naviapp.home.compose.home.ui.content.isDelayedRenderingEnabled
import com.naviapp.home.model.BottomBarTabType
import com.naviapp.home.reducer.HpStates
import com.naviapp.home.viewmodel.HomeViewModel
@@ -75,7 +74,7 @@ fun ScratchCardOverlayRenderer(
),
label = EMPTY,
)
val delayedRenderingEnabled = isDelayedRenderingEnabled(homeVM)
val delayedRenderingEnabled = homeVM().isDelayedRenderingEnabled()
var isReadyToRender by remember { mutableStateOf(!delayedRenderingEnabled) }
if (
shouldShowScratchCard(