NTP-19767 | Masthead (#14544)

Co-authored-by: Ankit <ankit.yadav@navi.com>
This commit is contained in:
Abhinav Gupta
2025-01-28 22:04:27 +05:30
committed by GitHub
parent f0d1d8bf14
commit 73e1ece5a7
23 changed files with 589 additions and 67 deletions

View File

@@ -13,6 +13,7 @@ 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.naviapp.analytics.utils.NaviAnalytics
import com.naviapp.common.transformer.AppLoadTimerMapper
@@ -23,10 +24,12 @@ import com.naviapp.home.reducer.HpEvents
import com.naviapp.home.viewmodel.HomeViewModel
import com.naviapp.network.di.DataDeserializers
import com.naviapp.screenOverlay.utils.PopupConstants.NOTIFICATION_ICON_ANIMATE
import com.naviapp.utils.Constants.HomePageConstants.ALPHA
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.HomePageConstants.TOP_NAV_ARC_BACKGROUND
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
@@ -64,9 +67,19 @@ constructor(
animateInAppNotificationIcon(effects.shouldAnimate, homeVM)
is HpEffects.HomePageUiRenderedEvent -> homeVM.logOnDataDisplayedEvent()
is HpEffects.TopNavRevealedAnalytics -> naviAnalyticsEventTracker.topNavRevealed()
is HpEffects.UpdateMastheadIconsState ->
updateMastheadIconsState(effects.alphaFactor, homeVM)
}
}
private fun updateMastheadIconsState(alphaFactor: Int, homeVM: HomeViewModel) {
homeVM.handleAction(
UpdateViewStateAction(
viewStates = mapOf(TOP_NAV_ARC_BACKGROUND to ALPHA.plus(alphaFactor))
)
)
}
private fun animateInAppNotificationIcon(shouldAnimate: Boolean, homeVM: HomeViewModel) {
if (shouldAnimate) {
homeVM.state.value.collapsingToolbar

View File

@@ -27,6 +27,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
@@ -38,7 +39,11 @@ import com.navi.naviwidgets.R
import com.navi.naviwidgets.utils.NaviWidgetIconUtils.getImageFromIconCode
import com.naviapp.home.utils.shimmerEffect
fun Modifier.bottomShadow(showShadow: Boolean = true, elevation: Float = 24f): Modifier =
fun Modifier.bottomShadow(
showShadow: Boolean = true,
elevation: Float = 24f,
shadowLengthFactor: Int = 2,
): Modifier =
this.then(
Modifier.drawBehind {
drawRect(
@@ -46,9 +51,14 @@ fun Modifier.bottomShadow(showShadow: Boolean = true, elevation: Float = 24f): M
when (showShadow) {
true ->
Brush.verticalGradient(
colors = listOf(Color.Black.copy(0.09f), Color.Transparent),
colorStops =
arrayOf(
Pair(0.22f, Color(0XFFB0C0D9).copy(alpha = 0.12f)),
Pair(1f, Color.Transparent),
),
startY = size.height,
endY = size.height + elevation.times(2),
endY = size.height + elevation.times(shadowLengthFactor),
tileMode = TileMode.Decal,
)
false ->
Brush.verticalGradient(
@@ -56,7 +66,7 @@ fun Modifier.bottomShadow(showShadow: Boolean = true, elevation: Float = 24f): M
)
},
topLeft = Offset(0f, size.height),
size = Size(size.width, elevation.times(2)),
size = Size(size.width, elevation.times(shadowLengthFactor)),
)
}
)

View File

@@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.navi.paymentclients.viewmodel.base.PaymentManager
import com.naviapp.analytics.utils.NaviAnalytics
import com.naviapp.common.viewmodel.BottomNavBarVM
@@ -48,6 +49,7 @@ fun NavGraphNavigationItem(
nudgeState: () -> NudgeState,
paymentManager: PaymentManager,
) {
val uiController = rememberSystemUiController()
when (tabId) {
NavigationItem.Home.tabId ->
HomeScreen(
@@ -60,6 +62,7 @@ fun NavGraphNavigationItem(
screenOverlayVM = screenOverlayVM,
homeVM = homeVM,
nudgeState = nudgeState,
uiController = uiController,
)
NavigationItem.Investment.tabId ->
InvestmentsScreen(
@@ -74,8 +77,10 @@ fun NavGraphNavigationItem(
bottomNavBarVM = bottomNavBarVM,
paymentManager = paymentManager,
dashboardSharedVM = dashboardSharedVM,
uiController = uiController,
)
NavigationItem.Loan.tabId -> LoansTabScreen(activity = activity)
NavigationItem.Loan.tabId ->
LoansTabScreen(activity = activity, uiController = uiController)
NavigationItem.Insurance.tabId -> {
InsuranceTabScreen(
activity = activity,
@@ -84,6 +89,7 @@ fun NavGraphNavigationItem(
.padding(bottom = 56.dp)
.statusBarsPadding()
.background(Color.White),
uiController = uiController,
)
}
}

View File

@@ -19,6 +19,7 @@ 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
@@ -45,6 +46,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
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.compose.ui.unit.dp
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
@@ -52,6 +54,7 @@ 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.common.model.common.MastheadWidgetData
import com.navi.naviwidgets.R
import com.navi.pay.utils.conditional
import com.navi.uitron.model.UiTronResponse
@@ -74,6 +77,9 @@ fun FrontLayerContent(
onEvent: (event: HpEvents) -> Unit,
onEffect: (effect: HpEffects) -> Unit,
staticNudgeContainer: @Composable () -> Unit,
mastheadWidget: MastheadWidgetData? = null,
isMastheadEnabled: Boolean,
appBarHeight: Dp,
) {
CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
Column(
@@ -85,7 +91,11 @@ fun FrontLayerContent(
) {
if (!widgets.isNullOrEmpty()) {
Column {
TopNotchUI()
if (isMastheadEnabled) {
MastheadWidget(homeWidgetRenderer, mastheadWidget, appBarHeight)
} else {
TopNotchUI()
}
staticNudgeContainer()
RenderUiTronContent(
elementList = widgets,
@@ -108,6 +118,29 @@ fun FrontLayerContent(
}
}
@Composable
private fun MastheadWidget(
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
mastheadWidget: MastheadWidgetData?,
appBarHeight: Dp,
) {
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(
@@ -185,7 +218,7 @@ private fun AnimatedContainerForWidgets(
}
@Composable
private fun RenderUiTronContent(
internal fun RenderUiTronContent(
elementList: List<AlchemistWidgetModelDefinition<UiTronResponse>>,
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
homeVM: () -> HomeViewModel,

View File

@@ -50,6 +50,7 @@ import com.naviapp.utils.Constants.HOME_SCREEN_IN_CAPS
@Composable
fun HomeAppUpdateWidget(
modifier: Modifier = Modifier,
content: AlchemistWidgetModelDefinition<UiTronResponse>,
appUpdateState: AppUpdateState,
inAppUpdateBridge: InAppUpdateBridge,
@@ -93,7 +94,8 @@ fun HomeAppUpdateWidget(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier.fillMaxWidth()
modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
.border(
width = 1.dp,

View File

@@ -52,6 +52,7 @@ import com.naviapp.utils.Constants.HOME_SCREEN_IN_CAPS
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun HomeNotifyMeWidget(
modifier: Modifier = Modifier,
widget: AlchemistWidgetModelDefinition<UiTronResponse>,
showBottomSheet: () -> Unit,
onDismissNudge: () -> Unit,
@@ -87,6 +88,7 @@ fun HomeNotifyMeWidget(
if (visible) {
notifyMeAnalytics.notifyMeNudgeViewEvent()
NotifyMeUI(
modifier = modifier,
permissionGranted = (pushNotificationPermission.allPermissionsGranted),
widgetData,
onDismissNudge = {
@@ -107,6 +109,7 @@ fun HomeNotifyMeWidget(
@Composable
private fun NotifyMeUI(
modifier: Modifier,
permissionGranted: Boolean,
widgetData: NotifyMeWidgetData?,
onDismissNudge: () -> Unit,
@@ -116,7 +119,8 @@ private fun NotifyMeUI(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier.fillMaxWidth()
modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
.border(width = 1.dp, color = Color(0xFFE3E5E5), shape = RoundedCornerShape(4.dp))
.padding(horizontal = 16.dp, vertical = 12.dp),

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -27,11 +28,15 @@ 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 com.google.accompanist.systemuicontroller.SystemUiController
import com.navi.common.alchemist.model.AlchemistCollapsingToolbar
import com.navi.common.extensions.conditional
import com.navi.naviwidgets.R as WidgetR
import com.navi.uitron.model.UiTronResponse
import com.naviapp.R
import com.naviapp.home.compose.extension.bottomShadow
import com.naviapp.home.reducer.HpEffects
import com.naviapp.utils.Constants.HomePageConstants.SCROLL_THRESHOLD
@Composable
fun HomeTopBar(
@@ -55,15 +60,132 @@ fun HomeTopBar(
}
// Render the top bar
Row(modifier = topBarModifier, verticalAlignment = Alignment.Top) {
ToolbarRender(topBarModifier, statusBarHeight, topBarContent, homeWidgetRenderer)
}
@Composable
fun MastheadTopBar(
modifier: Modifier,
statusBarHeight: Dp,
toolbarConfig: AlchemistCollapsingToolbar?,
renderWidget: @Composable (UiTronResponse) -> Unit,
scrollStateProvider: () -> ScrollState,
onEffect: (effect: HpEffects) -> Unit,
uiController: SystemUiController,
) {
val scrollState by remember {
derivedStateOf {
val scrollOffset = scrollStateProvider().value.toFloat()
val isScrolledBeyondThreshold = scrollOffset >= SCROLL_THRESHOLD
val scrollProgress = (scrollOffset / SCROLL_THRESHOLD).coerceIn(0f, 1f)
isScrolledBeyondThreshold to scrollProgress
}
}
val (isScrolledBeyondThreshold, scrollProgress) = scrollState
// Update status bar color based on scroll
HandleStatusBarIcons(
uiController = uiController,
isScrolledBeyondThreshold = isScrolledBeyondThreshold,
toolbarConfig = toolbarConfig,
)
// Update icon background alpha based on scroll progress
HandleToolbarIconsBackground(
scrollProgress = scrollProgress,
toolbarConfig = toolbarConfig,
onEffect = onEffect,
)
// Create toolbar modifier with scroll progress
val toolbarModifier = modifier.createToolbarModifier(scrollProgress = scrollProgress)
// Render the toolbar
ToolbarRender(
modifier = toolbarModifier,
statusBarHeight = statusBarHeight,
toolbarContent = toolbarConfig?.toolBarNav?.uiTronResponse,
homeWidgetRenderer = renderWidget,
mastheadEnable = true,
)
}
@Composable
private fun Modifier.createToolbarModifier(scrollProgress: Float): Modifier =
remember(scrollProgress) {
bottomShadow(showShadow = scrollProgress > 0, elevation = 5f, shadowLengthFactor = 8)
.drawBehind { drawRect(color = Color.White.copy(alpha = scrollProgress)) }
}
/**
* Changes status bar icon color based on background color and scroll state. Uses dark icons for
* light backgrounds.
*/
@Composable
private fun HandleStatusBarIcons(
uiController: SystemUiController,
isScrolledBeyondThreshold: Boolean,
toolbarConfig: AlchemistCollapsingToolbar?,
) {
LaunchedEffect(
key1 = isScrolledBeyondThreshold,
key2 = toolbarConfig?.mastheadWidget?.useLightStatusBar,
) {
toolbarConfig?.mastheadWidget?.let { mastheadWidget ->
when {
mastheadWidget.useLightStatusBar -> {
uiController.setStatusBarColor(
color = Color.Transparent,
darkIcons = isScrolledBeyondThreshold,
)
}
!uiController.statusBarDarkContentEnabled -> {
uiController.setStatusBarColor(color = Color.Transparent, darkIcons = true)
}
}
}
}
}
/**
* Updates the masthead icons' background alpha based on scroll progress. Ensures a minimum alpha of
* 0.3 (factor 3) and a maximum of 1.0 (factor 10).
*/
@Composable
private fun HandleToolbarIconsBackground(
scrollProgress: Float,
toolbarConfig: AlchemistCollapsingToolbar?,
onEffect: (HpEffects) -> Unit,
) {
val iconBackgroundAlphaFactor = remember(scrollProgress) { ((scrollProgress * 7) + 3).toInt() }
LaunchedEffect(
key1 = iconBackgroundAlphaFactor,
key2 = toolbarConfig?.mastheadWidget?.updateIconsOnScroll,
) {
toolbarConfig
?.mastheadWidget
?.takeIf { it.updateIconsOnScroll }
?.let { onEffect(HpEffects.UpdateMastheadIconsState(iconBackgroundAlphaFactor)) }
}
}
@Composable
private fun ToolbarRender(
modifier: Modifier,
statusBarHeight: Dp,
toolbarContent: UiTronResponse?,
homeWidgetRenderer: @Composable (UiTronResponse) -> Unit,
mastheadEnable: Boolean = false,
) {
Row(modifier = modifier, verticalAlignment = Alignment.Top) {
Row(Modifier.padding(top = statusBarHeight)) {
topBarContent?.let { homeWidgetRenderer(it) } ?: DefaultTopBar()
toolbarContent?.let { homeWidgetRenderer(it) } ?: DefaultTopBar(mastheadEnable)
}
}
}
@Composable
fun DefaultTopBar() {
fun DefaultTopBar(mastheadEnable: Boolean = false) {
Row(
Modifier.fillMaxWidth().height(60.dp).padding(start = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
@@ -71,12 +193,18 @@ fun DefaultTopBar() {
) {
Image(
modifier = Modifier.size(40.dp),
painter = painterResource(id = R.drawable.app_ic_profile),
painter =
if (mastheadEnable)
painterResource(id = com.navi.naviwidgets.R.drawable.ic_profile_with_upi)
else painterResource(id = R.drawable.app_ic_profile),
contentDescription = "Profile Icon",
)
Image(
modifier = Modifier.size(40.dp),
painter = painterResource(id = WidgetR.drawable.navi_widgets_ic_notification),
painter =
if (mastheadEnable)
painterResource(id = com.navi.naviwidgets.R.drawable.ic_home_notification)
else painterResource(id = WidgetR.drawable.navi_widgets_ic_notification),
contentDescription = "Notifications Icon",
)
}

View File

@@ -7,11 +7,15 @@
package com.naviapp.home.compose.home.ui.screen
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.systemuicontroller.SystemUiController
import com.navi.common.checkmate.core.CheckMateManager
import com.navi.common.checkmate.model.MetricInfo
import com.navi.common.network.models.RepoResult
@@ -46,6 +50,7 @@ fun HomeScreen(
naviAnalyticsEventTracker: NaviAnalytics.Home,
screenOverlayVM: ScreenOverlayVM,
nudgeState: () -> NudgeState,
uiController: SystemUiController,
) {
val homeScrollState = rememberScrollState()
val appUpdateState by sharedVM.appUpdateState.collectAsStateWithLifecycle()
@@ -119,6 +124,7 @@ fun HomeScreen(
},
)
},
uiController = uiController,
)
} else {
HomeScreenScaffoldRoot(
@@ -138,6 +144,10 @@ fun HomeScreen(
state = nudgeState().staticNudgeData,
widgetRenderer = {
WidgetRenderer(
modifier =
if (hpStates().collapsingToolbar?.mastheadEnable == true)
Modifier.padding(top = 8.dp)
else Modifier,
staticNudgeData = nudgeState().staticNudgeData,
sharedVM = sharedVM,
viewModel = screenOverlayVM,
@@ -154,6 +164,7 @@ fun HomeScreen(
},
)
},
uiController = uiController,
)
hpStates().footer?.let { handleBottomBarData(it, sharedVM) }
}

View File

@@ -32,11 +32,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import com.google.accompanist.systemuicontroller.SystemUiController
import com.navi.common.utils.getStatusBarHeight
import com.navi.uitron.model.UiTronResponse
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.HomeTopBar
import com.naviapp.home.compose.home.ui.header.MastheadTopBar
import com.naviapp.home.compose.home.utils.TopNaviMiddlePillAnimation
import com.naviapp.home.compose.home.utils.backDropNestedScrollConnection
import com.naviapp.home.compose.home.utils.getFrontLayerOffset
@@ -56,6 +58,7 @@ internal fun HomeScreenScaffoldRoot(
homeVM: () -> HomeViewModel,
homeWidgetRenderer: @Composable (UiTronResponse?) -> Unit,
staticNudgeContainer: @Composable () -> Unit,
uiController: SystemUiController,
) {
val density = LocalDensity.current
val statusBarHeight = remember { with(density) { getStatusBarHeight().toDp() } }
@@ -64,39 +67,63 @@ internal fun HomeScreenScaffoldRoot(
val frontLayerShape = remember {
RoundedCornerShape(topStart = HOME_SHAPE_CURVATURE, topEnd = HOME_SHAPE_CURVATURE)
}
Box(Modifier.fillMaxSize().navigationBarsPadding()) {
BackLayerContent(
backLayerHeight = backLayerHeight,
homeWidgetRenderer = homeWidgetRenderer,
backLayerData = (hpStates().collapsingToolbar?.collapsingTopNav)?.uiTronResponse,
)
FrontLayerRoot(
hpStates = hpStates,
homeScrollState = homeScrollState,
homeVM = homeVM,
homeWidgetRenderer = homeWidgetRenderer,
frontLayerShape = frontLayerShape,
appBarHeight = appBarHeight,
backLayerHeight = backLayerHeight,
staticNudgeContainer = staticNudgeContainer,
)
HomeTopBar(
modifier = Modifier.align(Alignment.TopCenter),
appBarHeight = appBarHeight,
statusBarHeight = statusBarHeight,
topBarContent = (hpStates().collapsingToolbar?.toolBarNav)?.uiTronResponse,
homeWidgetRenderer = homeWidgetRenderer,
homeScrollState = homeScrollState,
)
if (hpStates().collapsingToolbar?.mastheadEnable == true) {
FrontLayerContent(
modifier = Modifier,
widgets = (hpStates().frontLayerContent),
frontLayerShape = frontLayerShape,
homeScrollState = homeScrollState,
homeVM = homeVM,
homeWidgetRenderer = homeWidgetRenderer,
onEffect = { homeVM().setEffect { it } },
onEvent = { homeVM().sendEvent(it) },
isRenderingFirstTime = hpStates().isRenderingFirstTime,
staticNudgeContainer = staticNudgeContainer,
mastheadWidget = (hpStates().collapsingToolbar?.mastheadWidget),
isMastheadEnabled = true,
appBarHeight = appBarHeight,
)
MastheadTopBar(
modifier = Modifier,
statusBarHeight = statusBarHeight,
toolbarConfig = (hpStates().collapsingToolbar),
renderWidget = homeWidgetRenderer,
scrollStateProvider = homeScrollState,
onEffect = { homeVM().setEffect { it } },
uiController = uiController,
)
} else {
BackLayerContent(
backLayerHeight = backLayerHeight,
homeWidgetRenderer = homeWidgetRenderer,
backLayerData = (hpStates().collapsingToolbar?.collapsingTopNav)?.uiTronResponse,
)
FrontLayerWithBackDropScroll(
hpStates = hpStates,
homeScrollState = homeScrollState,
homeVM = homeVM,
homeWidgetRenderer = homeWidgetRenderer,
frontLayerShape = frontLayerShape,
appBarHeight = appBarHeight,
backLayerHeight = backLayerHeight,
staticNudgeContainer = staticNudgeContainer,
)
HomeTopBar(
modifier = Modifier.align(Alignment.TopCenter),
appBarHeight = appBarHeight,
statusBarHeight = statusBarHeight,
topBarContent = (hpStates().collapsingToolbar?.toolBarNav)?.uiTronResponse,
homeWidgetRenderer = homeWidgetRenderer,
homeScrollState = homeScrollState,
)
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun FrontLayerRoot(
private fun FrontLayerWithBackDropScroll(
hpStates: () -> HpStates,
homeScrollState: () -> ScrollState,
homeVM: () -> HomeViewModel,
@@ -137,15 +164,17 @@ private fun FrontLayerRoot(
.nestedScroll(backDropNestedScrollConnection(draggableState))
.anchoredDraggable(state = draggableState, orientation = Orientation.Vertical)
.padding(bottom = appBarHeight),
widgets = (hpStates().frontLayerContent),
frontLayerShape = frontLayerShape,
widgets = (hpStates().frontLayerContent),
homeWidgetRenderer = homeWidgetRenderer,
isRenderingFirstTime = hpStates().isRenderingFirstTime,
homeScrollState = homeScrollState,
homeVM = homeVM,
homeWidgetRenderer = homeWidgetRenderer,
onEffect = { homeVM().setEffect { it } },
onEvent = { homeVM().sendEvent(it) },
isRenderingFirstTime = hpStates().isRenderingFirstTime,
onEffect = { homeVM().setEffect { it } },
staticNudgeContainer = staticNudgeContainer,
isMastheadEnabled = false,
appBarHeight = appBarHeight,
)
LaunchedEffect(draggableState.settledValue) {

View File

@@ -5,13 +5,13 @@
*
*/
import android.app.Activity
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.systemuicontroller.SystemUiController
import com.navi.base.model.NaviClickAction
import com.navi.common.utils.setStatusBarColorInt
import com.navi.design.utils.parseColorSafe
import com.navi.common.utils.parseColor
import com.navi.naviwidgets.callbacks.WidgetCallback
import com.naviapp.common.tab.InsuranceTabViewModel
import com.naviapp.home.compose.activity.HomePageActivity
@@ -19,7 +19,11 @@ import com.naviapp.home.dashboard.ui.compose.insuranceTab.InsuranceTabInit
import com.naviapp.home.dashboard.ui.compose.insuranceTab.RenderUiTronDataSecondary
@Composable
fun InsuranceTabScreen(activity: HomePageActivity, modifier: Modifier = Modifier) {
fun InsuranceTabScreen(
activity: HomePageActivity,
modifier: Modifier = Modifier,
uiController: SystemUiController,
) {
val viewModel: InsuranceTabViewModel = hiltViewModel()
val widgetCallback =
object : WidgetCallback {
@@ -32,10 +36,10 @@ fun InsuranceTabScreen(activity: HomePageActivity, modifier: Modifier = Modifier
modifier = modifier,
viewModel = viewModel,
widgetCallback = widgetCallback,
toggleStatusBarColor = { color -> toggleStatusBarColor(activity, color) },
toggleStatusBarColor = { color -> toggleStatusBarColor(uiController, color) },
)
}
private fun toggleStatusBarColor(activity: Activity, color: String) {
activity.setStatusBarColorInt(color.parseColorSafe())
private fun toggleStatusBarColor(uiController: SystemUiController, color: String) {
uiController.setStatusBarColor(color = Color(color.parseColor()), darkIcons = true)
}

View File

@@ -5,17 +5,17 @@
*
*/
import android.app.Activity
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.systemuicontroller.SystemUiController
import com.navi.common.model.ModuleNameV2
import com.navi.common.ui.errorview.FullScreenErrorComposeView
import com.navi.common.utils.setStatusBarColorInt
import com.navi.design.utils.parseColorSafe
import com.navi.common.utils.parseColor
import com.navi.paymentclients.viewmodel.base.PaymentManager
import com.naviapp.common.viewmodel.BottomNavBarVM
import com.naviapp.dashboard.viewmodels.DashboardSharedVM
@@ -38,6 +38,7 @@ fun InvestmentsScreen(
bottomNavBarVM: BottomNavBarVM,
paymentManager: PaymentManager,
dashboardSharedVM: DashboardSharedVM,
uiController: SystemUiController,
) {
val investmentsTabVm by lazy { ViewModelProvider(activity)[InvestmentsVm::class.java] }
val investmentsScreenData =
@@ -61,6 +62,7 @@ fun InvestmentsScreen(
)
InvestmentsTabShimmer()
}
is InvestmentsVm.InvestmentsTabScreenState.Success -> {
investmentsTabVm.fireEvent(
eventName = InvestmentTabEvents.INVESTMENT_TAB_SUCCESS.eventName,
@@ -73,7 +75,7 @@ fun InvestmentsScreen(
sharedVM = sharedVM,
investmentsScreenHelper = investmentsScreenHelper,
hopper = hopper,
toggleStatusBarColor = { toggleStatusBarColor(activity, it) },
toggleStatusBarColor = { toggleStatusBarColor(uiController, it) },
bottomNavBarVM = bottomNavBarVM,
paymentManager = paymentManager,
dashboardSharedVM = dashboardSharedVM,
@@ -84,6 +86,7 @@ fun InvestmentsScreen(
)
}
}
is InvestmentsVm.InvestmentsTabScreenState.Error -> {
FullScreenErrorComposeView(
error = investmentsScreenData.error,
@@ -98,6 +101,6 @@ fun InvestmentsScreen(
}
}
private fun toggleStatusBarColor(activity: Activity, color: String) {
activity.setStatusBarColorInt(color.parseColorSafe())
private fun toggleStatusBarColor(uiController: SystemUiController, color: String) {
uiController.setStatusBarColor(color = Color(color.parseColor()), darkIcons = true)
}

View File

@@ -15,9 +15,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.lifecycleScope
import com.google.accompanist.systemuicontroller.SystemUiController
import com.navi.analytics.utils.NaviTrackEvent
import com.navi.common.ui.errorview.FullScreenErrorComposeView
import com.navi.common.utils.Constants.CTAData
@@ -33,7 +35,14 @@ import com.naviapp.utils.Constants.PL_WEB_URL_HOST_NAME
import com.naviapp.utils.Constants.STATE
@Composable
fun LoansTabScreen(activity: HomePageActivity, loansTabVm: LoanTabVm = hiltViewModel()) {
fun LoansTabScreen(
activity: HomePageActivity,
loansTabVm: LoanTabVm = hiltViewModel(),
uiController: SystemUiController,
) {
LaunchedEffect(Unit) {
uiController.setStatusBarColor(color = Color.Transparent, darkIcons = true)
}
val loansTabHelper by lazy { LoansTabHelper() }
LoansTabWebView(loansTabVm, activity, loansTabHelper)
}
@@ -57,6 +66,7 @@ private fun LoansTabWebView(
},
)
}
is AuthTokenState.Success -> {
Box(modifier = Modifier.fillMaxSize()) {
RenderWebView(

View File

@@ -0,0 +1,17 @@
/*
*
* * Copyright © 2023-2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.naviapp.home.model
import com.navi.common.alchemist.model.AlchemistCollapsingToolbar
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
import com.navi.uitron.model.UiTronResponse
data class HomePrioritySectionData(
val content: List<AlchemistWidgetModelDefinition<UiTronResponse>>?,
val topNav: AlchemistCollapsingToolbar? = null,
)

View File

@@ -28,6 +28,7 @@ import com.naviapp.common.model.UiTronActionHandler
import com.naviapp.home.compose.home.utils.updateScreenContent
import com.naviapp.home.compose.model.CtaActionEvent
import com.naviapp.home.compose.model.InitiatePaymentFromComposeData
import com.naviapp.home.model.HomePrioritySectionData
import com.naviapp.models.response.HomeFeatureResponse
class HomeReducer : BaseReducer<HpStates, HpEvents> {
@@ -51,7 +52,20 @@ class HomeReducer : BaseReducer<HpStates, HpEvents> {
bottomSheets = it.bottomSheets,
systemBackCta = it.systemBackCta,
renderActions = it.renderActions,
collapsingToolbar = it.collapsingToolbar,
collapsingToolbar = it.collapsingToolbar ?: previousState.collapsingToolbar,
screenMetaData = event.content.screenMetaData,
)
} ?: previousState.copy(screenMetaData = event.content.screenMetaData)
}
is HpEvents.UpdateScreenWithoutContentAndTopNav -> {
event.content.screenStructure?.let {
previousState.copy(
header = it.header,
footer = it.footer,
bottomSheets = it.bottomSheets,
systemBackCta = it.systemBackCta,
renderActions = it.renderActions,
screenMetaData = event.content.screenMetaData,
)
} ?: previousState.copy(screenMetaData = event.content.screenMetaData)
@@ -83,6 +97,22 @@ class HomeReducer : BaseReducer<HpStates, HpEvents> {
frontLayerContent = updatedList,
)
}
is HpEvents.UpdatePrioritySectionData -> {
val updatedList =
updateScreenContent(
renderingFirstTime = previousState.isRenderingFirstTime,
newWidgets =
event.prioritySectionData.content ?: previousState.frontLayerContent,
oldWidgets = previousState.frontLayerContent,
)
previousState.copy(
isLoading = false,
isError = false,
frontLayerContent = updatedList,
collapsingToolbar =
event.prioritySectionData.topNav ?: previousState.collapsingToolbar,
)
}
is HpEvents.RenderedFirstTime -> {
previousState.copy(isRenderingFirstTime = false)
}
@@ -116,6 +146,9 @@ sealed interface HpEvents : UiEvent {
data class UpdateScreenWithoutContent(val content: AlchemistScreenDefinition) : HpEvents
data class UpdateScreenWithoutContentAndTopNav(val content: AlchemistScreenDefinition) :
HpEvents
data class UpdateShadowOnFrontLayer(val showShadowOnFrontLayer: Boolean) : HpEvents
data class UpdateScreenContentWidgetRenderState(val id: String, val state: WidgetRenderState) :
@@ -127,6 +160,9 @@ sealed interface HpEvents : UiEvent {
val content: List<AlchemistWidgetModelDefinition<UiTronResponse>>
) : HpEvents
data class UpdatePrioritySectionData(val prioritySectionData: HomePrioritySectionData) :
HpEvents
data object ShowProfile : HpEvents
data class UpdateProfileDrawerState(val state: Boolean) : HpEvents
@@ -190,4 +226,6 @@ sealed interface HpEffects : UiEffect {
data object HomePageUiRenderedEvent : HpEffects
data object TopNavRevealedAnalytics : HpEffects
data class UpdateMastheadIconsState(val alphaFactor: Int) : HpEffects
}

View File

@@ -9,10 +9,12 @@ package com.naviapp.home.usecase
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.navi.common.alchemist.model.AlchemistCollapsingToolbar
import com.navi.common.alchemist.model.AlchemistScreenDefinition
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
import com.navi.common.utils.log
import com.navi.uitron.model.UiTronResponse
import com.naviapp.home.model.HomePrioritySectionData
import com.naviapp.network.di.DataDeserializers
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
@@ -25,12 +27,18 @@ class AsyncDeserialization @Inject constructor(@DataDeserializers private val de
object : TypeToken<AlchemistWidgetModelDefinition<UiTronResponse>>() {}.type
private var contentArray: JSONArray? = null
private var topNav: JSONObject? = null
private var cacheEntity: String? = null
private var isTopNavInPrioritySection: Boolean = false
fun setCacheEntity(newCacheEntity: String) {
if (cacheEntity != newCacheEntity) {
cacheEntity = newCacheEntity
contentArray = extractFrontLayerJsonArray(cacheEntity)
extractTopNavDataAndMastheadStatus(cacheEntity).let { (nav, isMastheadEnabled) ->
topNav = nav
isTopNavInPrioritySection = isMastheadEnabled
}
}
}
@@ -40,9 +48,14 @@ class AsyncDeserialization @Inject constructor(@DataDeserializers private val de
return getContentInRange(startIndex, endIndex)
}
suspend fun getPrioritySection(): List<AlchemistWidgetModelDefinition<UiTronResponse>>? {
val endIndex = CHUNK_SIZE.coerceAtMost(contentArray?.length() ?: 0)
return getContentInRange(0, endIndex)
suspend fun getPrioritySection(): HomePrioritySectionData {
val contentLength = contentArray?.length() ?: 0
val endIndex = CHUNK_SIZE.coerceAtMost(contentLength)
val topNavData = if (isTopNavInPrioritySection) getTopNav() else null
return HomePrioritySectionData(
content = getContentInRange(0, endIndex),
topNav = topNavData,
)
}
suspend fun getScreen(cacheEntity: String): AlchemistScreenDefinition {
@@ -51,11 +64,18 @@ class AsyncDeserialization @Inject constructor(@DataDeserializers private val de
}
}
// Here content refers to content in Alchemist screen definition
/**
* Here content refers to content in Alchemist screen definition. This function avoid taking the
* priority section if masthead isEnabled. *
*/
suspend fun getScreenDefinitionWithoutContent(): AlchemistScreenDefinition? {
return withContext(Dispatchers.Default) {
val screenJson = cacheEntity?.let { JSONObject(it) }
screenJson?.optJSONObject("screenStructure")?.remove("content")
screenJson
?.optJSONObject("screenStructure")
?.takeIf { isTopNavInPrioritySection }
?.remove("collapsingToolbar")
val strippedScreenJson = screenJson.toString()
deserializer.fromJson(strippedScreenJson, AlchemistScreenDefinition::class.java)
}
@@ -74,6 +94,12 @@ class AsyncDeserialization @Inject constructor(@DataDeserializers private val de
}
}
private suspend fun getTopNav(): AlchemistCollapsingToolbar {
return withContext(Dispatchers.Default) {
deserializer.fromJson(topNav.toString(), AlchemistCollapsingToolbar::class.java)
}
}
private fun extractFrontLayerJsonArray(cacheEntity: String?): JSONArray? {
return try {
val screenJson = cacheEntity?.let { JSONObject(it) } ?: return null
@@ -87,6 +113,24 @@ class AsyncDeserialization @Inject constructor(@DataDeserializers private val de
}
}
private fun extractTopNavDataAndMastheadStatus(
cacheEntity: String?
): Pair<JSONObject?, Boolean> {
return try {
val screenJson =
cacheEntity
?.let { JSONObject(it) }
?.optJSONObject("screenStructure")
?.optJSONObject("collapsingToolbar")
val isMastheadEnabled = screenJson?.optBoolean("mastheadEnable", false) ?: false
Pair(screenJson, isMastheadEnabled)
} catch (e: Exception) {
e.log()
Pair(null, false)
}
}
companion object {
const val CHUNK_SIZE = 4
}

View File

@@ -134,6 +134,7 @@ fun HandleUitronRenderer(uiTronResponse: UiTronResponse?, viewModel: BaseVM) {
@Composable
fun WidgetRenderer(
modifier: Modifier = Modifier,
staticNudgeData: StaticNudgeData?,
sharedVM: SharedVM,
viewModel: ScreenOverlayVM,
@@ -147,6 +148,7 @@ fun WidgetRenderer(
when (widget.widgetName) {
HomeCustomWidgetType.AppUpdateNudge.value -> {
HomeAppUpdateWidget(
modifier = modifier,
content = widget,
appUpdateState = appUpdateState(),
inAppUpdateBridge = inAppUpdateBridge,
@@ -165,6 +167,7 @@ fun WidgetRenderer(
}
HomeCustomWidgetType.NotifyMeWidget.value -> {
HomeNotifyMeWidget(
modifier = modifier,
widget = widget,
showBottomSheet = {
sharedVM.updateBottomSheetState(

View File

@@ -262,9 +262,7 @@ constructor(
deserializer.setCacheEntity(data.value)
viewModelScope
.async {
deserializer.getPrioritySection()?.let {
sendEvent(HpEvents.UpdateFrontLayerContent(it))
}
sendEvent(HpEvents.UpdatePrioritySectionData(deserializer.getPrioritySection()))
}
.await()
viewModelScope

View File

@@ -270,6 +270,9 @@ object Constants {
val DRAG_POSITIONAL_THRESHOLD = 56.dp
val DRAG_VELOCITY_THRESHOLD = 125.dp
const val SCROLL_FADE = "scroll_fade"
const val SCROLL_THRESHOLD = 60f
const val TOP_NAV_ARC_BACKGROUND = "top_nav_arc_background"
const val ALPHA = "alpha_"
}
// Home Custom Widget Constants

View File

@@ -9,6 +9,7 @@ package com.navi.common.alchemist.model
import com.google.gson.annotations.SerializedName
import com.navi.common.model.common.CollapsingTopNavConfig
import com.navi.common.model.common.MastheadWidgetData
import com.navi.common.model.common.UiTronActionAndResponse
import com.navi.common.utils.Constants.DEFAULT_BACKGROUND_COLOR
import com.navi.uitron.model.UiTronResponse
@@ -64,6 +65,8 @@ data class AlchemistFloatingActionButton(
data class AlchemistCollapsingToolbar(
val toolBarNav: UiTronActionAndResponse? = null,
val collapsingTopNav: UiTronActionAndResponse? = null,
val mastheadWidget: MastheadWidgetData? = null,
val mastheadEnable: Boolean? = null,
val collapsingTopNavConfig: CollapsingTopNavConfig? = null,
)

View File

@@ -0,0 +1,16 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.common.model.common
data class MastheadWidgetData(
val widgetData: UiTronActionAndResponse? = null,
val backgroundIllustration: UiTronActionAndResponse? = null,
val useLightStatusBar: Boolean = false,
val updateIconsOnScroll: Boolean = false,
val widgetBottomPadding: Int = 16,
)

View File

@@ -235,6 +235,7 @@ object NaviWidgetIconUtils {
private const val ARROW_FORWARD_FILLED_PURPLE = "ARROW_FORWARD_FILLED_PURPLE"
private const val ARROW_TERTIARY_BLUE = "ARROW_TERTIARY_BLUE"
private const val PROFILE_ICON = "PROFILE_ICON"
private const val PROFILE_ICON_WITH_UPI = "PROFILE_ICON_WITH_UPI"
private const val YELLOW_ALERT_ICON = "YELLOW_ALERT_ICON"
private const val NEW_GREEN_TICK_ICON_LARGE = "NEW_GREEN_TICK_ICON_LARGE"
private const val RED_BG_CROSS_LARGE = "RED_BG_CROSS_LARGE"
@@ -249,6 +250,7 @@ object NaviWidgetIconUtils {
private const val EXPLORE_FUNDS_MAGNIFIER = "EXPLORE_FUNDS_MAGNIFIER"
private const val LI_ARROW_RIGHT = "LI_ARROW_RIGHT"
private const val IN_APP_NOTIFICATION_ICON = "IN_APP_NOTIFICATION_ICON"
private const val NOTIFICATION_ICON_WITH_BORDER = "NOTIFICATION_ICON_WITH_BORDER"
const val ARROW_LEFT_PURPLE = "ARROW_LEFT_PURPLE"
const val TRIANGULAR_TIP_BLUE = "TRIANGULAR_TIP_BLUE"
const val BULLET_ELLIPSE_GREY = "BULLET_ELLIPSE_GREY"
@@ -560,6 +562,7 @@ object NaviWidgetIconUtils {
ORANGE_GRADIENT_LOADER_ICON -> R.drawable.ic_orange_gradient_loader
ARROW_TERTIARY_BLUE -> R.drawable.ic_blue_chevron_right
PROFILE_ICON -> R.drawable.navi_widgets_ic_profile
PROFILE_ICON_WITH_UPI -> R.drawable.ic_profile_with_upi
YELLOW_ALERT_ICON -> R.drawable.navi_widgets_ic_yellow_alert
WHITE_TICK_GREEN_BG_60 -> R.drawable.ic_green_tick_60
ICON_SINGLE_SELECTED_V2 -> R.drawable.ic_single_selected_v2
@@ -657,6 +660,7 @@ object NaviWidgetIconUtils {
NAVI_COIN_GREY -> R.drawable.ic_navi_coin_grey
HOLIDAY_SCRATCH_CARD_IMAGE -> R.drawable.ic_christmas_sc
REPUBLIC_DAY_SCRATCH_CARD_IMAGE -> R.drawable.ic_republic_day_scratch_card
NOTIFICATION_ICON_WITH_BORDER -> R.drawable.ic_home_notification
else -> -1
}
}

View File

@@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M20,0.556L20,0.556A19.444,19.444 0,0 1,39.444 20L39.444,20A19.444,19.444 0,0 1,20 39.444L20,39.444A19.444,19.444 0,0 1,0.556 20L0.556,20A19.444,19.444 0,0 1,20 0.556z"
android:strokeWidth="1.11111"
android:fillColor="#ffffff"
android:strokeColor="#EBEBEB"/>
<path
android:pathData="M13.768,17.409C13.768,14.244 16.059,11.413 19.236,11.044C23.011,10.607 26.222,13.507 26.222,17.156V18.871C26.222,20.448 26.579,21.99 27.281,23.406L27.926,24.73C28.363,25.616 27.707,26.663 26.705,26.663H13.308C12.307,26.663 11.639,25.616 12.088,24.73L12.732,23.406C13.423,21.99 13.791,20.436 13.791,18.871V17.409H13.768Z"
android:strokeLineJoin="round"
android:strokeWidth="1.4"
android:fillColor="#00000000"
android:strokeColor="#1F002A"
android:strokeLineCap="round"/>
<path
android:pathData="M17.278,26.653C17.474,27.988 18.614,29.001 20.006,29.001C21.399,29.001 22.527,27.988 22.722,26.653"
android:strokeLineJoin="round"
android:strokeWidth="1.4"
android:fillColor="#00000000"
android:strokeColor="#1F002A"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,118 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:strokeWidth="1"
android:pathData="M20,0.5L20,0.5A19.5,19.5 0,0 1,39.5 20L39.5,20A19.5,19.5 0,0 1,20 39.5L20,39.5A19.5,19.5 0,0 1,0.5 20L0.5,20A19.5,19.5 0,0 1,20 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#EBEBEB"/>
<path
android:pathData="M19.993,12.047C21.34,12.047 22.432,10.955 22.432,9.608C22.432,8.262 21.34,7.17 19.993,7.17C18.646,7.17 17.555,8.262 17.555,9.608C17.555,10.955 18.646,12.047 19.993,12.047Z"
android:strokeLineJoin="round"
android:strokeWidth="1.2"
android:fillColor="#00000000"
android:strokeColor="#1F002A"/>
<path
android:pathData="M25.976,18.205V19.527C25.976,20.069 25.542,20.503 25,20.503H14.997C14.461,20.503 14.022,20.069 14.022,19.527V18.205C14.022,16.001 15.67,14.616 17.738,14.045C19.177,13.65 20.821,13.65 22.264,14.045C22.294,14.055 22.328,14.065 22.357,14.075C24.381,14.66 25.976,16.035 25.976,18.205Z"
android:strokeLineJoin="round"
android:strokeWidth="1.2"
android:fillColor="#00000000"
android:strokeColor="#1F002A"/>
<group>
<clip-path
android:pathData="M8.779,24.111h21.859v7.715h-21.859z"/>
<group>
<clip-path
android:pathData="M6.952,21.594H32.472V34.342H6.952V21.594Z"/>
<path
android:pathData="M8.958,30.738H9.122L8.97,31.373C8.947,31.467 8.951,31.538 8.983,31.585C9.014,31.632 9.071,31.656 9.156,31.656C9.24,31.656 9.309,31.632 9.363,31.585C9.417,31.538 9.455,31.467 9.477,31.373L9.629,30.738H9.795L9.639,31.389C9.605,31.53 9.545,31.636 9.459,31.706C9.372,31.776 9.259,31.811 9.119,31.811C8.979,31.811 8.882,31.776 8.83,31.706C8.777,31.636 8.768,31.53 8.802,31.389L8.958,30.738Z"
android:fillColor="#434244"/>
<path
android:pathData="M9.773,31.783L10.033,30.697L10.528,31.336C10.541,31.354 10.554,31.373 10.568,31.393C10.581,31.414 10.594,31.437 10.608,31.463L10.782,30.738H10.935L10.675,31.824L10.17,31.174C10.156,31.156 10.144,31.138 10.132,31.118C10.12,31.098 10.109,31.078 10.099,31.057L9.925,31.783H9.773Z"
android:fillColor="#434244"/>
<path
android:pathData="M10.921,31.783L11.172,30.738H11.337L11.087,31.783H10.921Z"
android:fillColor="#434244"/>
<path
android:pathData="M11.325,31.783L11.576,30.738H12.144L12.11,30.882H11.707L11.644,31.143H12.047L12.012,31.292H11.608L11.491,31.783H11.325Z"
android:fillColor="#434244"/>
<path
android:pathData="M12.078,31.783L12.328,30.738H12.494L12.243,31.783H12.078Z"
android:fillColor="#434244"/>
<path
android:pathData="M12.481,31.783L12.731,30.738H13.3L13.266,30.882H12.863L12.8,31.144H13.203L13.167,31.294H12.764L12.683,31.631H13.086L13.05,31.783H12.481Z"
android:fillColor="#434244"/>
<path
android:pathData="M13.443,31.63H13.582C13.658,31.63 13.717,31.624 13.758,31.614C13.799,31.604 13.836,31.586 13.871,31.561C13.917,31.528 13.956,31.486 13.988,31.435C14.019,31.385 14.042,31.327 14.058,31.26C14.074,31.193 14.079,31.135 14.071,31.085C14.064,31.035 14.045,30.993 14.015,30.959C13.992,30.934 13.962,30.917 13.924,30.906C13.887,30.896 13.825,30.89 13.74,30.89H13.68H13.62L13.443,31.63ZM13.24,31.783L13.491,30.738H13.714C13.859,30.738 13.96,30.746 14.016,30.761C14.071,30.776 14.117,30.801 14.153,30.836C14.201,30.882 14.231,30.941 14.245,31.014C14.257,31.086 14.253,31.169 14.231,31.261C14.208,31.354 14.174,31.436 14.126,31.508C14.078,31.58 14.02,31.639 13.95,31.685C13.896,31.721 13.84,31.746 13.779,31.761C13.719,31.776 13.628,31.783 13.508,31.783H13.464H13.24Z"
android:fillColor="#434244"/>
<path
android:pathData="M15.038,31.175H15.081C15.173,31.175 15.237,31.165 15.272,31.144C15.308,31.124 15.332,31.088 15.344,31.036C15.357,30.98 15.351,30.941 15.323,30.918C15.296,30.895 15.238,30.884 15.151,30.884H15.108L15.038,31.175ZM14.995,31.314L14.882,31.783H14.727L14.977,30.738H15.228C15.302,30.738 15.356,30.743 15.389,30.751C15.422,30.759 15.449,30.773 15.471,30.792C15.497,30.816 15.514,30.849 15.523,30.89C15.531,30.931 15.529,30.976 15.517,31.026C15.505,31.075 15.485,31.121 15.457,31.163C15.429,31.205 15.396,31.238 15.358,31.261C15.328,31.281 15.294,31.294 15.257,31.302C15.22,31.31 15.164,31.314 15.09,31.314H15.057H14.995Z"
android:fillColor="#434244"/>
<path
android:pathData="M15.72,31.373H16.004L15.956,31.134C15.954,31.119 15.952,31.102 15.95,31.082C15.948,31.062 15.947,31.04 15.946,31.016C15.935,31.039 15.924,31.06 15.913,31.08C15.902,31.1 15.892,31.118 15.881,31.134L15.72,31.373ZM16.08,31.783L16.028,31.514H15.624L15.442,31.783H15.268L16.021,30.697L16.254,31.783H16.08Z"
android:fillColor="#434244"/>
<path
android:pathData="M16.493,31.783L16.607,31.305L16.4,30.738H16.574L16.703,31.093C16.706,31.102 16.709,31.114 16.713,31.129C16.718,31.143 16.722,31.159 16.725,31.177C16.736,31.16 16.746,31.144 16.757,31.129C16.768,31.115 16.779,31.101 16.789,31.089L17.093,30.738H17.258L16.772,31.305L16.657,31.783H16.493Z"
android:fillColor="#434244"/>
<path
android:pathData="M18.074,31.267C18.074,31.259 18.076,31.237 18.081,31.2C18.084,31.17 18.087,31.144 18.089,31.124C18.079,31.148 18.068,31.172 18.054,31.196C18.041,31.219 18.025,31.244 18.008,31.268L17.608,31.825L17.477,31.257C17.472,31.234 17.468,31.211 17.465,31.189C17.462,31.167 17.46,31.146 17.459,31.124C17.453,31.146 17.446,31.169 17.437,31.193C17.428,31.217 17.418,31.242 17.406,31.268L17.173,31.783H17.021L17.522,30.694L17.665,31.353C17.667,31.364 17.67,31.381 17.674,31.406C17.678,31.43 17.682,31.46 17.687,31.495C17.704,31.465 17.728,31.427 17.76,31.381C17.769,31.369 17.775,31.359 17.779,31.353L18.231,30.694L18.217,31.783H18.063L18.074,31.267Z"
android:fillColor="#434244"/>
<path
android:pathData="M18.348,31.783L18.599,30.738H19.167L19.133,30.882H18.73L18.667,31.144H19.07L19.034,31.294H18.631L18.55,31.631H18.954L18.917,31.783H18.348Z"
android:fillColor="#434244"/>
<path
android:pathData="M19.108,31.783L19.368,30.697L19.863,31.336C19.876,31.354 19.889,31.373 19.903,31.393C19.916,31.414 19.929,31.437 19.943,31.463L20.117,30.738H20.27L20.01,31.824L19.505,31.174C19.491,31.156 19.479,31.138 19.467,31.118C19.455,31.098 19.444,31.078 19.434,31.057L19.26,31.783H19.108Z"
android:fillColor="#434244"/>
<path
android:pathData="M20.799,30.882L20.584,31.783H20.418L20.634,30.882H20.363L20.397,30.738H21.103L21.069,30.882H20.799Z"
android:fillColor="#434244"/>
<path
android:pathData="M20.97,31.574L21.118,31.511C21.119,31.558 21.133,31.594 21.161,31.618C21.188,31.642 21.228,31.655 21.28,31.655C21.33,31.655 21.372,31.641 21.408,31.613C21.444,31.585 21.467,31.547 21.478,31.5C21.493,31.438 21.456,31.384 21.369,31.336C21.356,31.329 21.347,31.323 21.34,31.32C21.242,31.264 21.179,31.213 21.152,31.168C21.126,31.122 21.12,31.067 21.136,31.001C21.156,30.916 21.201,30.847 21.27,30.794C21.339,30.741 21.42,30.715 21.511,30.715C21.586,30.715 21.646,30.73 21.689,30.759C21.733,30.789 21.757,30.832 21.764,30.888L21.618,30.956C21.605,30.923 21.588,30.899 21.568,30.884C21.547,30.869 21.521,30.861 21.489,30.861C21.445,30.861 21.406,30.873 21.375,30.897C21.343,30.921 21.322,30.952 21.312,30.993C21.297,31.055 21.341,31.114 21.443,31.168C21.451,31.172 21.457,31.176 21.461,31.178C21.551,31.226 21.609,31.272 21.635,31.317C21.662,31.363 21.667,31.42 21.651,31.488C21.627,31.587 21.578,31.665 21.503,31.724C21.429,31.782 21.339,31.811 21.236,31.811C21.149,31.811 21.083,31.791 21.037,31.75C20.992,31.709 20.969,31.65 20.97,31.574Z"
android:fillColor="#434244"/>
<path
android:pathData="M22.194,31.783L22.445,30.738H22.61L22.36,31.783H22.194Z"
android:fillColor="#434244"/>
<path
android:pathData="M22.6,31.783L22.86,30.697L23.355,31.336C23.368,31.354 23.382,31.373 23.395,31.393C23.408,31.414 23.421,31.437 23.435,31.463L23.609,30.738H23.762L23.502,31.824L22.997,31.174C22.983,31.156 22.971,31.138 22.959,31.118C22.947,31.098 22.936,31.078 22.927,31.057L22.753,31.783H22.6Z"
android:fillColor="#434244"/>
<path
android:pathData="M24.29,30.882L24.074,31.783H23.909L24.124,30.882H23.854L23.888,30.738H24.594L24.56,30.882H24.29Z"
android:fillColor="#434244"/>
<path
android:pathData="M24.47,31.783L24.72,30.738H25.289L25.254,30.882H24.851L24.788,31.144H25.192L25.156,31.294H24.753L24.672,31.631H25.075L25.039,31.783H24.47Z"
android:fillColor="#434244"/>
<path
android:pathData="M25.538,31.184H25.568C25.656,31.184 25.717,31.173 25.751,31.152C25.784,31.131 25.808,31.094 25.82,31.041C25.834,30.983 25.829,30.943 25.803,30.919C25.778,30.896 25.724,30.884 25.64,30.884H25.61L25.538,31.184ZM25.496,31.316L25.384,31.783H25.229L25.479,30.738H25.711C25.778,30.738 25.83,30.743 25.865,30.752C25.9,30.761 25.929,30.776 25.95,30.797C25.976,30.822 25.992,30.855 26,30.895C26.007,30.935 26.005,30.978 25.994,31.026C25.974,31.11 25.938,31.175 25.888,31.223C25.837,31.27 25.772,31.298 25.692,31.308L25.93,31.783H25.742L25.515,31.316H25.496Z"
android:fillColor="#434244"/>
<path
android:pathData="M26.029,31.783L26.279,30.738H26.848L26.813,30.882H26.41L26.348,31.143H26.751L26.715,31.292H26.312L26.194,31.783H26.029Z"
android:fillColor="#434244"/>
<path
android:pathData="M27.038,31.373H27.323L27.275,31.134C27.273,31.119 27.271,31.102 27.269,31.082C27.267,31.062 27.266,31.04 27.265,31.016C27.254,31.039 27.243,31.06 27.232,31.08C27.221,31.1 27.211,31.118 27.2,31.134L27.038,31.373ZM27.398,31.783L27.347,31.514H26.943L26.76,31.783H26.586L27.34,30.697L27.573,31.783H27.398Z"
android:fillColor="#434244"/>
<path
android:pathData="M28.653,30.993C28.616,30.952 28.574,30.921 28.527,30.9C28.479,30.88 28.427,30.87 28.369,30.87C28.258,30.87 28.158,30.906 28.071,30.98C27.983,31.053 27.926,31.148 27.898,31.264C27.871,31.377 27.883,31.47 27.933,31.543C27.983,31.617 28.061,31.653 28.164,31.653C28.224,31.653 28.285,31.642 28.344,31.621C28.404,31.599 28.464,31.566 28.525,31.521L28.479,31.713C28.427,31.746 28.372,31.771 28.316,31.787C28.26,31.803 28.201,31.811 28.139,31.811C28.062,31.811 27.993,31.798 27.933,31.772C27.873,31.746 27.824,31.707 27.786,31.656C27.748,31.606 27.724,31.547 27.714,31.479C27.703,31.412 27.707,31.34 27.726,31.263C27.744,31.186 27.774,31.114 27.817,31.047C27.86,30.98 27.913,30.921 27.976,30.87C28.039,30.818 28.106,30.779 28.178,30.753C28.25,30.727 28.324,30.713 28.4,30.713C28.46,30.713 28.514,30.722 28.565,30.74C28.615,30.758 28.661,30.785 28.703,30.821L28.653,30.993Z"
android:fillColor="#434244"/>
<path
android:pathData="M28.669,31.783L28.919,30.738H29.488L29.453,30.882H29.05L28.987,31.144H29.39L29.355,31.294H28.951L28.871,31.631H29.274L29.237,31.783H28.669Z"
android:fillColor="#434244"/>
<path
android:pathData="M25.645,29.828H24.505L26.09,24.112H27.229L25.645,29.828Z"
android:fillColor="#66686C"/>
<path
android:pathData="M25.053,24.292C24.974,24.184 24.853,24.129 24.687,24.129H18.423L18.113,25.248H19.253V25.248H23.811L23.48,26.444H18.921L18.921,26.442H17.782L16.836,29.852H17.976L18.611,27.563H23.735C23.895,27.563 24.046,27.508 24.188,27.399C24.329,27.29 24.422,27.156 24.467,26.995L25.101,24.705C25.147,24.539 25.132,24.401 25.053,24.292Z"
android:fillColor="#66686C"/>
<path
android:pathData="M16.174,29.471C16.111,29.697 15.905,29.854 15.67,29.854H9.792C9.632,29.854 9.513,29.799 9.435,29.69C9.357,29.581 9.34,29.446 9.385,29.285L10.819,24.128H11.959L10.678,28.734H15.239L16.52,24.128H17.66L16.174,29.471Z"
android:fillColor="#66686C"/>
<path
android:pathData="M29.198,24.122L30.64,26.987L27.608,29.852L29.198,24.122Z"
android:fillColor="#27803B"/>
<path
android:pathData="M28.187,24.122L29.628,26.987L26.595,29.852L28.187,24.122Z"
android:fillColor="#E9661C"/>
</group>
</group>
</vector>