NTP-45169 loan tab offer nudge super app (#15305)

This commit is contained in:
Hitesh Kumar
2025-03-06 20:24:05 +05:30
committed by GitHub
parent b2d910f2d3
commit 74f10bbf1d
8 changed files with 167 additions and 17 deletions

View File

@@ -7,12 +7,17 @@
package com.naviapp.home.compose.home.ui.footer
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import com.navi.uitron.model.UiTronResponse
@@ -72,6 +77,7 @@ fun HomeFooterRoot(
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HomeFooter(
modifier: Modifier,
@@ -114,14 +120,24 @@ fun HomeFooter(
}
}
}
HomeBottomBar(
selectedTabId = selectedTabId,
bottomNavBarState = { state.bottomNavBarState },
onTabSelected = { tabId ->
onFooterEvent(HomeFooterEvents.BottomBarOnTabClick(tabId))
onNudgeEvent(NudgeEvent.UpdateNudgeExpandedState(expandState = false))
},
)
Column {
CompositionLocalProvider(
LocalDensity provides
Density(
LocalDensity.current.density,
LocalDensity.current.fontScale.coerceIn(0.85f, 1.5f),
),
LocalOverscrollConfiguration provides null,
) {
HomeBottomBar(
selectedTabId = selectedTabId,
bottomNavBarState = { state.bottomNavBarState },
onTabSelected = { tabId ->
onFooterEvent(HomeFooterEvents.BottomBarOnTabClick(tabId))
onNudgeEvent(NudgeEvent.UpdateNudgeExpandedState(expandState = false))
},
)
}
}
}
}

View File

