From c97429db0e32fecb2b0b2486a845f984451d1edc Mon Sep 17 00:00:00 2001 From: Aparna Vadlamani Date: Fri, 6 Dec 2024 18:51:03 +0530 Subject: [PATCH] NTP-8836 New App Update for homepage UAT changes (#14023) --- .../model/ProfileAppUpdateSettingData.kt | 9 +- .../profile/ProfileAppUpdateNudgeWidget.kt | 104 ++++------------- .../profile/ProfileCustomUiTronRenderer.kt | 3 + .../home/compose/profile/ProfileScreen.kt | 63 ++++++----- .../profile/ProfileScreenWidgetRenderer.kt | 4 +- .../home/model/CustomDismissibleToastState.kt | 38 +++++++ .../naviapp/home/ui/CustomDismissibleToast.kt | 107 ++++++++++++++++++ 7 files changed, 212 insertions(+), 116 deletions(-) create mode 100644 android/app/src/main/java/com/naviapp/home/model/CustomDismissibleToastState.kt create mode 100644 android/app/src/main/java/com/naviapp/home/ui/CustomDismissibleToast.kt diff --git a/android/app/src/main/java/com/naviapp/home/compose/model/ProfileAppUpdateSettingData.kt b/android/app/src/main/java/com/naviapp/home/compose/model/ProfileAppUpdateSettingData.kt index cb856f9525..1ed390f434 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/model/ProfileAppUpdateSettingData.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/model/ProfileAppUpdateSettingData.kt @@ -9,19 +9,14 @@ package com.naviapp.home.compose.model import com.navi.uitron.model.data.UiTronData import com.navi.uitron.model.ui.BaseProperty +import com.naviapp.home.model.ToastData data class ProfileAppUpdateSettingData( val leftIconUrl: String? = null, val titleText: String? = null, val updateCtaText: String? = null, val installCtaText: String? = null, - val toastData: AppUpdateToastData? = null + val toastData: ToastData? = null ) : UiTronData() -data class AppUpdateToastData( - val message: String? = null, - val leftIconUrl: String? = null, - val dismissIconUrl: String? = null -) - class ProfileAppUpdateSettingProperty : BaseProperty() diff --git a/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileAppUpdateNudgeWidget.kt b/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileAppUpdateNudgeWidget.kt index 8e817769d1..43cf61bbcf 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileAppUpdateNudgeWidget.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileAppUpdateNudgeWidget.kt @@ -7,8 +7,6 @@ package com.naviapp.home.compose.profile -import android.content.Context -import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -24,18 +22,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.setViewTreeLifecycleOwner -import androidx.savedstate.SavedStateRegistryOwner -import androidx.savedstate.setViewTreeSavedStateRegistryOwner import com.navi.common.utils.Constants.PROFILE_SCREEN import com.navi.elex.atoms.ElexAsyncImage import com.navi.elex.atoms.ElexText @@ -44,33 +34,41 @@ import com.naviapp.R import com.naviapp.analytics.utils.NaviAnalytics import com.naviapp.common.listeners.InAppUpdateBridge import com.naviapp.common.model.AppUpdateState -import com.naviapp.home.compose.model.AppUpdateToastData import com.naviapp.home.compose.model.ProfileAppUpdateSettingData +import com.naviapp.home.model.CustomDismissibleToastState @Composable fun ProfileAppUpdateWidget( + state: CustomDismissibleToastState, data: ProfileAppUpdateSettingData, appUpdateState: AppUpdateState, inAppUpdateBridge: InAppUpdateBridge, appUpdateAnalytics: NaviAnalytics.InAppUpdate = NaviAnalytics.naviAnalytics.InAppUpdate(PROFILE_SCREEN) ) { - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() - .clickable(enabled = (appUpdateState == AppUpdateState.UPDATE_NOT_AVAILABLE)) { - appUpdateAnalytics.appUpdateUnavailableClickEvent() - showToast( - content = { toast: Toast? -> AppUpdateToastUI(data.toastData, toast) }, - context = context, - lifecycleOwner = lifecycleOwner, - savedStateRegistryOwner = savedStateRegistryOwner - ) + .clickable(enabled = (appUpdateState != AppUpdateState.UPDATE_DOWNLOADING)) { + when (appUpdateState) { + AppUpdateState.UPDATE_AVAILABLE, + AppUpdateState.UPDATE_FAILED, + AppUpdateState.UPDATE_PENDING -> { + appUpdateAnalytics.appUpdateNudgeClickEvent() + inAppUpdateBridge.downloadUpdate() + } + AppUpdateState.UPDATE_DOWNLOADED -> { + appUpdateAnalytics.appInstallNudgeClickEvent() + inAppUpdateBridge.installUpdate() + } + AppUpdateState.UPDATE_NOT_AVAILABLE -> { + appUpdateAnalytics.appUpdateUnavailableClickEvent() + data.toastData?.let { state.showToast(it) } + } + AppUpdateState.UPDATE_DOWNLOADING -> {} + } } .padding(start = 16.dp, top = 12.dp, bottom = 12.dp, end = 16.dp) ) { @@ -98,10 +96,7 @@ fun ProfileAppUpdateWidget( AppUpdateState.UPDATE_AVAILABLE, AppUpdateState.UPDATE_FAILED, AppUpdateState.UPDATE_PENDING -> { - UpdateButton(text = data.updateCtaText.orEmpty()) { - appUpdateAnalytics.appUpdateNudgeClickEvent() - inAppUpdateBridge.downloadUpdate() - } + UpdateButton(text = data.updateCtaText.orEmpty()) } AppUpdateState.UPDATE_DOWNLOADING -> { ElexText( @@ -114,10 +109,7 @@ fun ProfileAppUpdateWidget( ) } AppUpdateState.UPDATE_DOWNLOADED -> { - UpdateButton(text = data.installCtaText.orEmpty()) { - appUpdateAnalytics.appInstallNudgeClickEvent() - inAppUpdateBridge.installUpdate() - } + UpdateButton(text = data.installCtaText.orEmpty()) } AppUpdateState.UPDATE_NOT_AVAILABLE -> {} } @@ -125,14 +117,13 @@ fun ProfileAppUpdateWidget( } @Composable -private fun UpdateButton(text: String, onClick: () -> Unit) { +private fun UpdateButton(text: String) { Row( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clip(RoundedCornerShape(4.dp)) .background(color = Color(0xFFF5F5F5), shape = RoundedCornerShape(4.dp)) - .clickable { onClick() } .padding(horizontal = 16.dp, vertical = 8.dp) ) { ElexText( @@ -145,52 +136,3 @@ private fun UpdateButton(text: String, onClick: () -> Unit) { ) } } - -private fun showToast( - content: @Composable (toast: Toast?) -> Unit, - context: Context, - lifecycleOwner: LifecycleOwner, - savedStateRegistryOwner: SavedStateRegistryOwner -) { - var toast: Toast? = null - toast = - Toast(context).apply { - duration = Toast.LENGTH_SHORT - val views = ComposeView(context).apply { setContent { content(toast) } } - views.setViewTreeLifecycleOwner(lifecycleOwner) - views.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner) - view = views - } - toast.show() -} - -@Composable -private fun AppUpdateToastUI(data: AppUpdateToastData?, toast: Toast?) { - Row( - modifier = - Modifier.background(color = Color(0xFFEDECFF), shape = RoundedCornerShape(4.dp)) - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - ElexAsyncImage( - icon = data?.leftIconUrl, - contentDescription = "", - modifier = Modifier.height(16.dp).width(16.dp) - ) - ElexText( - text = data?.message.orEmpty(), - color = Color(0xFF191919), - fontSize = 14.sp, - fontWeight = FontWeightEnum.NAVI_HEADLINE_REGULAR, - lineHeight = 22.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - ElexAsyncImage( - icon = data?.dismissIconUrl, - contentDescription = "", - modifier = Modifier.height(16.dp).width(16.dp).clickable { toast?.cancel() } - ) - } -} diff --git a/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileCustomUiTronRenderer.kt b/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileCustomUiTronRenderer.kt index 098fc101f9..ad44c7dabb 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileCustomUiTronRenderer.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileCustomUiTronRenderer.kt @@ -17,9 +17,11 @@ import com.navi.uitron.viewmodel.UiTronViewModel import com.naviapp.common.listeners.InAppUpdateBridge import com.naviapp.common.model.AppUpdateState import com.naviapp.home.compose.model.ProfileAppUpdateSettingData +import com.naviapp.home.model.CustomDismissibleToastState import com.naviapp.home.model.HomeCustomWidgetType class ProfileCustomUiTronRenderer( + private val state: CustomDismissibleToastState, private val appUpdateState: AppUpdateState, private val inAppUpdateBridge: InAppUpdateBridge, ) : CommonCustomUiTronRenderer() { @@ -38,6 +40,7 @@ class ProfileCustomUiTronRenderer( dataMap?.getOrElse(APP_UPDATE_DATA) { null } as? ProfileAppUpdateSettingData if (data != null) { ProfileAppUpdateWidget( + state = state, data = data, appUpdateState = appUpdateState, inAppUpdateBridge = inAppUpdateBridge diff --git a/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileScreen.kt b/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileScreen.kt index aaa756aa55..20072320b1 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileScreen.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -50,6 +51,8 @@ import com.naviapp.analytics.utils.NaviAnalytics import com.naviapp.forge.model.WidgetModelDefinition import com.naviapp.home.compose.activity.HomePageActivity import com.naviapp.home.compose.model.ProfileHeaderWidgetData +import com.naviapp.home.model.CustomDismissibleToastState +import com.naviapp.home.ui.CustomDismissibleToast import com.naviapp.home.ui.state.ProfileScreenState import com.naviapp.home.viewmodel.ProfileVM import com.naviapp.home.viewmodel.SharedVM @@ -63,6 +66,7 @@ fun ProfileScreen( activity: HomePageActivity, sharedVM: SharedVM ) { + val state = remember { CustomDismissibleToastState() } val profileItems = profileVM.profileScreenDataState.collectAsStateWithLifecycle() val appUpdateState by sharedVM.appUpdateState.collectAsStateWithLifecycle() val scrollState = rememberScrollState() @@ -87,42 +91,47 @@ fun ProfileScreen( } is ProfileScreenState.Success -> { CompositionLocalProvider(LocalOverscrollConfiguration provides null) { - Column( - modifier = - Modifier.fillMaxSize() - .background( - profileData.data.screenStructure - ?.content - ?.backgroundColor - ?.hexToComposeColor ?: Color.White - ) - .navigationBarsPadding() - ) { - // header widget - profileData.data.screenStructure?.header?.let { - ProfileScreenWidgetRenderer( - widget = it, - viewModel = profileVM, - drawerState = drawerState, - appUpdateState = appUpdateState, - inAppUpdateBridge = activity - ) - } - - // content widgets - Content( - scrollState = { scrollState }, + Box(Modifier.fillMaxSize().navigationBarsPadding()) { + Column( + modifier = + Modifier.fillMaxSize() + .background( + profileData.data.screenStructure + ?.content + ?.backgroundColor + ?.hexToComposeColor ?: Color.White + ) ) { - profileData.data.screenStructure?.content?.widgets?.forEach { widget -> + // header widget + profileData.data.screenStructure?.header?.let { ProfileScreenWidgetRenderer( - widget = widget, + state = state, + widget = it, viewModel = profileVM, drawerState = drawerState, appUpdateState = appUpdateState, inAppUpdateBridge = activity ) } + + // content widgets + Content( + scrollState = { scrollState }, + ) { + profileData.data.screenStructure?.content?.widgets?.forEach { widget + -> + ProfileScreenWidgetRenderer( + state = state, + widget = widget, + viewModel = profileVM, + drawerState = drawerState, + appUpdateState = appUpdateState, + inAppUpdateBridge = activity + ) + } + } } + CustomDismissibleToast(state) } } } diff --git a/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileScreenWidgetRenderer.kt b/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileScreenWidgetRenderer.kt index c67bf7a0fa..6acdcb6b79 100644 --- a/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileScreenWidgetRenderer.kt +++ b/android/app/src/main/java/com/naviapp/home/compose/profile/ProfileScreenWidgetRenderer.kt @@ -17,12 +17,14 @@ import com.naviapp.common.listeners.InAppUpdateBridge import com.naviapp.common.model.AppUpdateState import com.naviapp.forge.model.WidgetModelDefinition import com.naviapp.forge.model.WidgetTypes +import com.naviapp.home.model.CustomDismissibleToastState import com.naviapp.home.model.HomeCustomWidgetType import com.naviapp.home.viewmodel.ProfileVM @OptIn(ExperimentalMaterial3Api::class) @Composable fun ProfileScreenWidgetRenderer( + state: CustomDismissibleToastState, widget: WidgetModelDefinition?, viewModel: ProfileVM, drawerState: () -> DrawerState, @@ -36,7 +38,7 @@ fun ProfileScreenWidgetRenderer( dataMap = widget.widgetData?.data, uiTronViewModel = viewModel, customUiTronRenderer = - ProfileCustomUiTronRenderer(appUpdateState, inAppUpdateBridge) + ProfileCustomUiTronRenderer(state, appUpdateState, inAppUpdateBridge) ) .Render(composeViews = widget.widgetData?.parentComposeView.orEmpty()) } diff --git a/android/app/src/main/java/com/naviapp/home/model/CustomDismissibleToastState.kt b/android/app/src/main/java/com/naviapp/home/model/CustomDismissibleToastState.kt new file mode 100644 index 0000000000..7402c29616 --- /dev/null +++ b/android/app/src/main/java/com/naviapp/home/model/CustomDismissibleToastState.kt @@ -0,0 +1,38 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.naviapp.home.model + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +class CustomDismissibleToastState { + + var updateState by mutableStateOf(false) + private set + + private val _toastData = mutableStateOf(null) + val toastData: MutableState = _toastData + + fun showToast(toastData: ToastData) { + _toastData.value = toastData + updateState = !updateState + } + + fun isNotEmpty(): Boolean { + return _toastData.value != null + } +} + +data class ToastData( + val message: String? = null, + val leftIconUrl: String? = null, + val dismissIconUrl: String? = null, + val backgroundColor: String? = null +) diff --git a/android/app/src/main/java/com/naviapp/home/ui/CustomDismissibleToast.kt b/android/app/src/main/java/com/naviapp/home/ui/CustomDismissibleToast.kt new file mode 100644 index 0000000000..bbce371faf --- /dev/null +++ b/android/app/src/main/java/com/naviapp/home/ui/CustomDismissibleToast.kt @@ -0,0 +1,107 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.naviapp.home.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.navi.elex.atoms.ElexAsyncImage +import com.navi.elex.atoms.ElexText +import com.navi.elex.font.FontWeightEnum +import com.navi.uitron.utils.hexToComposeColor +import com.naviapp.home.model.CustomDismissibleToastState +import java.util.Timer +import kotlin.concurrent.schedule + +@Composable +fun CustomDismissibleToast(state: CustomDismissibleToastState) { + val showToast = remember { mutableStateOf(false) } + val data by rememberUpdatedState(newValue = state.toastData) + val timer = Timer("Animation Timer", true) + DisposableEffect(key1 = state.updateState) { + showToast.value = true + timer.schedule(3000L) { showToast.value = false } + onDispose { + timer.cancel() + timer.purge() + } + } + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ) { + AnimatedVisibility( + visible = state.isNotEmpty() && showToast.value, + enter = fadeIn(), + exit = fadeOut() + ) { + Row( + modifier = + Modifier.padding(start = 16.dp, end = 16.dp, bottom = 32.dp) + .background( + color = + data.value?.backgroundColor?.hexToComposeColor ?: Color.Black, + shape = RoundedCornerShape(4.dp) + ) + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + ElexAsyncImage( + icon = data.value?.leftIconUrl, + contentDescription = "", + modifier = Modifier.size(16.dp) + ) + ElexText( + text = data.value?.message.orEmpty(), + color = Color(0xFF191919), + fontSize = 14.sp, + fontWeight = FontWeightEnum.NAVI_HEADLINE_REGULAR, + lineHeight = 22.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + ElexAsyncImage( + icon = data.value?.dismissIconUrl, + contentDescription = "", + modifier = + Modifier.size(16.dp).clickable { + showToast.value = false + timer.cancel() + timer.purge() + } + ) + } + } + } + } +}