From 74f10bbf1d16f58817c20e69b769d4b2d7ecf367 Mon Sep 17 00:00:00 2001 From: Hitesh Kumar Date: Thu, 6 Mar 2025 20:24:05 +0530 Subject: [PATCH] NTP-45169 loan tab offer nudge super app (#15305) --- .../home/compose/home/ui/footer/HomeFooter.kt | 34 ++++++++--- .../home/ui/footer/HomePageBottomBar.kt | 61 ++++++++++++++++++- .../home/ui/footer/utils/HomeFooterUtils.kt | 15 ++++- .../home/model/BottomNavBarStateHolder.kt | 3 +- .../naviapp/home/model/HomeBottomNavData.kt | 10 ++- .../home/usecase/BottomNavTabsNudgeHandler.kt | 47 ++++++++++++++ .../com/naviapp/home/viewmodel/SharedVM.kt | 13 +++- .../main/java/com/naviapp/utils/Constants.kt | 1 + 8 files changed, 167 insertions(+), 17 deletions(-) create mode 100644 android/app/src/main/java/com/naviapp/home/usecase/BottomNavTabsNudgeHandler.kt diff --git a/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/HomeFooter.kt b/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/HomeFooter.kt index 71d5aba2a6..3cdf1a2216 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/HomeFooter.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/HomeFooter.kt @@ -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)) + }, + ) + } + } } } diff --git a/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/HomePageBottomBar.kt b/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/HomePageBottomBar.kt index dbeb703365..28e59e60f8 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/HomePageBottomBar.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/HomePageBottomBar.kt @@ -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, + ) + } + } +} diff --git a/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/utils/HomeFooterUtils.kt b/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/utils/HomeFooterUtils.kt index d12d15c83d..622962cd61 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/utils/HomeFooterUtils.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/home/ui/footer/utils/HomeFooterUtils.kt @@ -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, + ) } } diff --git a/android/app/src/main/java/com/naviapp/home/model/BottomNavBarStateHolder.kt b/android/app/src/main/java/com/naviapp/home/model/BottomNavBarStateHolder.kt index a819f8aacc..d2288f01f9 100644 --- a/android/app/src/main/java/com/naviapp/home/model/BottomNavBarStateHolder.kt +++ b/android/app/src/main/java/com/naviapp/home/model/BottomNavBarStateHolder.kt @@ -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 data class BottomNavBarItemData( val isTabClicked: Boolean = false, val showRedDotBadge: Boolean = false, + val nudgeText: String? = null, ) diff --git a/android/app/src/main/java/com/naviapp/home/model/HomeBottomNavData.kt b/android/app/src/main/java/com/naviapp/home/model/HomeBottomNavData.kt index 16819432ab..e118cc9fee 100644 --- a/android/app/src/main/java/com/naviapp/home/model/HomeBottomNavData.kt +++ b/android/app/src/main/java/com/naviapp/home/model/HomeBottomNavData.kt @@ -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? = 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, +) diff --git a/android/app/src/main/java/com/naviapp/home/usecase/BottomNavTabsNudgeHandler.kt b/android/app/src/main/java/com/naviapp/home/usecase/BottomNavTabsNudgeHandler.kt new file mode 100644 index 0000000000..4051392498 --- /dev/null +++ b/android/app/src/main/java/com/naviapp/home/usecase/BottomNavTabsNudgeHandler.kt @@ -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 + } +} diff --git a/android/app/src/main/java/com/naviapp/home/viewmodel/SharedVM.kt b/android/app/src/main/java/com/naviapp/home/viewmodel/SharedVM.kt index 3a3e4ad5ee..12aa3c25eb 100644 --- a/android/app/src/main/java/com/naviapp/home/viewmodel/SharedVM.kt +++ b/android/app/src/main/java/com/naviapp/home/viewmodel/SharedVM.kt @@ -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(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) } diff --git a/android/app/src/main/java/com/naviapp/utils/Constants.kt b/android/app/src/main/java/com/naviapp/utils/Constants.kt index 831bb23416..b9042daa7e 100644 --- a/android/app/src/main/java/com/naviapp/utils/Constants.kt +++ b/android/app/src/main/java/com/naviapp/utils/Constants.kt @@ -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"