@@ -7,12 +7,14 @@
package com.naviapp.home.compose.home.ui.footer
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -21,16 +23,31 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.BottomAppBar
import androidx.compose.material.ripple
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.navi.base.utils.EMPTY
import com.navi.design.font.FontWeightEnum
import com.navi.design.font.getFontWeight
import com.navi.design.font.naviFontFamily
import com.naviapp.home.compose.home.navigation.NavigationItem
import com.naviapp.home.compose.home.navigation.getDefaultBottomTabsList
import com.naviapp.home.compose.home.ui.footer.utils.isRedDotBadgeEnabled
@@ -39,6 +56,7 @@ import com.naviapp.home.utils.DrawIcon
import com.naviapp.home.utils.TabText
import com.naviapp.utils.BottomBarUtils
@SuppressLint("UseOfNonLambdaOffsetOverload")
@Composable
fun HomeBottomBar(
selectedTabId: String,
@@ -46,7 +64,7 @@ fun HomeBottomBar(
onTabSelected: (String) -> Unit,
) {
BottomAppBar(
modifier = Modifier.height(68.dp).fillMaxWidth(),
modifier = Modifier.height(70.dp).fillMaxWidth(),
backgroundColor = Color.White,
elevation = 8.dp,
contentPadding = PaddingValues(0.dp),
@@ -57,6 +75,7 @@ fun HomeBottomBar(
selectedTabId = selectedTabId,
onTabSelected = onTabSelected,
showRedDotBadge = { isRedDotBadgeEnabled(bottomTabItem.tabId, bottomNavBarState) },
nudgeText = bottomNavBarState().items[bottomTabItem.tabId]?.nudgeText ?: EMPTY,
)
}
}
@@ -68,8 +87,12 @@ private fun RowScope.BottomBarItem(
selectedTabId: String,
showRedDotBadge: () -> Boolean,
onTabSelected: (String) -> Unit,
nudgeText: String? = null,
) {
item.tabId.let {
val density = LocalDensity.current
val widthCheck = remember { mutableIntStateOf(0) }
val widthInDp = with(density) { widthCheck.intValue.toDp() }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
@@ -81,9 +104,10 @@ private fun RowScope.BottomBarItem(
) {
onTabSelected(it)
}
.padding(top = 6.dp, bottom = 10.dp),
.padding(top = 12.dp, bottom = 6.dp)
.onSizeChanged { widthCheck.intValue = it.width },
) {
Box {
Box(contentAlignment = Alignment.Center) {
DrawIcon(
drawableIconId =
BottomBarUtils.fetchDefaultTabIconResourceId(
@@ -102,6 +126,7 @@ private fun RowScope.BottomBarItem(
.background(Color.Red, shape = CircleShape)
)
}
nudgeText?.takeIf { it.isNotEmpty() }?.let { NudgePill(it, widthInDp) }
}
Spacer(modifier = Modifier.height(6.dp))
TabText(
@@ -112,3 +137,33 @@ private fun RowScope.BottomBarItem(
}
}
}
@Composable
fun NudgePill(text: String, widthInDp: Dp) {
Row {
Box(modifier = Modifier.weight(1f).offset(x = widthInDp / 2, y = (-10).dp)) {
Text(
text = text,
modifier =
Modifier.background(
Color(0xFF22A940),
shape =
RoundedCornerShape(bottomEnd = 4.dp, topStart = 4.dp, topEnd = 4.dp),
)
.padding(horizontal = 4.dp, vertical = 2.dp)
.wrapContentSize(),
color = Color.White,
style =
TextStyle(
fontSize = 8.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
lineHeight = 12.sp,
),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
textAlign = TextAlign.Center,
)
}
}
}

View File

@@ -109,6 +109,19 @@ fun handleBottomBarData(footer: AlchemistWidgetGroup, sharedVM: SharedVM) {
?.data
?.get(HOME_BOTTOM_NAV_BAR_DATA) as? HomeBottomNavBarData
bottomNavBarData?.bottomNavBarDetails?.entries?.forEach { (key, value) ->
sharedVM.updateBottomNavBarState(tabId = key, showRedDotBadge = value.showRedDot)
if (value.resetNudgeVisibleCount == true && key == BottomBarTabType.LOAN.name) {
sharedVM.bottomNavTabsNudgeHandler.resetLoanTabOfferNudgeAppearedCount()
}
val nudgeText =
sharedVM.bottomNavTabsNudgeHandler.getNudgeText(
value.nudgeText,
value.nudgeTextSuffix,
value.nudgeVisibleMaxCount,
)
sharedVM.updateBottomNavBarState(
tabId = key,
showRedDotBadge = value.showRedDot,
nudgeText = nudgeText,
)
}
}

View File

@@ -1,6 +1,6 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * Copyright © 2024-2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
@@ -12,4 +12,5 @@ data class BottomNavBarStateHolder(val items: Map<String, BottomNavBarItemData>
data class BottomNavBarItemData(
val isTabClicked: Boolean = false,
val showRedDotBadge: Boolean = false,
val nudgeText: String? = null,
)

View File

@@ -1,6 +1,6 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * Copyright © 2024-2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
@@ -13,4 +13,10 @@ data class HomeBottomNavBarData(
val bottomNavBarDetails: Map<String, BottomNavBarItemInfo>? = null
) : UiTronData()
data class BottomNavBarItemInfo(val showRedDot: Boolean? = null)
data class BottomNavBarItemInfo(
val showRedDot: Boolean? = null,
val nudgeText: String? = null,
val nudgeTextSuffix: String? = null,
val nudgeVisibleMaxCount: Int? = null,
val resetNudgeVisibleCount: Boolean? = null,
)

View File

@@ -0,0 +1,47 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.naviapp.home.usecase
import com.navi.base.sharedpref.PreferenceManager
import com.navi.base.utils.EMPTY
import com.naviapp.utils.Constants.LOAN_TAB_OFFER_NUDGE_APPEARED_COUNT
import javax.inject.Inject
class BottomNavTabsNudgeHandler @Inject constructor() {
companion object {
private const val DEFAULT_NUDGE_VISIBLE_MAX_COUNT = 3
}
private var loanTabOfferNudgeAppearedCount: Int =
PreferenceManager.getIntPreference(LOAN_TAB_OFFER_NUDGE_APPEARED_COUNT)
fun incrementLoanTabOfferNudgeAppearedCount() {
PreferenceManager.setIntPreference(
LOAN_TAB_OFFER_NUDGE_APPEARED_COUNT,
loanTabOfferNudgeAppearedCount + 1,
)
}
fun resetLoanTabOfferNudgeAppearedCount() {
PreferenceManager.setIntPreference(LOAN_TAB_OFFER_NUDGE_APPEARED_COUNT, 0)
}
fun getNudgeText(
nudgeText: String?,
nudgeTextSuffix: String?,
nudgeVisibleMaxCount: Int?,
): String {
val finalNudgeText = (nudgeText ?: EMPTY) + (nudgeTextSuffix ?: EMPTY)
val shouldDisplayNudge =
loanTabOfferNudgeAppearedCount <
(nudgeVisibleMaxCount ?: DEFAULT_NUDGE_VISIBLE_MAX_COUNT)
return if (shouldDisplayNudge) finalNudgeText else EMPTY
}
}

View File

@@ -8,6 +8,7 @@
package com.naviapp.home.viewmodel
import androidx.lifecycle.viewModelScope
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.common.viewmodel.BaseVM
import com.naviapp.common.model.AppUpdateState
import com.naviapp.common.model.UiTronActionHandler
@@ -20,6 +21,7 @@ import com.naviapp.home.model.HpBottomSheetConfig
import com.naviapp.home.model.HpBottomSheetContent
import com.naviapp.home.model.HpBottomSheetState
import com.naviapp.home.model.HpBottomSheetStateHolder
import com.naviapp.home.usecase.BottomNavTabsNudgeHandler
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -30,7 +32,8 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@HiltViewModel
class SharedVM @Inject constructor() : BaseVM() {
class SharedVM @Inject constructor(val bottomNavTabsNudgeHandler: BottomNavTabsNudgeHandler) :
BaseVM() {
private val _uiTronActionHandler = MutableStateFlow<UiTronActionHandler?>(null)
val uiTronActionHandler = _uiTronActionHandler.asStateFlow()
@@ -70,6 +73,12 @@ class SharedVM @Inject constructor() : BaseVM() {
fun updateSelectedTabId(tabId: String) {
_selectedTabId.update { tabId }
if (
tabId == BottomBarTabType.LOAN.name &&
_bottomNavBarStateHolder.value.items[tabId]?.nudgeText.isNotNullAndNotEmpty()
) {
bottomNavTabsNudgeHandler.incrementLoanTabOfferNudgeAppearedCount()
}
}
fun getSelectedTabId(): String {
@@ -84,6 +93,7 @@ class SharedVM @Inject constructor() : BaseVM() {
tabId: String,
isTabClicked: Boolean? = null,
showRedDotBadge: Boolean? = null,
nudgeText: String? = null,
) {
val currentState = _bottomNavBarStateHolder.value.items.toMutableMap()
currentState[tabId]?.let {
@@ -91,6 +101,7 @@ class SharedVM @Inject constructor() : BaseVM() {
BottomNavBarItemData(
isTabClicked = isTabClicked ?: it.isTabClicked,
showRedDotBadge = showRedDotBadge ?: it.showRedDotBadge,
nudgeText = nudgeText ?: it.nudgeText,
)
}
_bottomNavBarStateHolder.update { BottomNavBarStateHolder(currentState) }

View File

@@ -241,6 +241,7 @@ object Constants {
const val STATUS = "status"
const val HTTP_REGEX = "^https?://"
const val NAVIHQ = "NAVIHQ"
const val LOAN_TAB_OFFER_NUDGE_APPEARED_COUNT = "LOAN_TAB_OFFER_NUDGE_APPEARED_COUNT"
object Notification {
const val HIDE_NOTIFICATION_COUNT = "hideNotificationCount"