NTP-43715 | Nudge V2 (#15256)
Signed-off-by: Naman Khurmi <naman.khurmi@navi.com>
This commit is contained in:
@@ -174,7 +174,7 @@ import com.naviapp.registration.helper.isReadSmsPermissionGranted
|
||||
import com.naviapp.registration.viewmodel.RegistrationVM
|
||||
import com.naviapp.registration.viewmodel.UploadUserDataUseCase
|
||||
import com.naviapp.screenOverlay.handler.ScreenOverlayEffectHandler
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.popup.model.PopupEffect
|
||||
import com.naviapp.screenOverlay.popup.model.PopupEvent
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
@@ -378,8 +378,8 @@ class HomePageActivity :
|
||||
homeVM.sendEvent(HpEvents.UpdateProfileDrawerState(false))
|
||||
} else if (screenOverlayVM.popupState.value.isPopupListVisible) {
|
||||
handlePopupDismissal()
|
||||
} else if (screenOverlayVM.nudgeState.value.isNudgeExpanded) {
|
||||
screenOverlayVM.sendEvent(NudgeEvent.UpdateNudgeExpandedState(expandState = false))
|
||||
} else if (screenOverlayVM.nudgeState.value.descriptiveViewEnabled) {
|
||||
screenOverlayVM.sendEvent(NudgeEvent.ToggleDescriptiveView(enabled = false))
|
||||
} else if (selectedTabId == BottomBarTabType.HOME.name) {
|
||||
TemporaryStorageHelper.homePageBackPressed = true
|
||||
super.onBackPressed()
|
||||
@@ -487,9 +487,9 @@ class HomePageActivity :
|
||||
|
||||
private fun observeScreenOverlayEffect() {
|
||||
lifecycleScope.launch {
|
||||
screenOverlayVM.nudgeEffect.collect {
|
||||
screenOverlayVM.nudgeEffect.collect { effect ->
|
||||
screenOverlayEffectHandler.handleNudgeEffect(
|
||||
effect = it,
|
||||
effect = effect,
|
||||
deletedItemsMap = screenOverlayVM.deletedItemsMap,
|
||||
triggerStateUpdateApiCall = { nudgeTransitionState ->
|
||||
screenOverlayVM.triggerStateUpdateApiCall(
|
||||
@@ -497,6 +497,12 @@ class HomePageActivity :
|
||||
naeScreenName = screenName,
|
||||
)
|
||||
},
|
||||
triggerActionUpdateApiCall = { nudgeActions ->
|
||||
screenOverlayVM.triggerActionUpdateApiCall(
|
||||
nudgeActions,
|
||||
naeScreenName = screenName,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -30,7 +30,7 @@ import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -31,10 +31,10 @@ import com.naviapp.home.compose.home.utils.updateBottomOverlayBounds
|
||||
import com.naviapp.home.model.BottomBarTabType
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.ui.NudgeContainer
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.ui.container.NudgeContainer
|
||||
|
||||
@Composable
|
||||
fun HomeFooterRoot(
|
||||
@@ -46,7 +46,7 @@ fun HomeFooterRoot(
|
||||
navController: NavHostController,
|
||||
selectedTabId: String,
|
||||
nudgeState: () -> NudgeState,
|
||||
nudgeUitronRenderer: @Composable (UiTronResponse?) -> Unit,
|
||||
nudgeUitronRenderer: @Composable (response: UiTronResponse?, modifier: Modifier) -> Unit,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
) {
|
||||
@@ -85,7 +85,7 @@ fun HomeFooter(
|
||||
bottomNudgeData: BottomStickyNudgeData? = null,
|
||||
state: HomeFooterStates,
|
||||
nudgeState: () -> NudgeState,
|
||||
nudgeUitronRenderer: @Composable (UiTronResponse?) -> Unit,
|
||||
nudgeUitronRenderer: @Composable (response: UiTronResponse?, modifier: Modifier) -> Unit,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
onFooterEvent: (event: HomeFooterEvents) -> Unit,
|
||||
@@ -134,7 +134,7 @@ fun HomeFooter(
|
||||
bottomNavBarState = { state.bottomNavBarState },
|
||||
onTabSelected = { tabId ->
|
||||
onFooterEvent(HomeFooterEvents.BottomBarOnTabClick(tabId))
|
||||
onNudgeEvent(NudgeEvent.UpdateNudgeExpandedState(expandState = false))
|
||||
onNudgeEvent(NudgeEvent.ToggleDescriptiveView(enabled = false))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ import com.naviapp.home.viewmodel.ProfileVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.model.OverlayItemActionData
|
||||
import com.naviapp.screenOverlay.model.OverlayItemStateUpdate
|
||||
import com.naviapp.screenOverlay.nudge.utils.getOverlayItemAction
|
||||
import com.naviapp.screenOverlay.nudge.utils.getOverlayItemTransitionState
|
||||
import com.naviapp.screenOverlay.nudge.utils.mapper.mapToTransitionState
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@Composable
|
||||
@@ -130,7 +129,7 @@ fun handleBottomSheetAction(
|
||||
action.states.map {
|
||||
OverlayItemStateUpdate(
|
||||
nudgeId = it.nudgeId,
|
||||
state = getOverlayItemTransitionState(overlayItemState = it.state),
|
||||
state = mapToTransitionState(overlayItemState = it.state),
|
||||
)
|
||||
}
|
||||
screenOverlayVM.triggerStateUpdateApiCall(
|
||||
@@ -141,10 +140,7 @@ fun handleBottomSheetAction(
|
||||
is ScreenOverlayActionUpdateAction -> {
|
||||
val nudgeActions =
|
||||
action.actions.map {
|
||||
OverlayItemActionData(
|
||||
nudgeId = it.nudgeId,
|
||||
action = getOverlayItemAction(action = it.action),
|
||||
)
|
||||
OverlayItemActionData(nudgeId = it.nudgeId, action = it.action)
|
||||
}
|
||||
screenOverlayVM.triggerActionUpdateApiCall(nudgeActions, activity.screenName)
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.ProfileVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.bottomsheet.ui.HomeScreenBottomSheet
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.utils.InitScreenOverlayComponents
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.initializer.InitScreenOverlayComponents
|
||||
import com.naviapp.screenOverlay.popup.ui.PopupRenderer
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@@ -143,8 +143,8 @@ fun HomeContentFrame(
|
||||
navController = navController,
|
||||
selectedTabId = selectedTabId,
|
||||
nudgeState = { nudgeState },
|
||||
nudgeUitronRenderer = { uiTronResponse ->
|
||||
HandleUitronRenderer(uiTronResponse, screenOverlayVM)
|
||||
nudgeUitronRenderer = { uiTronResponse, modifier ->
|
||||
HandleUitronRenderer(uiTronResponse, screenOverlayVM, modifier)
|
||||
},
|
||||
onNudgeEvent = { event -> screenOverlayVM.sendEvent(event) },
|
||||
onNudgeEffect = { effect -> screenOverlayVM.setEffect { effect } },
|
||||
@@ -165,12 +165,11 @@ fun HomeContentFrame(
|
||||
FullLayerScrim(
|
||||
color = scrimColor,
|
||||
onDismiss = {
|
||||
screenOverlayVM.sendEvent(
|
||||
NudgeEvent.UpdateNudgeExpandedState(expandState = false)
|
||||
)
|
||||
screenOverlayVM.sendEvent(NudgeEvent.ToggleDescriptiveView(enabled = false))
|
||||
},
|
||||
visible =
|
||||
(nudgeState.isNudgeExpanded && selectedTabId == BottomBarTabType.HOME.value),
|
||||
(nudgeState.descriptiveViewEnabled &&
|
||||
selectedTabId == BottomBarTabType.HOME.value),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -33,9 +33,9 @@ import com.naviapp.home.utils.WidgetRenderer
|
||||
import com.naviapp.home.viewmodel.HomeViewModel
|
||||
import com.naviapp.home.viewmodel.NotificationVM
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.model.StaticNudgeUiState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeUiState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.ui.StaticNudgeContainer
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ import com.naviapp.home.model.HpBottomSheetRenderType
|
||||
import com.naviapp.home.model.HpBottomSheetState
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.model.StaticNudgeUiState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeUiState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
import com.naviapp.utils.Constants.HOME_SCREEN_IN_CAPS
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -127,9 +127,13 @@ fun <T> getHomeWidgetAnimationSpec(): FiniteAnimationSpec<T> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandleUitronRenderer(uiTronResponse: UiTronResponse?, viewModel: BaseVM) {
|
||||
fun HandleUitronRenderer(
|
||||
uiTronResponse: UiTronResponse?,
|
||||
viewModel: BaseVM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
UiTronRenderer(dataMap = uiTronResponse?.data, uiTronViewModel = viewModel)
|
||||
.Render(composeViews = uiTronResponse?.parentComposeView.orEmpty())
|
||||
.Render(composeViews = uiTronResponse?.parentComposeView.orEmpty(), modifier = modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -157,9 +161,9 @@ fun WidgetRenderer(
|
||||
NudgeEvent.UpdateStaticNudgeUiState(StaticNudgeUiState.DISMISSED)
|
||||
)
|
||||
viewModel.setEffect {
|
||||
NudgeEffect.OnDeleteNudge(
|
||||
nudgeId = staticNudgeData.nudgeId.orEmpty(),
|
||||
overlayItemTransitionState = OverlayItemTransitionState.PAUSED,
|
||||
NudgeEffect.OnNudgeStateUpdate(
|
||||
id = staticNudgeData.nudgeId.orEmpty(),
|
||||
transitionState = OverlayItemTransitionState.PAUSED,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -199,9 +203,9 @@ fun WidgetRenderer(
|
||||
NudgeEvent.UpdateStaticNudgeUiState(StaticNudgeUiState.DISMISSED)
|
||||
)
|
||||
viewModel.setEffect {
|
||||
NudgeEffect.OnDeleteNudge(
|
||||
nudgeId = staticNudgeData.nudgeId.orEmpty(),
|
||||
overlayItemTransitionState = OverlayItemTransitionState.PAUSED,
|
||||
NudgeEffect.OnNudgeStateUpdate(
|
||||
id = staticNudgeData.nudgeId.orEmpty(),
|
||||
transitionState = OverlayItemTransitionState.PAUSED,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
|
||||
package com.naviapp.screenOverlay.handler
|
||||
|
||||
import com.naviapp.screenOverlay.model.OverlayItemActionData
|
||||
import com.naviapp.screenOverlay.model.OverlayItemStateUpdate
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NUDGE
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NUDGE_DISMISSED_EVENT
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NUDGE_ID
|
||||
@@ -23,19 +24,18 @@ constructor(private val screenOverlayHandler: ScreenOverlayHandler) {
|
||||
effect: NudgeEffect,
|
||||
deletedItemsMap: MutableMap<String, MutableSet<String>>,
|
||||
triggerStateUpdateApiCall: (nudgeTransitionState: List<OverlayItemStateUpdate>) -> Unit,
|
||||
triggerActionUpdateApiCall: (nudgeActions: List<OverlayItemActionData>) -> Unit,
|
||||
) {
|
||||
when (effect) {
|
||||
is NudgeEffect.OnDeleteNudge -> {
|
||||
deletedItemsMap.getOrPut(NUDGE) { mutableSetOf() }.add(effect.nudgeId)
|
||||
is NudgeEffect.OnNudgeStateUpdate -> {
|
||||
deletedItemsMap.getOrPut(NUDGE) { mutableSetOf() }.add(effect.id)
|
||||
triggerStateUpdateApiCall(
|
||||
mutableListOf(
|
||||
OverlayItemStateUpdate(effect.nudgeId, effect.overlayItemTransitionState)
|
||||
)
|
||||
mutableListOf(OverlayItemStateUpdate(effect.id, effect.transitionState))
|
||||
)
|
||||
if (effect.overlayItemTransitionState == OverlayItemTransitionState.PAUSED) {
|
||||
if (effect.transitionState == OverlayItemTransitionState.PAUSED) {
|
||||
screenOverlayHandler.triggerClickStreamEvent(
|
||||
NUDGE_DISMISSED_EVENT,
|
||||
mapOf(NUDGE_ID to effect.nudgeId),
|
||||
mapOf(NUDGE_ID to effect.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,13 @@ constructor(private val screenOverlayHandler: ScreenOverlayHandler) {
|
||||
effect.eventValue.orEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
is NudgeEffect.OnNudgeDismissAction -> {
|
||||
deletedItemsMap.getOrPut(NUDGE) { mutableSetOf() }.add(effect.id)
|
||||
triggerActionUpdateApiCall(
|
||||
mutableListOf(OverlayItemActionData(effect.id, effect.action))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ package com.naviapp.screenOverlay.handler
|
||||
|
||||
import com.navi.uitron.model.data.UiTronActionData
|
||||
import com.naviapp.screenOverlay.bottomsheet.model.BottomSheetEffect
|
||||
import com.naviapp.screenOverlay.model.OverlayItemActionData
|
||||
import com.naviapp.screenOverlay.model.OverlayItemStateUpdate
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.popup.model.PopupEffect
|
||||
import com.naviapp.screenOverlay.popup.model.PopupState
|
||||
import javax.inject.Inject
|
||||
@@ -27,8 +28,14 @@ constructor(
|
||||
effect: NudgeEffect,
|
||||
deletedItemsMap: MutableMap<String, MutableSet<String>>,
|
||||
triggerStateUpdateApiCall: (nudgeTransitionState: List<OverlayItemStateUpdate>) -> Unit,
|
||||
triggerActionUpdateApiCall: (nudgeActions: List<OverlayItemActionData>) -> Unit,
|
||||
) {
|
||||
nudgeEffectHandler.handleNudgeEffect(effect, deletedItemsMap, triggerStateUpdateApiCall)
|
||||
nudgeEffectHandler.handleNudgeEffect(
|
||||
effect,
|
||||
deletedItemsMap,
|
||||
triggerStateUpdateApiCall,
|
||||
triggerActionUpdateApiCall,
|
||||
)
|
||||
}
|
||||
|
||||
fun handlePopupEffect(
|
||||
|
||||
@@ -14,8 +14,8 @@ import com.naviapp.screenOverlay.bottomsheet.model.BottomSheetData
|
||||
import com.naviapp.screenOverlay.model.OverlayItemsStateUpdates
|
||||
import com.naviapp.screenOverlay.model.OverlayScreenStructure
|
||||
import com.naviapp.screenOverlay.model.ScreenOverlayActionUpdateRequest
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeListData
|
||||
import com.naviapp.screenOverlay.nudge.model.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeListData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.popup.model.PopupListData
|
||||
import com.naviapp.screenOverlay.repositories.ScreenOverlayRepository
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NUDGE
|
||||
|
||||
@@ -13,9 +13,13 @@ import com.navi.common.basemvi.UiEvent
|
||||
import com.navi.common.uitron.model.action.CtaAction
|
||||
import com.navi.uitron.model.action.AnalyticsAction
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeUiState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateUpdateType
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeClickAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeCtaAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeDragAction
|
||||
import com.naviapp.screenOverlay.popup.model.PopupEvent
|
||||
import com.naviapp.screenOverlay.popup.model.PopupUiState
|
||||
@@ -26,6 +30,7 @@ import javax.inject.Inject
|
||||
class ScreenOverlayUitronActionHandler @Inject constructor() {
|
||||
|
||||
fun onActionTriggered(
|
||||
state: NudgeState,
|
||||
uiTronAction: UiTronAction,
|
||||
sendEvent: (event: UiEvent) -> Unit,
|
||||
lastClickedNudgeId: (nudgeId: String) -> Unit,
|
||||
@@ -34,15 +39,18 @@ class ScreenOverlayUitronActionHandler @Inject constructor() {
|
||||
when (uiTronAction) {
|
||||
is NudgeDragAction -> {
|
||||
sendEvent(
|
||||
NudgeEvent.UpdateNudgeUiState(
|
||||
uiTronAction.nudgeId.orEmpty(),
|
||||
NudgeUiState.DRAGGED,
|
||||
NudgeEvent.UpdateNudgeDraggedStateMap(
|
||||
uiTronAction.id.orEmpty(),
|
||||
NudgeDraggedStateData(
|
||||
state = NudgeDraggedState.DRAGGED,
|
||||
type = NudgeDraggedStateUpdateType.TOGGLE,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
is NudgeClickAction -> {
|
||||
lastClickedNudgeId(uiTronAction.nudgeId.orEmpty())
|
||||
sendEvent(NudgeEvent.UpdateNudgeExpandedState(false))
|
||||
lastClickedNudgeId(uiTronAction.id.orEmpty())
|
||||
sendEvent(NudgeEvent.ToggleDescriptiveView(false))
|
||||
}
|
||||
is PopupDismissAction -> {
|
||||
sendEvent(
|
||||
@@ -52,6 +60,24 @@ class ScreenOverlayUitronActionHandler @Inject constructor() {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is NudgeCtaAction -> {
|
||||
val nudgeUiState = state.nudgeDraggedStateMap[uiTronAction.id]?.state
|
||||
if (nudgeUiState == NudgeDraggedState.DRAGGED) {
|
||||
sendEvent(
|
||||
NudgeEvent.UpdateNudgeDraggedStateMap(
|
||||
uiTronAction.id,
|
||||
NudgeDraggedStateData(
|
||||
state = NudgeDraggedState.IDLE,
|
||||
type = NudgeDraggedStateUpdateType.TOGGLE,
|
||||
),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
uiTronAction.ctaAction.ctaData?.let { ctaAction(it) }
|
||||
}
|
||||
}
|
||||
|
||||
is CtaAction -> {
|
||||
uiTronAction.ctaData?.let { ctaAction(it) }
|
||||
}
|
||||
|
||||
@@ -9,10 +9,4 @@ package com.naviapp.screenOverlay.model
|
||||
|
||||
data class ScreenOverlayActionUpdateRequest(val nudgeItems: List<OverlayItemActionData>)
|
||||
|
||||
data class OverlayItemActionData(val nudgeId: String, val action: OverlayItemAction)
|
||||
|
||||
enum class OverlayItemAction {
|
||||
VIEW,
|
||||
CLICK,
|
||||
DISMISS,
|
||||
}
|
||||
data class OverlayItemActionData(val nudgeId: String, val action: String)
|
||||
|
||||
@@ -9,8 +9,8 @@ package com.naviapp.screenOverlay.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.naviapp.screenOverlay.bottomsheet.model.BottomSheetData
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeListData
|
||||
import com.naviapp.screenOverlay.nudge.model.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeListData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.popup.model.PopupListData
|
||||
|
||||
data class OverlayScreenStructure(
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.domain.model.data
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
|
||||
typealias NudgeId = String
|
||||
|
||||
@Stable
|
||||
data class NudgeListData(@SerializedName("contentList") val nudgeList: List<NudgeData>? = null)
|
||||
|
||||
@Stable
|
||||
data class NudgeData(
|
||||
@SerializedName("nudgeId") val nudgeId: NudgeId? = null,
|
||||
@SerializedName("nudgeIconUrl") val nudgeIconUrl: String? = null,
|
||||
@SerializedName("nudgeData") val nudgeUitronData: UiTronResponse? = null,
|
||||
@SerializedName("nudgeStatus") val nudgeStatus: NudgeStatus = NudgeStatus.DEFAULT,
|
||||
@SerializedName("nudgeDragEnabled") val isNudgeDragEnabled: Boolean? = true,
|
||||
@SerializedName("nudgeDismissibleActions")
|
||||
val dismissibleActionList: List<NudgeDismissibleActionData>? = null,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class NudgeDismissibleActionData(
|
||||
val actionText: String? = null,
|
||||
val actionIllustration: String? = null,
|
||||
val actionBackgroundColor: String? = null,
|
||||
val actionTextColor: String? = null,
|
||||
val dismissAction: String? = null,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class NudgeDraggedStateData(
|
||||
val state: NudgeDraggedState,
|
||||
val type: NudgeDraggedStateUpdateType,
|
||||
)
|
||||
|
||||
enum class NudgeStatus {
|
||||
IN_PROGRESS,
|
||||
SUCCESS,
|
||||
DEFAULT,
|
||||
DISMISSED,
|
||||
}
|
||||
|
||||
enum class NudgeDraggedState {
|
||||
IDLE,
|
||||
DRAGGED,
|
||||
}
|
||||
|
||||
enum class NudgeDraggedStateUpdateType {
|
||||
DRAG,
|
||||
TOGGLE,
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.model
|
||||
package com.naviapp.screenOverlay.nudge.domain.model.data
|
||||
|
||||
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
|
||||
data class StaticNudgeData(
|
||||
val nudgeId: String? = null,
|
||||
val nudgeId: NudgeId? = null,
|
||||
val nudgeData: AlchemistWidgetModelDefinition<UiTronResponse>? = null,
|
||||
val nudgeUiState: StaticNudgeUiState = StaticNudgeUiState.INITIAL,
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.domain.model.effect
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.navi.common.basemvi.UiEffect
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
|
||||
@Immutable
|
||||
sealed interface NudgeEffect : UiEffect {
|
||||
|
||||
data class OnNudgeDismissAction(val id: NudgeId, val action: String) : NudgeEffect
|
||||
|
||||
data class OnNudgeStateUpdate(
|
||||
val id: NudgeId,
|
||||
val transitionState: OverlayItemTransitionState,
|
||||
) : NudgeEffect
|
||||
|
||||
data class TriggerClickStreamEvent(
|
||||
val eventName: String,
|
||||
val eventValue: Map<String, String>? = null,
|
||||
) : NudgeEffect
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.domain.model.event
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.navi.common.basemvi.UiEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeUiState
|
||||
|
||||
@Immutable
|
||||
sealed interface NudgeEvent : UiEvent {
|
||||
data class ToggleDescriptiveView(val enabled: Boolean) : NudgeEvent
|
||||
|
||||
data class UpdateNudgeList(val data: List<NudgeData>?) : NudgeEvent
|
||||
|
||||
data class DismissNudge(val id: NudgeId) : NudgeEvent
|
||||
|
||||
data class DismissibleActionClicked(val id: NudgeId, val index: Int) : NudgeEvent
|
||||
|
||||
data class UpdateNudgeDraggedStateMap(val key: NudgeId, val value: NudgeDraggedStateData) :
|
||||
NudgeEvent
|
||||
|
||||
data class UpdateStaticNudgeData(val data: StaticNudgeData?) : NudgeEvent
|
||||
|
||||
data class UpdateStaticNudgeUiState(val uiState: StaticNudgeUiState) : NudgeEvent
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.domain.model.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.navi.common.basemvi.UiState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeData
|
||||
|
||||
@Immutable
|
||||
data class NudgeState(
|
||||
val isNudgeListVisible: Boolean,
|
||||
val descriptiveViewEnabled: Boolean,
|
||||
val nudgeList: List<NudgeData>?,
|
||||
val staticNudgeData: StaticNudgeData? = null,
|
||||
val nudgeDraggedStateMap: Map<NudgeId, NudgeDraggedStateData> = emptyMap(),
|
||||
val selectedDismissibleActionIndexMap: Map<NudgeId, Int> = emptyMap(),
|
||||
) : UiState {
|
||||
companion object {
|
||||
val initialState =
|
||||
NudgeState(
|
||||
isNudgeListVisible = false,
|
||||
descriptiveViewEnabled = false,
|
||||
nudgeList = null,
|
||||
staticNudgeData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.domain.reducer
|
||||
|
||||
import com.navi.common.basemvi.BaseReducer
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateUpdateType
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
|
||||
class NudgeReducer : BaseReducer<NudgeState, NudgeEvent> {
|
||||
|
||||
override fun reduce(previousState: NudgeState, event: NudgeEvent): NudgeState {
|
||||
return when (event) {
|
||||
/**
|
||||
* Handles the UpdateNudgeExpandedState event. Updates the expanded state of the nudge
|
||||
* container and sets the UI state of any currently dragged nudge to IDLE.
|
||||
*/
|
||||
is NudgeEvent.ToggleDescriptiveView -> {
|
||||
val draggedStateMap = previousState.nudgeDraggedStateMap.toMutableMap()
|
||||
|
||||
draggedStateMap.entries.forEach {
|
||||
draggedStateMap[it.key] =
|
||||
NudgeDraggedStateData(
|
||||
NudgeDraggedState.IDLE,
|
||||
NudgeDraggedStateUpdateType.TOGGLE,
|
||||
)
|
||||
}
|
||||
|
||||
previousState.copy(
|
||||
descriptiveViewEnabled = event.enabled,
|
||||
nudgeDraggedStateMap = draggedStateMap,
|
||||
)
|
||||
}
|
||||
is NudgeEvent.UpdateNudgeList -> {
|
||||
|
||||
val nudgeDraggedStateMap = mutableMapOf<NudgeId, NudgeDraggedStateData>()
|
||||
|
||||
event.data?.forEach { nudgeData ->
|
||||
nudgeData.nudgeId?.let {
|
||||
nudgeDraggedStateMap[nudgeData.nudgeId] =
|
||||
NudgeDraggedStateData(
|
||||
state = NudgeDraggedState.IDLE,
|
||||
type = NudgeDraggedStateUpdateType.TOGGLE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
previousState.copy(
|
||||
nudgeList = event.data,
|
||||
isNudgeListVisible = true,
|
||||
nudgeDraggedStateMap = nudgeDraggedStateMap,
|
||||
)
|
||||
}
|
||||
|
||||
is NudgeEvent.DismissNudge -> {
|
||||
previousState.copy(
|
||||
nudgeList =
|
||||
previousState.nudgeList?.map { nudge ->
|
||||
if (nudge.nudgeId == event.id) {
|
||||
nudge.copy(nudgeStatus = NudgeStatus.DISMISSED)
|
||||
} else {
|
||||
nudge
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is NudgeEvent.UpdateStaticNudgeData -> {
|
||||
previousState.copy(staticNudgeData = event.data)
|
||||
}
|
||||
is NudgeEvent.UpdateStaticNudgeUiState -> {
|
||||
previousState.copy(
|
||||
staticNudgeData =
|
||||
previousState.staticNudgeData?.copy(nudgeUiState = event.uiState)
|
||||
)
|
||||
}
|
||||
|
||||
is NudgeEvent.DismissibleActionClicked -> {
|
||||
val previousMap = previousState.selectedDismissibleActionIndexMap.toMutableMap()
|
||||
previousMap[event.id] = event.index
|
||||
previousState.copy(selectedDismissibleActionIndexMap = previousMap)
|
||||
}
|
||||
|
||||
is NudgeEvent.UpdateNudgeDraggedStateMap -> {
|
||||
val previousMap = previousState.nudgeDraggedStateMap.toMutableMap()
|
||||
previousMap.getUpdatedNudgeUiState(event)
|
||||
previousState.copy(nudgeDraggedStateMap = previousMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableMap<NudgeId, NudgeDraggedStateData>.getUpdatedNudgeUiState(
|
||||
event: NudgeEvent.UpdateNudgeDraggedStateMap
|
||||
) {
|
||||
this[event.key] =
|
||||
when {
|
||||
this[event.key]?.state == NudgeDraggedState.DRAGGED &&
|
||||
event.value.state == NudgeDraggedState.DRAGGED ->
|
||||
NudgeDraggedStateData(NudgeDraggedState.IDLE, event.value.type)
|
||||
else -> event.value
|
||||
}
|
||||
|
||||
entries
|
||||
.filter { it.key != event.key && it.value.state == NudgeDraggedState.DRAGGED }
|
||||
.forEach {
|
||||
this[it.key] =
|
||||
NudgeDraggedStateData(
|
||||
NudgeDraggedState.IDLE,
|
||||
NudgeDraggedStateUpdateType.TOGGLE,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.handler.action
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.navi.ap.common.handler.HandlePublishEventAction
|
||||
import com.navi.common.uitron.model.action.ScreenOverlayActionUpdateAction
|
||||
import com.navi.common.uitron.model.action.ScreenOverlayApiAction
|
||||
import com.navi.common.uitron.model.action.ScreenOverlayStateUpdateAction
|
||||
import com.naviapp.screenOverlay.model.OverlayItemActionData
|
||||
import com.naviapp.screenOverlay.model.OverlayItemStateUpdate
|
||||
import com.naviapp.screenOverlay.nudge.utils.mapper.mapToTransitionState
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@Composable
|
||||
fun HandleNudgeActions(
|
||||
viewModel: ScreenOverlayVM,
|
||||
naeScreenName: String,
|
||||
isNotificationPermissionEnabled: Boolean,
|
||||
) {
|
||||
HandlePublishEventAction(viewModel = viewModel)
|
||||
HandleApiAction(
|
||||
viewModel = viewModel,
|
||||
naeScreenName = naeScreenName,
|
||||
isNotificationPermissionEnabled = isNotificationPermissionEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HandleApiAction(
|
||||
viewModel: ScreenOverlayVM,
|
||||
naeScreenName: String,
|
||||
isNotificationPermissionEnabled: Boolean,
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.getActionCallback().collect { action ->
|
||||
when (action) {
|
||||
is ScreenOverlayApiAction -> {
|
||||
viewModel.fetchOverlayScreenData(
|
||||
triggerLoadingState = true,
|
||||
naeScreenName = naeScreenName,
|
||||
isNotificationPermissionEnabled = isNotificationPermissionEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
is ScreenOverlayStateUpdateAction -> {
|
||||
val nudgeTransitionState =
|
||||
action.states.map {
|
||||
OverlayItemStateUpdate(
|
||||
nudgeId = it.nudgeId,
|
||||
state = mapToTransitionState(overlayItemState = it.state),
|
||||
)
|
||||
}
|
||||
viewModel.triggerStateUpdateApiCall(
|
||||
nudgeTransitionState = nudgeTransitionState,
|
||||
naeScreenName = naeScreenName,
|
||||
)
|
||||
}
|
||||
|
||||
is ScreenOverlayActionUpdateAction -> {
|
||||
val nudgeActions =
|
||||
action.actions.map {
|
||||
OverlayItemActionData(nudgeId = it.nudgeId, action = it.action)
|
||||
}
|
||||
viewModel.triggerActionUpdateApiCall(nudgeActions, naeScreenName)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.handler.bottomsheet
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.naviapp.home.model.BottomBarTabType
|
||||
import com.naviapp.home.model.HpBottomSheetState
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.bottomsheet.model.BottomSheetState
|
||||
import com.naviapp.screenOverlay.bottomsheet.utils.toHpBottomSheetConfig
|
||||
import com.naviapp.screenOverlay.bottomsheet.utils.toHpBottomSheetContent
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@Composable
|
||||
fun HandleNudgeBottomSheetState(
|
||||
bottomSheetState: BottomSheetState,
|
||||
selectedTabId: String,
|
||||
hpStates: () -> HpStates,
|
||||
sharedVM: SharedVM,
|
||||
screenOverlayVM: ScreenOverlayVM,
|
||||
) {
|
||||
LaunchedEffect(
|
||||
bottomSheetState,
|
||||
selectedTabId,
|
||||
hpStates().profileDrawerState,
|
||||
hpStates().isHomePageRendered,
|
||||
) {
|
||||
val bottomSheetData = bottomSheetState.bottomSheetData ?: return@LaunchedEffect
|
||||
bottomSheetData.let {
|
||||
sharedVM.updateBottomSheetState(
|
||||
state =
|
||||
if (
|
||||
selectedTabId == BottomBarTabType.HOME.value &&
|
||||
hpStates().isHomePageRendered &&
|
||||
hpStates().profileDrawerState.not()
|
||||
) {
|
||||
HpBottomSheetState.Visible
|
||||
} else HpBottomSheetState.Hidden,
|
||||
config = bottomSheetData.toHpBottomSheetConfig(viewModel = screenOverlayVM),
|
||||
content = bottomSheetData.bottomSheetUiTronData?.toHpBottomSheetContent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.handler.drag
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.AnchoredDraggableState
|
||||
import androidx.compose.foundation.gestures.animateTo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateUpdateType
|
||||
|
||||
enum class DragAnchors {
|
||||
Normal,
|
||||
Dragged,
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun HandleAnchorDragStateChange(
|
||||
nudgeDraggedStateData: () -> NudgeDraggedStateData?,
|
||||
anchoredDraggableState: AnchoredDraggableState<DragAnchors>,
|
||||
) {
|
||||
LaunchedEffect(nudgeDraggedStateData()) {
|
||||
if (nudgeDraggedStateData()?.type == NudgeDraggedStateUpdateType.TOGGLE) {
|
||||
if (nudgeDraggedStateData()?.state == NudgeDraggedState.DRAGGED) {
|
||||
anchoredDraggableState.animateTo(DragAnchors.Dragged)
|
||||
} else if (nudgeDraggedStateData()?.state == NudgeDraggedState.IDLE) {
|
||||
anchoredDraggableState.animateTo(DragAnchors.Normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun UpdateNudgeDraggedStateOnDrag(
|
||||
anchoredDraggableState: AnchoredDraggableState<DragAnchors>,
|
||||
onNudgeUiStateChange: (uiState: NudgeDraggedState) -> Unit,
|
||||
nudgeDraggedState: () -> NudgeDraggedState?,
|
||||
) {
|
||||
LaunchedEffect(anchoredDraggableState.currentValue) {
|
||||
if (
|
||||
nudgeDraggedState() == NudgeDraggedState.DRAGGED &&
|
||||
anchoredDraggableState.currentValue == DragAnchors.Normal
|
||||
) {
|
||||
onNudgeUiStateChange(NudgeDraggedState.IDLE)
|
||||
} else if (
|
||||
nudgeDraggedState() == NudgeDraggedState.IDLE &&
|
||||
anchoredDraggableState.currentValue == DragAnchors.Dragged
|
||||
) {
|
||||
onNudgeUiStateChange(NudgeDraggedState.DRAGGED)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.handler.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.navi.base.deeplink.DeepLinkManager
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.base.utils.orFalse
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@Composable
|
||||
fun HandleNudgeCtaNavigation(screenOverlayVM: ScreenOverlayVM, activity: HomePageActivity) {
|
||||
LaunchedEffect(Unit) {
|
||||
screenOverlayVM.redirectionCtaData.collect { ctaData ->
|
||||
ctaData?.let { navigateCtaDeepLink(activity, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateCtaDeepLink(activity: HomePageActivity, ctaData: CtaData) {
|
||||
DeepLinkManager.getDeepLinkListener()
|
||||
?.navigateTo(
|
||||
activity = activity,
|
||||
ctaData = ctaData,
|
||||
finish = ctaData.finish.orFalse(),
|
||||
clearTask = ctaData.clearTask.orFalse(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.initializer
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.naviapp.appsettings.utils.hasNotificationPermission
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.nudge.handler.action.HandleNudgeActions
|
||||
import com.naviapp.screenOverlay.nudge.handler.bottomsheet.HandleNudgeBottomSheetState
|
||||
import com.naviapp.screenOverlay.nudge.handler.navigation.HandleNudgeCtaNavigation
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@Composable
|
||||
fun InitScreenOverlayComponents(
|
||||
screenOverlayVM: ScreenOverlayVM,
|
||||
sharedVM: SharedVM,
|
||||
selectedTabId: String,
|
||||
hpStates: () -> HpStates,
|
||||
activity: HomePageActivity,
|
||||
) {
|
||||
val bottomSheetState by screenOverlayVM.bottomSheetState.collectAsStateWithLifecycle()
|
||||
HandleNudgeActions(
|
||||
viewModel = screenOverlayVM,
|
||||
naeScreenName = activity.screenName,
|
||||
isNotificationPermissionEnabled = hasNotificationPermission(activity),
|
||||
)
|
||||
HandleNudgeCtaNavigation(screenOverlayVM = screenOverlayVM, activity = activity)
|
||||
HandleNudgeBottomSheetState(
|
||||
bottomSheetState = bottomSheetState,
|
||||
selectedTabId = selectedTabId,
|
||||
hpStates = hpStates,
|
||||
sharedVM = sharedVM,
|
||||
screenOverlayVM = screenOverlayVM,
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.navi.common.basemvi.UiEffect
|
||||
import com.navi.common.basemvi.UiEvent
|
||||
import com.navi.common.basemvi.UiState
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
|
||||
@Immutable
|
||||
data class NudgeState(
|
||||
val isNudgeListVisible: Boolean,
|
||||
val isNudgeExpanded: Boolean,
|
||||
val nudgeList: List<NudgeData>?,
|
||||
val staticNudgeData: StaticNudgeData? = null,
|
||||
) : UiState {
|
||||
companion object {
|
||||
val initialState =
|
||||
NudgeState(
|
||||
isNudgeListVisible = false,
|
||||
isNudgeExpanded = false,
|
||||
nudgeList = null,
|
||||
staticNudgeData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface NudgeEffect : UiEffect {
|
||||
data class OnDeleteNudge(
|
||||
val nudgeId: String,
|
||||
val overlayItemTransitionState: OverlayItemTransitionState,
|
||||
) : NudgeEffect
|
||||
|
||||
data class TriggerClickStreamEvent(
|
||||
val eventName: String,
|
||||
val eventValue: Map<String, String>? = null,
|
||||
) : NudgeEffect
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface NudgeEvent : UiEvent {
|
||||
data class UpdateNudgeExpandedState(val expandState: Boolean) : NudgeEvent
|
||||
|
||||
data class UpdateNudgeList(val data: List<NudgeData>?) : NudgeEvent
|
||||
|
||||
data class DeleteNudge(val nudgeId: String) : NudgeEvent
|
||||
|
||||
data class UpdateNudgeUiState(val nudgeId: String, val state: NudgeUiState) : NudgeEvent
|
||||
|
||||
data class UpdateStaticNudgeData(val data: StaticNudgeData?) : NudgeEvent
|
||||
|
||||
data class UpdateStaticNudgeUiState(val nudgeUiState: StaticNudgeUiState) : NudgeEvent
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
|
||||
@Stable
|
||||
data class NudgeListData(@SerializedName("contentList") val nudgeList: List<NudgeData>? = null)
|
||||
|
||||
@Stable
|
||||
data class NudgeData(
|
||||
@SerializedName("nudgeId") val nudgeId: String? = null,
|
||||
@SerializedName("nudgeData") val nudgeUitronData: UiTronResponse? = null,
|
||||
@SerializedName("isNudgeDragEnabled") val isNudgeDragEnabled: Boolean? = null,
|
||||
@SerializedName("nudgeStatus") val nudgeStatus: NudgeStatus = NudgeStatus.DEFAULT,
|
||||
val nudgeUiState: NudgeUiState = NudgeUiState.IDLE,
|
||||
)
|
||||
|
||||
// Don't remove UnUsed Status From Here
|
||||
enum class NudgeStatus {
|
||||
SUCCESS,
|
||||
IN_PROGRESS,
|
||||
DEFAULT,
|
||||
DELETED,
|
||||
}
|
||||
|
||||
enum class NudgeUiState {
|
||||
IDLE,
|
||||
DRAGGED,
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.reducer
|
||||
|
||||
import com.navi.common.basemvi.BaseReducer
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeUiState
|
||||
|
||||
class NudgeReducer : BaseReducer<NudgeState, NudgeEvent> {
|
||||
|
||||
override fun reduce(previousState: NudgeState, event: NudgeEvent): NudgeState {
|
||||
return when (event) {
|
||||
/**
|
||||
* Handles the UpdateNudgeExpandedState event. Updates the expanded state of the nudge
|
||||
* container and sets the UI state of any currently dragged nudge to IDLE.
|
||||
*/
|
||||
is NudgeEvent.UpdateNudgeExpandedState -> {
|
||||
previousState.copy(
|
||||
isNudgeExpanded = event.expandState,
|
||||
nudgeList =
|
||||
previousState.nudgeList?.map { nudge ->
|
||||
if (nudge.nudgeUiState == NudgeUiState.DRAGGED) {
|
||||
nudge.copy(nudgeUiState = NudgeUiState.IDLE)
|
||||
} else {
|
||||
nudge
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
is NudgeEvent.UpdateNudgeList -> {
|
||||
previousState.copy(nudgeList = event.data, isNudgeListVisible = true)
|
||||
}
|
||||
is NudgeEvent.UpdateNudgeUiState -> {
|
||||
previousState.copy(
|
||||
nudgeList =
|
||||
previousState.nudgeList?.map { nudge ->
|
||||
getUpdatedNudgeUiState(nudge, event)
|
||||
}
|
||||
)
|
||||
}
|
||||
is NudgeEvent.DeleteNudge -> {
|
||||
previousState.copy(
|
||||
nudgeList =
|
||||
previousState.nudgeList?.map { nudge ->
|
||||
if (nudge.nudgeId == event.nudgeId) {
|
||||
nudge.copy(nudgeStatus = NudgeStatus.DELETED)
|
||||
} else {
|
||||
nudge
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
is NudgeEvent.UpdateStaticNudgeData -> {
|
||||
previousState.copy(staticNudgeData = event.data)
|
||||
}
|
||||
is NudgeEvent.UpdateStaticNudgeUiState -> {
|
||||
previousState.copy(
|
||||
staticNudgeData =
|
||||
previousState.staticNudgeData?.copy(nudgeUiState = event.nudgeUiState)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the updated UI state for a given nudge element based on a UI state update event.
|
||||
* Handles the following scenarios:* - If the event targets the given nudge and it's currently
|
||||
* being dragged, set its state to IDLE.
|
||||
* - If any other nudge is currently being dragged and the event indicates a new drag, set the
|
||||
* other nudge's state to IDLE.
|
||||
*
|
||||
* @param nudge The current nudge element data.
|
||||
* @param event The UI state update event containing the new state and target nudge ID.
|
||||
* @return The updated nudgeData with the appropriate UI state.
|
||||
*/
|
||||
private fun getUpdatedNudgeUiState(
|
||||
nudge: NudgeData,
|
||||
event: NudgeEvent.UpdateNudgeUiState,
|
||||
): NudgeData {
|
||||
return when {
|
||||
nudge.nudgeId == event.nudgeId ->
|
||||
nudge.copy(
|
||||
nudgeUiState =
|
||||
if (
|
||||
event.state == NudgeUiState.DRAGGED &&
|
||||
nudge.nudgeUiState == NudgeUiState.DRAGGED
|
||||
)
|
||||
NudgeUiState.IDLE
|
||||
else event.state
|
||||
)
|
||||
nudge.nudgeUiState == NudgeUiState.DRAGGED && event.state == NudgeUiState.DRAGGED ->
|
||||
nudge.copy(nudgeUiState = NudgeUiState.IDLE)
|
||||
else -> nudge
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeColor.borderColor
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeRootUtils.HandleExpandedStateByListSize
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeRootUtils.filterDeletedNudges
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeRootUtils.nudgeContainerEnterTransition
|
||||
|
||||
/**
|
||||
* Nudge Container, managing its visibility based on the nudge visibility state in the
|
||||
* `NudgeReducer.NudgeState`. It orchestrates the display of both collapsed and expanded nudge
|
||||
* states within the container.
|
||||
*
|
||||
* @param state The current state of the nudge system, controlling the container's visibility and
|
||||
* providing nudge data.
|
||||
* @param nudgeUitronRenderer Composable function responsible for rendering the Uitron content of
|
||||
* nudge elements.
|
||||
* @param onNudgeEvent Callback function to dispatch NudgeReducer events triggered by user
|
||||
* interactions.
|
||||
*/
|
||||
@Composable
|
||||
fun NudgeContainer(
|
||||
state: () -> NudgeState,
|
||||
nudgeUitronRenderer: @Composable (UiTronResponse?) -> Unit,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = state().isNudgeListVisible,
|
||||
enter = nudgeContainerEnterTransition,
|
||||
exit = ExitTransition.None,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.BottomCenter) {
|
||||
if (state().isNudgeExpanded.not()) {
|
||||
NudgeContainerCollapsedState(
|
||||
nudgeState = state(),
|
||||
nudgeUitronRenderer = nudgeUitronRenderer,
|
||||
onNudgeEvent = onNudgeEvent,
|
||||
onNudgeEffect = onNudgeEffect,
|
||||
)
|
||||
}
|
||||
NudgeContainerExpandedState(
|
||||
nudgeState = state(),
|
||||
nudgeUitronRenderer = nudgeUitronRenderer,
|
||||
onNudgeEvent = onNudgeEvent,
|
||||
onNudgeEffect = onNudgeEffect,
|
||||
)
|
||||
if (filterDeletedNudges(state()).isNotEmpty()) {
|
||||
HorizontalDivider(color = borderColor, thickness = 1.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
HandleExpandedStateByListSize(state, onNudgeEvent)
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ContentTransform
|
||||
import androidx.compose.animation.SizeTransform
|
||||
import androidx.compose.animation.core.keyframes
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.navi.base.utils.orZero
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.ui.nudgeUI.NudgeUI
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeCollapsedStateUtils.collapsedContainerExitTransition
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeCollapsedStateUtils.collapsedContainerRankChangeTransition
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeRootUtils.filterDeletedNudges
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NudgeAnimationConstants.COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NudgeAnimationConstants.REPLACE_NUDGE_IN_COLLAPSED_STATE
|
||||
|
||||
/**
|
||||
* The collapsed state for nudge container, showing a single nudge element and a middle pill if more
|
||||
* are available. It manages the transition between different nudge elements in the collapsed state
|
||||
* and handles the expansion to the full nudge list.
|
||||
*
|
||||
* @param nudgeState The current state of the nudge system, containing information about available
|
||||
* nudges.
|
||||
* @param nudgeUitronRenderer Composable function responsible for rendering the Uitron content of
|
||||
* individual nudge elements.
|
||||
* @param onNudgeEvent Callback function to dispatch NudgeReducer events, such as expanding the
|
||||
* nudge list.
|
||||
*/
|
||||
@Composable
|
||||
fun NudgeContainerCollapsedState(
|
||||
nudgeState: NudgeState,
|
||||
nudgeUitronRenderer: @Composable (UiTronResponse?) -> Unit,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
) {
|
||||
val filteredNudge = filterDeletedNudges(nudgeState).firstOrNull()
|
||||
if (filteredNudge != null) {
|
||||
Box(Modifier.fillMaxWidth()) {
|
||||
AnimatedContent(
|
||||
targetState = filteredNudge,
|
||||
contentKey = { it.nudgeId },
|
||||
transitionSpec = {
|
||||
getCollapsedNudgeTransitionSpec(
|
||||
nudgeState,
|
||||
this.initialState.nudgeId.orEmpty(),
|
||||
) using
|
||||
SizeTransform { old, new ->
|
||||
keyframes {
|
||||
IntSize(new.width, old.height) at
|
||||
COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION
|
||||
durationMillis = COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION
|
||||
}
|
||||
}
|
||||
},
|
||||
label = REPLACE_NUDGE_IN_COLLAPSED_STATE,
|
||||
) {
|
||||
it.let {
|
||||
NudgeUI(
|
||||
modifier = Modifier.padding(top = 13.dp),
|
||||
nudgeUitronRenderer = nudgeUitronRenderer,
|
||||
nudgeData = it,
|
||||
onNudgeEvent = onNudgeEvent,
|
||||
enableFrontLayerElevation = true,
|
||||
onNudgeEffect = onNudgeEffect,
|
||||
)
|
||||
}
|
||||
}
|
||||
NudgeMiddlePill(
|
||||
nudgeCount = filterDeletedNudges(nudgeState).size.orZero(),
|
||||
onClick = {
|
||||
onNudgeEvent(NudgeEvent.UpdateNudgeExpandedState(true))
|
||||
onNudgeEffect(
|
||||
NudgeEffect.TriggerClickStreamEvent(eventName = "nudge_middle_pill_clicked")
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCollapsedNudgeTransitionSpec(
|
||||
nudgeState: NudgeState,
|
||||
initialNudgeId: String,
|
||||
): ContentTransform {
|
||||
return if (filterDeletedNudges(nudgeState).map { it.nudgeId }.contains(initialNudgeId)) {
|
||||
collapsedContainerRankChangeTransition
|
||||
} else {
|
||||
collapsedContainerExitTransition
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.EnterTransition
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.navi.design.font.FontWeightEnum
|
||||
import com.navi.design.font.getFontWeight
|
||||
import com.navi.design.font.naviFontFamily
|
||||
import com.navi.design.theme.FF191919
|
||||
import com.navi.naviwidgets.composewidget.reusable.whiteColor
|
||||
import com.navi.naviwidgets.extensions.NaviText
|
||||
import com.navi.naviwidgets.utils.NaviWidgetIconUtils.getImageFromIconCode
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.ui.nudgeUI.NudgeUI
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeExpandedStateUtils.expandedNudgeExitTransition
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeExpandedStateUtils.getExpandedStateEnterTransitionSpec
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeExpandedStateUtils.getExpandedStateExitTransitionSpec
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.YOUR_PAYMENTS
|
||||
import com.naviapp.utils.IconUtils.ICON_CROSS_BLACK
|
||||
|
||||
/**
|
||||
* Expanded State of Nudge Container, showing a list of available nudge elements. The visibility of
|
||||
* this UI is controlled by the `nudgeExpandedState` in the `NudgeReducer.NudgeState` and the
|
||||
* presence of nudge elements.
|
||||
*
|
||||
* @param nudgeState The current state of the nudge, containing information about the nudge states
|
||||
* and available nudges.
|
||||
* @param nudgeUitronRenderer Composable function responsible for rendering the Uitron content of
|
||||
* individual nudge elements.
|
||||
* @param onNudgeEvent Callback function to dispatch NudgeReducer events, such as updating the
|
||||
* expanded state.
|
||||
*/
|
||||
@Composable
|
||||
fun NudgeContainerExpandedState(
|
||||
nudgeState: NudgeState,
|
||||
nudgeUitronRenderer: @Composable (UiTronResponse?) -> Unit,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
) {
|
||||
val localConfiguration = LocalConfiguration.current
|
||||
val screenHeight = remember { localConfiguration.screenHeightDp.dp }
|
||||
AnimatedVisibility(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.heightIn(min = 0.dp, max = screenHeight - 200.dp)
|
||||
.clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
|
||||
.background(whiteColor)
|
||||
.clickable(enabled = false) {},
|
||||
visible = nudgeState.isNudgeExpanded,
|
||||
enter = getExpandedStateEnterTransitionSpec,
|
||||
exit = getExpandedStateExitTransitionSpec,
|
||||
) {
|
||||
Column {
|
||||
NudgeExpandedStateHeader {
|
||||
onNudgeEvent(NudgeEvent.UpdateNudgeExpandedState(expandState = false))
|
||||
}
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
nudgeState.nudgeList?.forEach { nudgeData ->
|
||||
key(nudgeData.nudgeId) {
|
||||
val nudgeVisible by
|
||||
remember(nudgeData.nudgeStatus) {
|
||||
derivedStateOf { nudgeData.nudgeStatus != NudgeStatus.DELETED }
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = nudgeVisible,
|
||||
enter = EnterTransition.None,
|
||||
exit = expandedNudgeExitTransition,
|
||||
) {
|
||||
NudgeUI(
|
||||
nudgeUitronRenderer = nudgeUitronRenderer,
|
||||
nudgeData = nudgeData,
|
||||
onNudgeEvent = onNudgeEvent,
|
||||
onNudgeEffect = onNudgeEffect,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The header for the expanded nudge state UI, including a title and a close icon.
|
||||
*
|
||||
* @param onClick Callback function to be invoked when the close icon is clicked.
|
||||
*/
|
||||
@Composable
|
||||
private fun NudgeExpandedStateHeader(onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.background(whiteColor)
|
||||
.padding(top = 12.dp, bottom = 12.dp, start = 16.dp, end = 12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
NaviText(
|
||||
text = YOUR_PAYMENTS,
|
||||
color = FF191919,
|
||||
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
|
||||
style = TextStyle(fontSize = 16.sp, fontFamily = naviFontFamily),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.size(28.dp).clip(CircleShape).clickable(onClick = { onClick() }),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
painter = painterResource(getImageFromIconCode(ICON_CROSS_BLACK)),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.navi.coin.theme.borderAlt
|
||||
import com.navi.design.font.FontWeightEnum
|
||||
import com.navi.design.font.getFontWeight
|
||||
import com.navi.design.font.naviFontFamily
|
||||
import com.navi.naviwidgets.composewidget.reusable.whiteColor
|
||||
import com.navi.naviwidgets.extensions.NaviText
|
||||
import com.navi.naviwidgets.utils.NaviWidgetIconUtils.CHEVRON_UP_BLACK
|
||||
import com.navi.naviwidgets.utils.NaviWidgetIconUtils.getImageFromIconCode
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeCollapsedStateUtils.middlePillContentTransitionSpec
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeCollapsedStateUtils.middlePillEnterTransitionSpec
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeCollapsedStateUtils.middlePillExitTransitionSpec
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeColor.darkTextColor
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.MORE
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NudgeAnimationConstants.MIDDLE_PILL_CONTENT_ANIMATION
|
||||
|
||||
/**
|
||||
* Middle pill on collapsed nudge state depicting the number of nudge Elements. The pill's
|
||||
* visibility is determined by the `nudgeCount`, becoming visible when there are more than one nudge
|
||||
* elements.
|
||||
*
|
||||
* @param nudgeCount The total number of nudge elements available.
|
||||
* @param onClick Callback function to be invoked when the middle pill is clicked.
|
||||
*/
|
||||
@Composable
|
||||
fun BoxScope.NudgeMiddlePill(nudgeCount: Int, onClick: () -> Unit) {
|
||||
val nudgeMiddlePillVisibility by remember(nudgeCount) { derivedStateOf { nudgeCount > 1 } }
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
visible = nudgeMiddlePillVisibility,
|
||||
enter = middlePillEnterTransitionSpec,
|
||||
exit = middlePillExitTransitionSpec,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier,
|
||||
shape = RoundedCornerShape(50),
|
||||
border = BorderStroke(width = 1.dp, color = borderAlt),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = whiteColor),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier.clip(RoundedCornerShape(50))
|
||||
.heightIn(min = 24.dp)
|
||||
.clickable(onClick = { onClick() })
|
||||
.padding(start = 14.dp, end = 12.dp, top = 4.dp, bottom = 4.dp),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = nudgeCount,
|
||||
transitionSpec = { middlePillContentTransitionSpec },
|
||||
label = MIDDLE_PILL_CONTENT_ANIMATION,
|
||||
) {
|
||||
NaviText(
|
||||
text = "+${(it - 1).coerceAtMost(9)} ",
|
||||
color = darkTextColor,
|
||||
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
|
||||
style = TextStyle(fontSize = 12.sp, fontFamily = naviFontFamily),
|
||||
)
|
||||
}
|
||||
NaviText(
|
||||
text = MORE,
|
||||
color = darkTextColor,
|
||||
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
|
||||
style = TextStyle(fontSize = 12.sp, fontFamily = naviFontFamily),
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Image(
|
||||
modifier = Modifier.size(12.dp),
|
||||
painter = painterResource(id = getImageFromIconCode(CHEVRON_UP_BLACK)),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import com.naviapp.home.utils.getHomeWidgetAnimationSpec
|
||||
import com.naviapp.screenOverlay.nudge.model.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.model.StaticNudgeUiState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeUiState
|
||||
|
||||
@Composable
|
||||
fun StaticNudgeContainer(state: StaticNudgeData?, widgetRenderer: @Composable () -> Unit) {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.container
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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 com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.ui.container.collapsed.NudgeContainerCollapsedState
|
||||
import com.naviapp.screenOverlay.nudge.ui.container.collapsed.NudgeMiddlePillContainer
|
||||
import com.naviapp.screenOverlay.nudge.ui.container.expanded.NudgeContainerExpandedState
|
||||
import com.naviapp.screenOverlay.nudge.ui.container.expanded.NudgeExpandedStateHeader
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeEnterAnimation
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeExitAnimation
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeConstants
|
||||
import com.naviapp.screenOverlay.nudge.utils.extensions.filterDeletedNudges
|
||||
import com.naviapp.screenOverlay.nudge.utils.root.HandleExpandedStateByListSize
|
||||
|
||||
@Composable
|
||||
fun NudgeContainer(
|
||||
state: () -> NudgeState,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
nudgeUitronRenderer: @Composable (response: UiTronResponse?, modifier: Modifier) -> Unit,
|
||||
) {
|
||||
val singleNudgeHeight = remember { mutableIntStateOf(0) }
|
||||
|
||||
val descriptiveViewEnabled =
|
||||
remember(state().descriptiveViewEnabled) { state().descriptiveViewEnabled }
|
||||
|
||||
val nudgeList = remember(state().nudgeList) { state().nudgeList }
|
||||
|
||||
val selectedDismissibleActionIndexMap =
|
||||
remember(state().selectedDismissibleActionIndexMap) {
|
||||
state().selectedDismissibleActionIndexMap
|
||||
}
|
||||
|
||||
val nudgeDraggedStateMap =
|
||||
remember(state().nudgeDraggedStateMap) { state().nudgeDraggedStateMap }
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = state().isNudgeListVisible,
|
||||
enter = nudgeEnterAnimation(),
|
||||
exit = nudgeExitAnimation(),
|
||||
) {
|
||||
Column {
|
||||
NudgeExpandedStateHeader(state().descriptiveViewEnabled) {
|
||||
onNudgeEvent(NudgeEvent.ToggleDescriptiveView(false))
|
||||
}
|
||||
|
||||
Box(contentAlignment = Alignment.BottomCenter) {
|
||||
NudgeContainerCollapsedState(
|
||||
nudgeList = nudgeList,
|
||||
isDescriptiveViewEnabled = descriptiveViewEnabled,
|
||||
onHeightChange = { singleNudgeHeight.intValue = it },
|
||||
onNudgeEvent = onNudgeEvent,
|
||||
onNudgeEffect = onNudgeEffect,
|
||||
nudgeUitronRenderer = nudgeUitronRenderer,
|
||||
selectedDismissibleActionIndexMap = selectedDismissibleActionIndexMap,
|
||||
nudgeDraggedStateMap = nudgeDraggedStateMap,
|
||||
)
|
||||
NudgeContainerExpandedState(
|
||||
nudgeList = nudgeList,
|
||||
isDescriptiveViewEnabled = descriptiveViewEnabled,
|
||||
singleNudgeHeight = singleNudgeHeight.intValue,
|
||||
onNudgeEvent = onNudgeEvent,
|
||||
onNudgeEffect = onNudgeEffect,
|
||||
nudgeUitronRenderer = nudgeUitronRenderer,
|
||||
selectedDismissibleActionIndexMap = selectedDismissibleActionIndexMap,
|
||||
nudgeDraggedStateMap = nudgeDraggedStateMap,
|
||||
)
|
||||
|
||||
NudgeMiddlePillContainer(
|
||||
nudgeDraggedStateMap = state().nudgeDraggedStateMap,
|
||||
isDescriptiveViewEnabled = descriptiveViewEnabled,
|
||||
nudgeList = nudgeList.filterDeletedNudges(),
|
||||
onClick = {
|
||||
onNudgeEvent(NudgeEvent.ToggleDescriptiveView(true))
|
||||
onNudgeEffect(
|
||||
NudgeEffect.TriggerClickStreamEvent(
|
||||
eventName = NudgeConstants.MIDDLE_PILL_CLICKED
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HandleExpandedStateByListSize(state, onNudgeEvent)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.container.collapsed
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.ui.nudgeUI.NudgeUI
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeCollapsedStateTransitionSpec
|
||||
import com.naviapp.screenOverlay.nudge.utils.extensions.filterDeletedNudges
|
||||
import com.naviapp.screenOverlay.nudge.utils.extensions.getFirstNonDismissedNudge
|
||||
import com.naviapp.screenOverlay.nudge.utils.modifier.BorderSides
|
||||
|
||||
@Composable
|
||||
fun NudgeContainerCollapsedState(
|
||||
nudgeList: List<NudgeData>?,
|
||||
isDescriptiveViewEnabled: Boolean,
|
||||
onHeightChange: (Int) -> Unit,
|
||||
nudgeDraggedStateMap: Map<NudgeId, NudgeDraggedStateData>,
|
||||
selectedDismissibleActionIndexMap: Map<NudgeId, Int>,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
nudgeUitronRenderer: @Composable (response: UiTronResponse?, modifier: Modifier) -> Unit,
|
||||
) {
|
||||
val nudgeData =
|
||||
remember(nudgeList.getFirstNonDismissedNudge()) { nudgeList.getFirstNonDismissedNudge() }
|
||||
|
||||
val showBorder =
|
||||
remember(nudgeData?.nudgeStatus) { nudgeData?.nudgeStatus != NudgeStatus.SUCCESS }
|
||||
|
||||
AnimatedContent(
|
||||
modifier = Modifier.onSizeChanged { onHeightChange(it.height) },
|
||||
targetState = nudgeData,
|
||||
contentKey = { it?.nudgeId },
|
||||
contentAlignment = Alignment.BottomCenter,
|
||||
transitionSpec = {
|
||||
nudgeCollapsedStateTransitionSpec(
|
||||
isDescriptiveViewEnabled = isDescriptiveViewEnabled,
|
||||
filteredNudgeList = nudgeList.filterDeletedNudges(),
|
||||
initialNudgeId = this.initialState?.nudgeId.toString(),
|
||||
)
|
||||
},
|
||||
) { data ->
|
||||
NudgeUI(
|
||||
modifier = Modifier,
|
||||
nudgeUitronRenderer = nudgeUitronRenderer,
|
||||
nudgeData = data,
|
||||
nudgeDraggedStateData = { nudgeDraggedStateMap[data?.nudgeId] },
|
||||
onNudgeEvent = onNudgeEvent,
|
||||
elevateFrontLayer = true,
|
||||
onNudgeEffect = onNudgeEffect,
|
||||
borderSides = if (showBorder) BorderSides.VERTICAL else BorderSides.NONE,
|
||||
selectedDismissibleActionIndex = selectedDismissibleActionIndexMap[data?.nudgeId],
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.container.collapsed
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.naviapp.R
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeMiddlePillEnterAnimationSpec
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeMiddlePillExitAnimationSpec
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeAnimationConstants
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeAnimationConstants.defaultAnimationSpec
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeColor.darkTextColor
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeColor.shadowColor
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.UNPAID
|
||||
|
||||
@Composable
|
||||
fun BoxScope.NudgeMiddlePillContainer(
|
||||
nudgeList: List<NudgeData>,
|
||||
isDescriptiveViewEnabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
nudgeDraggedStateMap: Map<NudgeId, NudgeDraggedStateData>,
|
||||
) {
|
||||
val firstNudge = nudgeList.firstOrNull()
|
||||
|
||||
var previousNudgeId by remember { mutableStateOf(firstNudge?.nudgeId) }
|
||||
|
||||
val isFirstNudgeReplaced = firstNudge?.nudgeId != previousNudgeId
|
||||
|
||||
LaunchedEffect(firstNudge?.nudgeId) { previousNudgeId = firstNudge?.nudgeId }
|
||||
|
||||
val visibilityBaseCondition by
|
||||
remember(nudgeList, isDescriptiveViewEnabled) {
|
||||
derivedStateOf { nudgeList.size > 1 && isDescriptiveViewEnabled.not() }
|
||||
}
|
||||
|
||||
val visibilityConditionDueToUiState by
|
||||
remember(nudgeDraggedStateMap[firstNudge?.nudgeId]?.state) {
|
||||
derivedStateOf {
|
||||
nudgeDraggedStateMap[firstNudge?.nudgeId]?.state == NudgeDraggedState.IDLE
|
||||
}
|
||||
}
|
||||
|
||||
val subsequentTopNudgeIcons =
|
||||
remember(nudgeList) { nudgeList.drop(1).take(2).mapNotNull { it.nudgeIconUrl } }
|
||||
|
||||
val nudgeCount = remember(nudgeList) { (nudgeList.size - 1).coerceAtMost(9) }
|
||||
|
||||
val defaultElevation = remember {
|
||||
Animatable(if (visibilityConditionDueToUiState || visibilityBaseCondition) 1f else 0f)
|
||||
}
|
||||
|
||||
LaunchedEffect(visibilityConditionDueToUiState, visibilityBaseCondition) {
|
||||
defaultElevation.animateTo(
|
||||
if (visibilityConditionDueToUiState && visibilityBaseCondition) 1f else 0f,
|
||||
defaultAnimationSpec(),
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.graphicsLayer { translationY = size.height.times(-0.67f) }
|
||||
.shadow(
|
||||
elevation = (defaultElevation.value * 4).dp,
|
||||
shape = CircleShape,
|
||||
clip = true,
|
||||
ambientColor = shadowColor.copy(defaultElevation.value * 0.42f),
|
||||
spotColor = shadowColor.copy(defaultElevation.value * 0.42f),
|
||||
)
|
||||
.align(Alignment.TopCenter)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visibilityBaseCondition,
|
||||
enter = nudgeMiddlePillEnterAnimationSpec(NudgeAnimationConstants.DURATION),
|
||||
exit = ExitTransition.None,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visibilityConditionDueToUiState,
|
||||
enter =
|
||||
nudgeMiddlePillEnterAnimationSpec(
|
||||
if (isFirstNudgeReplaced)
|
||||
NudgeAnimationConstants.COLLAPSED_NUDGE_ELEMENT_TRANSITION_DELAY
|
||||
else 0
|
||||
),
|
||||
exit = nudgeMiddlePillExitAnimationSpec(),
|
||||
content = nudgeMiddlePillContent(subsequentTopNudgeIcons, nudgeCount, onClick),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun nudgeMiddlePillContent(
|
||||
iconUrlList: List<String>,
|
||||
nudgeCount: Int,
|
||||
onClick: () -> Unit,
|
||||
): @Composable AnimatedVisibilityScope.() -> Unit = {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.border(width = 0.9.dp, color = darkTextColor, shape = CircleShape)
|
||||
.padding(0.7.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier.graphicsLayer {
|
||||
clip = true
|
||||
shape = CircleShape
|
||||
}
|
||||
.background(Color.White)
|
||||
.heightIn(min = 24.dp)
|
||||
.clickable(onClick = { onClick() })
|
||||
.padding(start = 4.dp, end = 8.dp, top = 4.dp, bottom = 4.dp),
|
||||
) {
|
||||
MiddlePillIllustration(iconUrlList = iconUrlList)
|
||||
ElexText(
|
||||
text = "+$nudgeCount",
|
||||
color = darkTextColor,
|
||||
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(start = if (iconUrlList.isEmpty()) 8.dp else 0.dp),
|
||||
)
|
||||
ElexText(
|
||||
text = UNPAID,
|
||||
color = darkTextColor,
|
||||
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
Spacer(Modifier.width(2.dp))
|
||||
Image(
|
||||
modifier = Modifier.size(12.dp),
|
||||
painter = painterResource(id = R.drawable.cheveron_up),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MiddlePillIllustration(iconUrlList: List<String>) {
|
||||
if (iconUrlList.isEmpty()) return
|
||||
|
||||
val iconModifier = remember {
|
||||
Modifier.size(20.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White, CircleShape)
|
||||
.border(0.24.dp, Color(0xff6B6B6B), CircleShape)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.padding(end = if (iconUrlList.size == 1) 4.dp else 12.dp)) {
|
||||
if (iconUrlList.size == 1) {
|
||||
ElexAsyncImage(
|
||||
icon = iconUrlList.first(),
|
||||
contentDescription = null,
|
||||
modifier = iconModifier,
|
||||
)
|
||||
} else {
|
||||
ElexAsyncImage(
|
||||
icon = iconUrlList.first(),
|
||||
contentDescription = null,
|
||||
modifier = iconModifier,
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
ElexAsyncImage(
|
||||
icon = iconUrlList.last(),
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
Modifier.size(20.dp)
|
||||
.offset(x = 8.dp, y = 0.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White, CircleShape)
|
||||
.border(0.24.dp, Color(0xff6B6B6B), CircleShape),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.container.expanded
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.ui.nudgeUI.NudgeUI
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeDescriptiveViewEnterAnimation
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeDescriptiveViewExitAnimation
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeExitTransitionInDescriptiveView
|
||||
import com.naviapp.screenOverlay.nudge.utils.modifier.BorderSides
|
||||
|
||||
@Composable
|
||||
fun NudgeContainerExpandedState(
|
||||
nudgeList: List<NudgeData>?,
|
||||
isDescriptiveViewEnabled: Boolean,
|
||||
singleNudgeHeight: Int,
|
||||
nudgeDraggedStateMap: Map<NudgeId, NudgeDraggedStateData>,
|
||||
selectedDismissibleActionIndexMap: Map<String, Int>,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
nudgeUitronRenderer: @Composable (UiTronResponse?, Modifier) -> Unit,
|
||||
) {
|
||||
val localConfiguration = LocalConfiguration.current
|
||||
val maxViewHeight = remember { localConfiguration.screenHeightDp.dp - 200.dp }
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.heightIn(min = 0.dp, max = maxViewHeight).verticalScroll(scrollState),
|
||||
visible = isDescriptiveViewEnabled,
|
||||
enter = nudgeDescriptiveViewEnterAnimation(singleNudgeHeight),
|
||||
exit = nudgeDescriptiveViewExitAnimation(singleNudgeHeight),
|
||||
) {
|
||||
Column {
|
||||
nudgeList?.forEachIndexed { index, nudgeData ->
|
||||
key(nudgeData.nudgeId) {
|
||||
AnimatedVisibility(
|
||||
visible = nudgeData.nudgeStatus != NudgeStatus.DISMISSED,
|
||||
enter = EnterTransition.None,
|
||||
exit = nudgeExitTransitionInDescriptiveView(),
|
||||
) {
|
||||
NudgeUI(
|
||||
nudgeUitronRenderer = nudgeUitronRenderer,
|
||||
nudgeData = nudgeData,
|
||||
onNudgeEvent = onNudgeEvent,
|
||||
onNudgeEffect = onNudgeEffect,
|
||||
selectedDismissibleActionIndex =
|
||||
selectedDismissibleActionIndexMap[nudgeData.nudgeId],
|
||||
nudgeDraggedStateData = { nudgeDraggedStateMap[nudgeData.nudgeId] },
|
||||
borderSides =
|
||||
BorderSides(
|
||||
top =
|
||||
index ==
|
||||
nudgeList.indexOfFirst {
|
||||
it.nudgeStatus == NudgeStatus.DEFAULT
|
||||
},
|
||||
bottom = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.container.expanded
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.navi.design.theme.FF191919
|
||||
import com.navi.elex.atoms.ElexText
|
||||
import com.navi.elex.font.FontWeightEnum
|
||||
import com.navi.naviwidgets.composewidget.reusable.whiteColor
|
||||
import com.navi.naviwidgets.utils.NaviWidgetIconUtils.ICON_CROSS_WHITE_24_24
|
||||
import com.navi.naviwidgets.utils.NaviWidgetIconUtils.getImageFromIconCode
|
||||
import com.naviapp.home.ui.theme.color.HomeScreenColor.scrimColor
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeHeaderEnterAnimation
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeHeaderExitAnimation
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.UNPAID_BILLS_RECHARGES
|
||||
|
||||
@Composable
|
||||
fun NudgeExpandedStateHeader(isVisible: Boolean, crossIconOnClick: () -> Unit) {
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
enter = nudgeHeaderEnterAnimation(),
|
||||
exit = nudgeHeaderExitAnimation(),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(32.dp)) {
|
||||
CrossIcon(crossIconOnClick)
|
||||
HeaderTitle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderTitle() {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
|
||||
.background(whiteColor, RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
ElexText(
|
||||
text = UNPAID_BILLS_RECHARGES,
|
||||
color = FF191919,
|
||||
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
|
||||
fontSize = 16.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CrossIcon(onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.size(48.dp).clip(CircleShape).background(scrimColor),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(getImageFromIconCode(ICON_CROSS_WHITE_24_24)),
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
Modifier.size(24.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = true),
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.nudgeUI
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.sp
|
||||
import com.navi.design.font.FontWeightEnum
|
||||
import com.navi.design.font.getFontWeight
|
||||
import com.navi.design.font.naviFontFamily
|
||||
import com.navi.naviwidgets.extensions.NaviText
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeColor.colorRed
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeColor.lightRed
|
||||
import com.naviapp.screenOverlay.nudge.utils.animateDismissBoxWidth
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.ANCHOR_DRAG_WIDTH
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.DISMISS
|
||||
|
||||
/**
|
||||
* Nudge element back layer, revealed when the front layer is dragged.
|
||||
*
|
||||
* @param nudgeHeight The height of the front layer, used to provide the height of the back layer.
|
||||
* @param frontLayerVisible Indicates whether the front layer is visible or not.
|
||||
* @param onDismissed Invoked upon clicking the dismiss row to hide the front layer.
|
||||
* @param deleteNudge Invoked upon completion of the dismiss animation to initiate the deletion of
|
||||
* the nudge element.
|
||||
*/
|
||||
@Composable
|
||||
fun NudgeBackLayer(
|
||||
nudgeHeight: Dp,
|
||||
frontLayerVisible: Boolean,
|
||||
onDismissed: () -> Unit,
|
||||
deleteNudge: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
Modifier.height(nudgeHeight).fillMaxWidth().background(lightRed).clickable {
|
||||
onDismissed()
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
Modifier.width(animateDismissBoxWidth(frontLayerVisible, deleteNudge).value)
|
||||
.fillMaxHeight()
|
||||
)
|
||||
NaviText(
|
||||
text = DISMISS,
|
||||
color = colorRed,
|
||||
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style =
|
||||
TextStyle(
|
||||
fontSize = 12.sp,
|
||||
fontFamily = naviFontFamily,
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
modifier = Modifier.width(ANCHOR_DRAG_WIDTH),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.nudgeUI
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.core.exponentialDecay
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.AnchoredDraggableState
|
||||
import androidx.compose.foundation.gestures.DraggableAnchors
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.anchoredDraggable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeUiState
|
||||
import com.naviapp.screenOverlay.nudge.utils.DragAnchors
|
||||
import com.naviapp.screenOverlay.nudge.utils.HandleAnchorDragStateChange
|
||||
import com.naviapp.screenOverlay.nudge.utils.UpdateNudgeUiStateOnDrag
|
||||
import com.naviapp.screenOverlay.nudge.utils.getFrontLayerExitTransitionSpec
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.ANCHOR_DRAG_WIDTH
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* The front layer of a nudge element, supporting anchored dragging for dismissal.
|
||||
*
|
||||
* @param modifier Modifier for customizing the appearance and behavior of the front layer.
|
||||
* @param frontLayerVisibility Controls the visibility of the front layer.
|
||||
* @param nudgeUiState Provides access to the current UI state of the nudge element.
|
||||
* @param frontLayerContent Composable content to be displayed within the front layer.
|
||||
* @param onNudgeUiStateChange Callback invoked when the nudge element's UI state needs to be
|
||||
* updated.
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun NudgeFrontLayer(
|
||||
modifier: Modifier,
|
||||
frontLayerVisibility: Boolean,
|
||||
isNudgeDragEnabled: Boolean,
|
||||
nudgeUiState: () -> NudgeUiState?,
|
||||
frontLayerContent: @Composable () -> Unit,
|
||||
onNudgeUiStateChange: (state: NudgeUiState) -> Unit,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val anchoredDragWidth = remember { with(density) { (ANCHOR_DRAG_WIDTH).toPx() } }
|
||||
val anchoredDraggableState = remember {
|
||||
AnchoredDraggableState(
|
||||
initialValue = DragAnchors.Normal,
|
||||
anchors =
|
||||
DraggableAnchors {
|
||||
DragAnchors.Normal at 0f
|
||||
DragAnchors.Dragged at anchoredDragWidth
|
||||
},
|
||||
positionalThreshold = { distance: Float -> distance * 0.5f },
|
||||
velocityThreshold = { anchoredDragWidth },
|
||||
snapAnimationSpec = tween(),
|
||||
decayAnimationSpec = exponentialDecay(),
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = frontLayerVisibility,
|
||||
enter = EnterTransition.None,
|
||||
exit = getFrontLayerExitTransitionSpec,
|
||||
) {
|
||||
Box(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.offset {
|
||||
IntOffset(x = -anchoredDraggableState.requireOffset().roundToInt(), y = 0)
|
||||
}
|
||||
.anchoredDraggable(
|
||||
state = anchoredDraggableState,
|
||||
orientation = Orientation.Horizontal,
|
||||
enabled = isNudgeDragEnabled,
|
||||
reverseDirection = true,
|
||||
)
|
||||
) {
|
||||
frontLayerContent()
|
||||
}
|
||||
}
|
||||
|
||||
HandleAnchorDragStateChange(
|
||||
nudgeUiState = nudgeUiState,
|
||||
anchoredDraggableState = anchoredDraggableState,
|
||||
)
|
||||
UpdateNudgeUiStateOnDrag(
|
||||
nudgeUiState = nudgeUiState,
|
||||
anchoredDraggableState = anchoredDraggableState,
|
||||
onNudgeUiStateChange = onNudgeUiStateChange,
|
||||
)
|
||||
}
|
||||
@@ -7,99 +7,142 @@
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.nudgeUI
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.navi.base.utils.orTrue
|
||||
import com.navi.base.utils.orZero
|
||||
import com.navi.naviwidgets.composewidget.reusable.whiteColor
|
||||
import com.navi.pay.utils.pxToDp
|
||||
import com.navi.uitron.model.UiTronResponse
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.utils.HandleNudgeStatusChange
|
||||
import com.naviapp.screenOverlay.nudge.utils.NudgeColor.borderColor
|
||||
import com.navi.uitron.utils.hexToComposeColor
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateUpdateType
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.ui.nudgeUI.components.NudgeBackLayer
|
||||
import com.naviapp.screenOverlay.nudge.ui.nudgeUI.components.NudgeFrontLayer
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeColor.borderColor
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeConstants.dismissibleActionHorizontalPadding
|
||||
import com.naviapp.screenOverlay.nudge.utils.measurement.getMaxWidthForDismissibleActionItem
|
||||
import com.naviapp.screenOverlay.nudge.utils.modifier.BorderSides
|
||||
import com.naviapp.screenOverlay.nudge.utils.modifier.DirectionalBorderModifier
|
||||
import com.naviapp.screenOverlay.nudge.utils.root.HandleNudgeStatusChange
|
||||
|
||||
/**
|
||||
* Nudge element, managing its front and back layers, and handling interactions.
|
||||
*
|
||||
* @param modifier Modifier for customizing the appearance and behavior of the root container.
|
||||
* @param nudgeData Nudge element data.
|
||||
* @param nudgeUitronRenderer Composable function responsible for rendering the Uitron content of
|
||||
* the nudge element.
|
||||
* @param onNudgeEvent Callback function to dispatchNudgeReducer events.
|
||||
*/
|
||||
@Composable
|
||||
fun NudgeUI(
|
||||
modifier: Modifier = Modifier,
|
||||
nudgeData: NudgeData,
|
||||
nudgeUitronRenderer: @Composable (UiTronResponse?) -> Unit,
|
||||
borderSides: BorderSides,
|
||||
nudgeData: NudgeData?,
|
||||
nudgeDraggedStateData: () -> NudgeDraggedStateData?,
|
||||
selectedDismissibleActionIndex: Int?,
|
||||
elevateFrontLayer: Boolean = false,
|
||||
onNudgeEvent: (event: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
enableFrontLayerElevation: Boolean = false,
|
||||
nudgeUitronRenderer: @Composable (response: UiTronResponse?, modifier: Modifier) -> Unit,
|
||||
) {
|
||||
var frontLayerVisible by remember { mutableStateOf(true) }
|
||||
var frontLayerHeight by remember { mutableIntStateOf(0) }
|
||||
if (nudgeData == null) return
|
||||
val density = LocalDensity.current
|
||||
|
||||
val nudgeId = remember(nudgeData.nudgeId) { nudgeData.nudgeId.orEmpty() }
|
||||
|
||||
var frontLayerVisibility by remember { mutableStateOf(true) }
|
||||
|
||||
var frontLayerHeight by remember(nudgeId) { mutableIntStateOf(0) }
|
||||
|
||||
var isFrontLayerHeightMeasured by remember(nudgeId) { mutableStateOf(false) }
|
||||
|
||||
val maxWidthForDismissibleAction =
|
||||
getMaxWidthForDismissibleActionItem(actions = nudgeData.dismissibleActionList).pxToDp() +
|
||||
dismissibleActionHorizontalPadding
|
||||
|
||||
val contextMenuWidth by
|
||||
remember(maxWidthForDismissibleAction) {
|
||||
derivedStateOf {
|
||||
maxWidthForDismissibleAction.times(nudgeData.dismissibleActionList?.size.orZero())
|
||||
}
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier,
|
||||
modifier = modifier,
|
||||
elevation =
|
||||
CardDefaults.cardElevation(
|
||||
defaultElevation = if (enableFrontLayerElevation) 4.dp else 0.dp
|
||||
),
|
||||
shape = RoundedCornerShape(size = 0.dp),
|
||||
CardDefaults.cardElevation(defaultElevation = if (elevateFrontLayer) 4.dp else 0.dp),
|
||||
shape = RectangleShape,
|
||||
colors = CardDefaults.cardColors(containerColor = whiteColor),
|
||||
) {
|
||||
Box {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
NudgeBackLayer(
|
||||
nudgeHeight = with(LocalDensity.current) { frontLayerHeight.toDp() },
|
||||
frontLayerVisible = frontLayerVisible,
|
||||
onDismissed = { frontLayerVisible = false },
|
||||
deleteNudge = {
|
||||
onNudgeEvent(NudgeEvent.DeleteNudge(nudgeId = nudgeData.nudgeId.orEmpty()))
|
||||
onNudgeEffect(
|
||||
NudgeEffect.OnDeleteNudge(
|
||||
nudgeId = nudgeData.nudgeId.orEmpty(),
|
||||
overlayItemTransitionState = OverlayItemTransitionState.PAUSED,
|
||||
)
|
||||
)
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.height(with(density) { frontLayerHeight.toDp() })
|
||||
.background(
|
||||
nudgeData.dismissibleActionList
|
||||
?.first()
|
||||
?.actionBackgroundColor
|
||||
?.hexToComposeColor ?: Color.White
|
||||
),
|
||||
dismissibleActionList = nudgeData.dismissibleActionList,
|
||||
dismissibleActionItemWidth = maxWidthForDismissibleAction,
|
||||
onActionItemClicked = { index ->
|
||||
onNudgeEvent(NudgeEvent.DismissibleActionClicked(nudgeId, index))
|
||||
frontLayerVisibility = false
|
||||
},
|
||||
selectedIndex = selectedDismissibleActionIndex,
|
||||
onDismissNudge = { action ->
|
||||
onNudgeEvent(NudgeEvent.DismissNudge(nudgeId))
|
||||
onNudgeEffect(NudgeEffect.OnNudgeDismissAction(nudgeId, action))
|
||||
},
|
||||
)
|
||||
NudgeFrontLayer(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().onGloballyPositioned {
|
||||
frontLayerHeight = it.size.height
|
||||
Modifier.fillMaxWidth().onSizeChanged {
|
||||
if (isFrontLayerHeightMeasured.not()) {
|
||||
isFrontLayerHeightMeasured = true
|
||||
frontLayerHeight = it.height
|
||||
}
|
||||
},
|
||||
nudgeUiState = { nudgeData.nudgeUiState },
|
||||
nudgeDraggedStateData = nudgeDraggedStateData,
|
||||
isNudgeDragEnabled = nudgeData.isNudgeDragEnabled.orTrue(),
|
||||
frontLayerVisibility = frontLayerVisible,
|
||||
frontLayerContent = { nudgeUitronRenderer(nudgeData.nudgeUitronData) },
|
||||
onNudgeUiStateChange = { state ->
|
||||
frontLayerVisibility = frontLayerVisibility,
|
||||
contextMenuWidth = contextMenuWidth,
|
||||
frontLayerContent = {
|
||||
nudgeUitronRenderer(
|
||||
nudgeData.nudgeUitronData,
|
||||
Modifier.then(DirectionalBorderModifier(borderSides, 1.dp, borderColor)),
|
||||
)
|
||||
},
|
||||
onNudgeDraggedStateChange = { state ->
|
||||
onNudgeEvent(
|
||||
NudgeEvent.UpdateNudgeUiState(
|
||||
nudgeId = nudgeData.nudgeId.orEmpty(),
|
||||
state = state,
|
||||
NudgeEvent.UpdateNudgeDraggedStateMap(
|
||||
key = nudgeId,
|
||||
value =
|
||||
NudgeDraggedStateData(
|
||||
state = state,
|
||||
type = NudgeDraggedStateUpdateType.DRAG,
|
||||
),
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
if (nudgeData.nudgeStatus != NudgeStatus.SUCCESS) {
|
||||
HorizontalDivider(color = borderColor, thickness = 1.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HandleNudgeStatusChange(nudgeData, onNudgeEvent, onNudgeEffect)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.nudgeUI.action
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
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.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.Dp
|
||||
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.naviwidgets.R as NaviWidgetR
|
||||
import com.navi.uitron.utils.hexToComposeColor
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDismissibleActionData
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeAnimationConstants
|
||||
import com.naviapp.utils.Constants.EMPTY
|
||||
|
||||
@Composable
|
||||
fun NudgeDismissibleActionItem(
|
||||
item: NudgeDismissibleActionData,
|
||||
shouldExpand: () -> Boolean,
|
||||
actionWidth: Dp,
|
||||
itemContainerWidth: Dp,
|
||||
onClick: () -> Unit,
|
||||
dismissAction: () -> Unit,
|
||||
) {
|
||||
if (item.actionText == null) return
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val maxScreenWidth = remember { configuration.screenWidthDp.dp }
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.fillMaxHeight()
|
||||
.background(item.actionBackgroundColor?.hexToComposeColor ?: Color.Gray)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = onClick,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.width(actionWidth).padding(vertical = 13.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
ElexAsyncImage(
|
||||
icon = item.actionIllustration,
|
||||
modifier = Modifier.size(20.dp),
|
||||
contentDescription = EMPTY,
|
||||
placeholder = NaviWidgetR.drawable.image_placeholder_small,
|
||||
)
|
||||
ElexText(
|
||||
text = item.actionText,
|
||||
color = item.actionTextColor?.hexToComposeColor ?: Color.White,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 18.sp,
|
||||
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier.animateContentSize(
|
||||
animationSpec = NudgeAnimationConstants.defaultAnimationSpec(),
|
||||
finishedListener = { _, _ -> dismissAction() },
|
||||
)
|
||||
.width(if (shouldExpand()) maxScreenWidth - itemContainerWidth else 0.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.nudgeUI.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
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.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.navi.uitron.utils.hexToComposeColor
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDismissibleActionData
|
||||
import com.naviapp.screenOverlay.nudge.ui.nudgeUI.action.NudgeDismissibleActionItem
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeDismissibleActionExitFadeAnimation
|
||||
|
||||
@Composable
|
||||
fun NudgeBackLayer(
|
||||
modifier: Modifier,
|
||||
selectedIndex: Int?,
|
||||
dismissibleActionItemWidth: Dp = 0.dp,
|
||||
dismissibleActionList: List<NudgeDismissibleActionData>?,
|
||||
onActionItemClicked: (Int) -> Unit = {},
|
||||
onDismissNudge: (String) -> Unit,
|
||||
) {
|
||||
val size = remember(dismissibleActionList) { dismissibleActionList?.size ?: 0 }
|
||||
Box(modifier = modifier, contentAlignment = Alignment.CenterEnd) {
|
||||
dismissibleActionList?.forEachIndexed { index, actionData ->
|
||||
key(actionData.dismissAction) {
|
||||
AnimatedVisibility(
|
||||
visible =
|
||||
!dismissibleActionList.subList(0, index).indices.any {
|
||||
it == selectedIndex
|
||||
},
|
||||
exit = nudgeDismissibleActionExitFadeAnimation(),
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.background(
|
||||
actionData.actionBackgroundColor?.hexToComposeColor
|
||||
?: Color.Gray
|
||||
)
|
||||
.widthIn(min = dismissibleActionItemWidth.times(size - index))
|
||||
.fillMaxHeight()
|
||||
) {
|
||||
NudgeDismissibleActionItem(
|
||||
item = actionData,
|
||||
shouldExpand = {
|
||||
dismissibleActionList
|
||||
.subList(index, size)
|
||||
.indices
|
||||
.map { it + index }
|
||||
.any { it == selectedIndex }
|
||||
},
|
||||
onClick = { onActionItemClicked(index) },
|
||||
dismissAction = {
|
||||
if (index == selectedIndex && actionData.dismissAction != null) {
|
||||
onDismissNudge(actionData.dismissAction)
|
||||
}
|
||||
},
|
||||
itemContainerWidth = dismissibleActionItemWidth * (size - index),
|
||||
actionWidth = dismissibleActionItemWidth,
|
||||
)
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier.width(dismissibleActionItemWidth * ((size - 1) - index))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.ui.nudgeUI.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.core.exponentialDecay
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.AnchoredDraggableState
|
||||
import androidx.compose.foundation.gestures.DraggableAnchors
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.anchoredDraggable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDraggedStateData
|
||||
import com.naviapp.screenOverlay.nudge.handler.drag.DragAnchors
|
||||
import com.naviapp.screenOverlay.nudge.handler.drag.HandleAnchorDragStateChange
|
||||
import com.naviapp.screenOverlay.nudge.handler.drag.UpdateNudgeDraggedStateOnDrag
|
||||
import com.naviapp.screenOverlay.nudge.utils.animation.nudgeFrontLayerExitAnimationSpec
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun NudgeFrontLayer(
|
||||
modifier: Modifier,
|
||||
frontLayerVisibility: Boolean,
|
||||
contextMenuWidth: Dp,
|
||||
isNudgeDragEnabled: Boolean,
|
||||
nudgeDraggedStateData: () -> NudgeDraggedStateData?,
|
||||
frontLayerContent: @Composable BoxScope.() -> Unit,
|
||||
onNudgeDraggedStateChange: (state: NudgeDraggedState) -> Unit,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val anchoredDragWidth = remember { with(density) { contextMenuWidth.toPx() } }
|
||||
val anchoredDraggableState = remember {
|
||||
AnchoredDraggableState(
|
||||
initialValue = DragAnchors.Normal,
|
||||
anchors =
|
||||
DraggableAnchors {
|
||||
DragAnchors.Normal at 0f
|
||||
DragAnchors.Dragged at anchoredDragWidth
|
||||
},
|
||||
positionalThreshold = { distance: Float -> distance * 0.5f },
|
||||
velocityThreshold = { anchoredDragWidth },
|
||||
snapAnimationSpec = tween(),
|
||||
decayAnimationSpec = exponentialDecay(),
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = frontLayerVisibility,
|
||||
enter = EnterTransition.None,
|
||||
exit = nudgeFrontLayerExitAnimationSpec(),
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.graphicsLayer { translationX = -anchoredDraggableState.requireOffset() }
|
||||
.anchoredDraggable(
|
||||
state = anchoredDraggableState,
|
||||
orientation = Orientation.Horizontal,
|
||||
enabled = isNudgeDragEnabled,
|
||||
reverseDirection = true,
|
||||
),
|
||||
content = frontLayerContent,
|
||||
)
|
||||
}
|
||||
|
||||
HandleAnchorDragStateChange(
|
||||
nudgeDraggedStateData = nudgeDraggedStateData,
|
||||
anchoredDraggableState = anchoredDraggableState,
|
||||
)
|
||||
UpdateNudgeDraggedStateOnDrag(
|
||||
nudgeDraggedState = { nudgeDraggedStateData()?.state },
|
||||
anchoredDraggableState = anchoredDraggableState,
|
||||
onNudgeUiStateChange = onNudgeDraggedStateChange,
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
@@ -10,8 +10,9 @@ package com.naviapp.screenOverlay.nudge.uitronAction
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.navi.uitron.model.data.ActionDetails
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
|
||||
data class NudgeClickAction(@SerializedName("nudgeId") val nudgeId: String?) : UiTronAction() {
|
||||
data class NudgeClickAction(@SerializedName("nudgeId") val id: NudgeId?) : UiTronAction() {
|
||||
override suspend fun manageAction(actionDetails: ActionDetails) {
|
||||
val action = actionDetails.uiTronAction as NudgeClickAction
|
||||
actionDetails.actionCallbackFlow?.emit(action)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.uitronAction
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.navi.common.uitron.model.action.CtaAction
|
||||
import com.navi.uitron.model.data.ActionDetails
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
|
||||
data class NudgeCtaAction(
|
||||
@SerializedName("nudgeId") val id: NudgeId,
|
||||
@SerializedName("ctaAction") val ctaAction: CtaAction,
|
||||
) : UiTronAction() {
|
||||
override suspend fun manageAction(actionDetails: ActionDetails) {
|
||||
val action = actionDetails.uiTronAction as NudgeCtaAction
|
||||
actionDetails.actionCallbackFlow?.emit(action)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
@@ -10,8 +10,9 @@ package com.naviapp.screenOverlay.nudge.uitronAction
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.navi.uitron.model.data.ActionDetails
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
|
||||
data class NudgeDragAction(@SerializedName("nudgeId") val nudgeId: String?) : UiTronAction() {
|
||||
data class NudgeDragAction(@SerializedName("nudgeId") val id: NudgeId?) : UiTronAction() {
|
||||
override suspend fun manageAction(actionDetails: ActionDetails) {
|
||||
val action = actionDetails.uiTronAction as NudgeDragAction
|
||||
actionDetails.actionCallbackFlow?.emit(action)
|
||||
|
||||
@@ -10,4 +10,5 @@ package com.naviapp.screenOverlay.nudge.uitronAction
|
||||
enum class NudgeUiTronActions {
|
||||
DRAG_NUDGE,
|
||||
NUDGE_CLICKED,
|
||||
NUDGE_CTA_ACTION,
|
||||
}
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.navi.ap.common.handler.HandlePublishEventAction
|
||||
import com.navi.base.deeplink.DeepLinkManager
|
||||
import com.navi.base.model.CtaData
|
||||
import com.navi.base.utils.orFalse
|
||||
import com.navi.common.uitron.model.action.ScreenOverlayActionUpdateAction
|
||||
import com.navi.common.uitron.model.action.ScreenOverlayApiAction
|
||||
import com.navi.common.uitron.model.action.ScreenOverlayStateUpdateAction
|
||||
import com.naviapp.appsettings.utils.hasNotificationPermission
|
||||
import com.naviapp.home.compose.activity.HomePageActivity
|
||||
import com.naviapp.home.model.BottomBarTabType
|
||||
import com.naviapp.home.model.HpBottomSheetState
|
||||
import com.naviapp.home.reducer.HpStates
|
||||
import com.naviapp.home.viewmodel.SharedVM
|
||||
import com.naviapp.screenOverlay.bottomsheet.model.BottomSheetState
|
||||
import com.naviapp.screenOverlay.bottomsheet.utils.toHpBottomSheetConfig
|
||||
import com.naviapp.screenOverlay.bottomsheet.utils.toHpBottomSheetContent
|
||||
import com.naviapp.screenOverlay.model.OverlayItemAction
|
||||
import com.naviapp.screenOverlay.model.OverlayItemActionData
|
||||
import com.naviapp.screenOverlay.model.OverlayItemStateUpdate
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
import com.naviapp.screenOverlay.viewModel.ScreenOverlayVM
|
||||
|
||||
@Composable
|
||||
fun InitScreenOverlayComponents(
|
||||
screenOverlayVM: ScreenOverlayVM,
|
||||
sharedVM: SharedVM,
|
||||
selectedTabId: String,
|
||||
hpStates: () -> HpStates,
|
||||
activity: HomePageActivity,
|
||||
) {
|
||||
val bottomSheetState by screenOverlayVM.bottomSheetState.collectAsStateWithLifecycle()
|
||||
InitActionsHandler(
|
||||
viewModel = screenOverlayVM,
|
||||
naeScreenName = activity.screenName,
|
||||
isNotificationPermissionEnabled = hasNotificationPermission(activity),
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
screenOverlayVM.redirectionCtaData.collect {
|
||||
it?.let { handleCta(activity = activity, ctaData = it) }
|
||||
}
|
||||
}
|
||||
HandleBottomSheetNudgeState(
|
||||
bottomSheetState,
|
||||
selectedTabId,
|
||||
hpStates,
|
||||
sharedVM,
|
||||
screenOverlayVM,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HandleBottomSheetNudgeState(
|
||||
bottomSheetState: BottomSheetState,
|
||||
selectedTabId: String,
|
||||
hpStates: () -> HpStates,
|
||||
sharedVM: SharedVM,
|
||||
screenOverlayVM: ScreenOverlayVM,
|
||||
) {
|
||||
LaunchedEffect(
|
||||
bottomSheetState,
|
||||
selectedTabId,
|
||||
hpStates().profileDrawerState,
|
||||
hpStates().isHomePageRendered,
|
||||
) {
|
||||
bottomSheetState.bottomSheetData?.let {
|
||||
sharedVM.updateBottomSheetState(
|
||||
state =
|
||||
if (
|
||||
selectedTabId == BottomBarTabType.HOME.value &&
|
||||
hpStates().isHomePageRendered &&
|
||||
hpStates().profileDrawerState.not()
|
||||
)
|
||||
HpBottomSheetState.Visible
|
||||
else HpBottomSheetState.Hidden,
|
||||
config =
|
||||
bottomSheetState.bottomSheetData?.toHpBottomSheetConfig(
|
||||
viewModel = screenOverlayVM
|
||||
),
|
||||
content =
|
||||
bottomSheetState.bottomSheetData
|
||||
?.bottomSheetUiTronData
|
||||
?.toHpBottomSheetContent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InitActionsHandler(
|
||||
viewModel: ScreenOverlayVM,
|
||||
naeScreenName: String,
|
||||
isNotificationPermissionEnabled: Boolean,
|
||||
) {
|
||||
HandlePublishEventAction(viewModel = viewModel)
|
||||
HandleApiAction(
|
||||
viewModel = viewModel,
|
||||
naeScreenName = naeScreenName,
|
||||
isNotificationPermissionEnabled = isNotificationPermissionEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandleApiAction(
|
||||
viewModel: ScreenOverlayVM,
|
||||
naeScreenName: String,
|
||||
isNotificationPermissionEnabled: Boolean,
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.getActionCallback().collect { action ->
|
||||
when (action) {
|
||||
is ScreenOverlayApiAction -> {
|
||||
viewModel.fetchOverlayScreenData(
|
||||
triggerLoadingState = true,
|
||||
naeScreenName = naeScreenName,
|
||||
isNotificationPermissionEnabled = isNotificationPermissionEnabled,
|
||||
)
|
||||
}
|
||||
is ScreenOverlayStateUpdateAction -> {
|
||||
val nudgeTransitionState =
|
||||
action.states.map {
|
||||
OverlayItemStateUpdate(
|
||||
nudgeId = it.nudgeId,
|
||||
state = getOverlayItemTransitionState(overlayItemState = it.state),
|
||||
)
|
||||
}
|
||||
viewModel.triggerStateUpdateApiCall(
|
||||
nudgeTransitionState = nudgeTransitionState,
|
||||
naeScreenName = naeScreenName,
|
||||
)
|
||||
}
|
||||
is ScreenOverlayActionUpdateAction -> {
|
||||
val nudgeActions =
|
||||
action.actions.map {
|
||||
OverlayItemActionData(
|
||||
nudgeId = it.nudgeId,
|
||||
action = getOverlayItemAction(action = it.action),
|
||||
)
|
||||
}
|
||||
viewModel.triggerActionUpdateApiCall(nudgeActions, naeScreenName)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getOverlayItemTransitionState(overlayItemState: String): OverlayItemTransitionState {
|
||||
return when (overlayItemState) {
|
||||
OverlayItemTransitionState.SUCCESS_ACKNOWLEDGED.name -> {
|
||||
OverlayItemTransitionState.SUCCESS_ACKNOWLEDGED
|
||||
}
|
||||
OverlayItemTransitionState.COMPLETED.name -> {
|
||||
OverlayItemTransitionState.COMPLETED
|
||||
}
|
||||
else -> {
|
||||
OverlayItemTransitionState.PAUSED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getOverlayItemAction(action: String): OverlayItemAction {
|
||||
return when (action) {
|
||||
OverlayItemAction.VIEW.name -> {
|
||||
OverlayItemAction.VIEW
|
||||
}
|
||||
OverlayItemAction.CLICK.name -> {
|
||||
OverlayItemAction.CLICK
|
||||
}
|
||||
else -> {
|
||||
OverlayItemAction.DISMISS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCta(activity: HomePageActivity, ctaData: CtaData) {
|
||||
DeepLinkManager.getDeepLinkListener()
|
||||
?.navigateTo(
|
||||
activity = activity,
|
||||
ctaData = ctaData,
|
||||
finish = ctaData.finish.orFalse(),
|
||||
clearTask = ctaData.clearTask.orFalse(),
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
object NudgeColor {
|
||||
val colorRed = Color(0xFFBD0000)
|
||||
val borderColor = Color(0xFFE3E5E5)
|
||||
val lightRed = Color(0xFFFEECEC)
|
||||
val darkTextColor = Color(0xFF1F002A)
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils
|
||||
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.AnchoredDraggableState
|
||||
import androidx.compose.foundation.gestures.animateTo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeUiState
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.ANCHOR_DRAG_WIDTH
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NudgeAnimationConstants.DISMISS_TEXT_SYNC_ANIMATION
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* Observes changes in the nudge element's UI state and updates the draggable state of the front
|
||||
* layer accordingly. When the UI state transitions to `Dragged` (e.g., after clicking the cross
|
||||
* icon), the front layer animates to its dragged position. Conversely, when the UI state
|
||||
* transitions to `Normal` (e.g., after clicking the cross icon again), the front layer animates
|
||||
* back to its normal position.
|
||||
*
|
||||
* @param nudgeUiState A lambda providing access to the current NudgeUiState, reflecting the UI
|
||||
* state of the nudge element.
|
||||
* @param anchoredDraggableState The AnchoredDraggableState that controls the draggable behavior of
|
||||
* the front layer.
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun HandleAnchorDragStateChange(
|
||||
nudgeUiState: () -> NudgeUiState?,
|
||||
anchoredDraggableState: AnchoredDraggableState<DragAnchors>,
|
||||
) {
|
||||
LaunchedEffect(nudgeUiState()) {
|
||||
if (nudgeUiState() == NudgeUiState.DRAGGED) {
|
||||
anchoredDraggableState.animateTo(DragAnchors.Dragged)
|
||||
} else if (nudgeUiState() == NudgeUiState.IDLE) {
|
||||
anchoredDraggableState.animateTo(DragAnchors.Normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes changes in the AnchoredDraggableState and updates the NudgeUiState accordingly,
|
||||
* reflecting the current drag state of the nudge element.
|
||||
*
|
||||
* When the AnchoredDraggableState transitions to `Normal` from `Dragged`, the NudgeUiState is
|
||||
* updated to `IDLE`. Conversely, when the AnchoredDraggableState transitions to `Dragged` from
|
||||
* `Normal`, the NudgeUiState is updated to `DRAGGED`.
|
||||
*
|
||||
* @param anchoredDraggableState The AnchoredDraggableState that controls the draggable behavior of
|
||||
* the nudge element.
|
||||
* @param onNudgeUiStateChange A callback function to be invoked when the NudgeUiState needs to be
|
||||
* updated.
|
||||
* @param nudgeUiState A function providing the current NudgeUiState.
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun UpdateNudgeUiStateOnDrag(
|
||||
anchoredDraggableState: AnchoredDraggableState<DragAnchors>,
|
||||
onNudgeUiStateChange: (state: NudgeUiState) -> Unit,
|
||||
nudgeUiState: () -> NudgeUiState?,
|
||||
) {
|
||||
var oldAnchoredDraggableState by remember {
|
||||
mutableStateOf(anchoredDraggableState.currentValue)
|
||||
}
|
||||
LaunchedEffect(anchoredDraggableState.currentValue) {
|
||||
if (
|
||||
nudgeUiState() == NudgeUiState.DRAGGED &&
|
||||
oldAnchoredDraggableState == DragAnchors.Dragged &&
|
||||
anchoredDraggableState.currentValue == DragAnchors.Normal
|
||||
) {
|
||||
onNudgeUiStateChange(NudgeUiState.IDLE)
|
||||
} else if (
|
||||
nudgeUiState() == NudgeUiState.IDLE &&
|
||||
oldAnchoredDraggableState == DragAnchors.Normal &&
|
||||
anchoredDraggableState.currentValue == DragAnchors.Dragged
|
||||
) {
|
||||
onNudgeUiStateChange(NudgeUiState.DRAGGED)
|
||||
}
|
||||
oldAnchoredDraggableState = anchoredDraggableState.currentValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reacts to changes in the status of a nudge element and dispatches corresponding events to the
|
||||
* NudgeReducer. When the nudge element's status transitions to either `SUCCESS` or `IN_PROGRESS`,
|
||||
* an `UpdateNudgeStatus` event is dispatched to the reducer, carrying the element's ID and its
|
||||
* updated status.
|
||||
*
|
||||
* @param nudgeData Nudge Element Data.
|
||||
* @param onNudgeEvent Callback function to dispatch NudgeReducer events.
|
||||
*/
|
||||
@Composable
|
||||
fun HandleNudgeStatusChange(
|
||||
nudgeData: NudgeData,
|
||||
onNudgeEvent: (nudgeEvent: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
) {
|
||||
LaunchedEffect(nudgeData.nudgeStatus) {
|
||||
if (nudgeData.nudgeStatus == NudgeStatus.SUCCESS) {
|
||||
/**
|
||||
* Note: When the status is set to SUCCESS, a 1300ms(300ms is success animation time)
|
||||
* delay is introduced before triggering the deletion of the nudge element to allow for
|
||||
* a delete animation.
|
||||
*/
|
||||
delay(1300)
|
||||
onNudgeEvent(NudgeEvent.DeleteNudge(nudgeId = nudgeData.nudgeId.orEmpty()))
|
||||
onNudgeEffect(
|
||||
NudgeEffect.OnDeleteNudge(
|
||||
nudgeId = nudgeData.nudgeId.orEmpty(),
|
||||
overlayItemTransitionState = OverlayItemTransitionState.SUCCESS_ACKNOWLEDGED,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun animateDismissBoxWidth(frontLayerVisible: Boolean, deleteNudge: () -> Unit): State<Dp> {
|
||||
val localConfiguration = LocalConfiguration.current
|
||||
val screenWidth = remember { localConfiguration.screenWidthDp.dp }
|
||||
val width by
|
||||
animateDpAsState(
|
||||
targetValue = if (frontLayerVisible) screenWidth - ANCHOR_DRAG_WIDTH else 0.dp,
|
||||
animationSpec = frontLayerAnimationSpec(),
|
||||
label = DISMISS_TEXT_SYNC_ANIMATION,
|
||||
) {
|
||||
deleteNudge()
|
||||
}
|
||||
return remember(width) { mutableStateOf(width) }
|
||||
}
|
||||
|
||||
private fun <T> frontLayerAnimationSpec(): FiniteAnimationSpec<T> =
|
||||
tween(400, easing = CubicBezierEasing(0.83f, 0.17f, 0.23f, 0.89f))
|
||||
|
||||
val getFrontLayerExitTransitionSpec = slideOutHorizontally(frontLayerAnimationSpec()) { -it }
|
||||
|
||||
enum class DragAnchors {
|
||||
Normal,
|
||||
Dragged,
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils
|
||||
|
||||
import androidx.compose.animation.ContentTransform
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeStatus
|
||||
import com.naviapp.screenOverlay.utils.NudgeConstants.NudgeAnimationConstants.COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION
|
||||
import com.naviapp.utils.dpToPx
|
||||
|
||||
object NudgeExpandedStateUtils {
|
||||
val getExpandedStateEnterTransitionSpec: EnterTransition =
|
||||
expandVertically(
|
||||
initialHeight = { dpToPx(70) },
|
||||
animationSpec = expandedStateAnimationSpec(),
|
||||
)
|
||||
|
||||
val getExpandedStateExitTransitionSpec: ExitTransition =
|
||||
shrinkVertically(
|
||||
targetHeight = { dpToPx(70) },
|
||||
animationSpec = expandedStateAnimationSpec(),
|
||||
)
|
||||
|
||||
val expandedNudgeExitTransition =
|
||||
fadeOut(tween(300)) +
|
||||
shrinkVertically(shrinkTowards = Alignment.Bottom, animationSpec = tween(300)) { -it }
|
||||
|
||||
private fun <T> expandedStateAnimationSpec(): FiniteAnimationSpec<T> =
|
||||
tween(200, easing = CubicBezierEasing(0.8f, 0.09f, 0.14f, 1f))
|
||||
}
|
||||
|
||||
object NudgeCollapsedStateUtils {
|
||||
val collapsedContainerExitTransition: ContentTransform =
|
||||
slideInHorizontally(tween(COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION)) { it }
|
||||
.togetherWith(
|
||||
slideOutHorizontally(tween(COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION)) { -it }
|
||||
)
|
||||
|
||||
val collapsedContainerRankChangeTransition: ContentTransform =
|
||||
slideInVertically(tween(COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION)) { it }
|
||||
.togetherWith(fadeOut(tween(COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION)))
|
||||
|
||||
val middlePillEnterTransitionSpec: EnterTransition = fadeIn(tween(200))
|
||||
|
||||
val middlePillExitTransitionSpec: ExitTransition = fadeOut(tween(100))
|
||||
|
||||
val middlePillContentTransitionSpec: ContentTransform =
|
||||
expandVertically(tween(durationMillis = 400)) { -it }
|
||||
.togetherWith(shrinkVertically(tween(durationMillis = 400)) { it })
|
||||
}
|
||||
|
||||
object NudgeRootUtils {
|
||||
fun filterDeletedNudges(nudgeState: NudgeState): List<NudgeData> =
|
||||
nudgeState.nudgeList?.filterNot { it.nudgeStatus == NudgeStatus.DELETED }.orEmpty()
|
||||
|
||||
val nudgeContainerEnterTransition = slideInVertically(tween(300)) { it }
|
||||
|
||||
@Composable
|
||||
fun HandleExpandedStateByListSize(
|
||||
state: () -> NudgeState,
|
||||
onEventSent: (event: NudgeEvent) -> Unit,
|
||||
) {
|
||||
LaunchedEffect(key1 = filterDeletedNudges(state()).size) {
|
||||
if (state().isNudgeExpanded && filterDeletedNudges(state()).size <= 1) {
|
||||
onEventSent(NudgeEvent.UpdateNudgeExpandedState(expandState = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils.animation
|
||||
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.ContentTransform
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.SizeTransform
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.keyframes
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeId
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeAnimationConstants
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeAnimationConstants.defaultAnimationSpec
|
||||
|
||||
// --------------------- Nudge Visibility ---------------------
|
||||
fun nudgeEnterAnimation() = slideInVertically(defaultAnimationSpec()) { it.times(1.4).toInt() }
|
||||
|
||||
fun nudgeExitAnimation() = ExitTransition.None
|
||||
|
||||
// --------------------- Nudge Collapsed State container transitions ---------------------
|
||||
fun AnimatedContentTransitionScope<NudgeData?>.nudgeCollapsedStateTransitionSpec(
|
||||
filteredNudgeList: List<NudgeData>?,
|
||||
initialNudgeId: NudgeId,
|
||||
isDescriptiveViewEnabled: Boolean,
|
||||
): ContentTransform {
|
||||
if (isDescriptiveViewEnabled) return EnterTransition.None togetherWith ExitTransition.None
|
||||
return if (filteredNudgeList?.any { it.nudgeId == initialNudgeId } == true) {
|
||||
collapsedContainerRankChangeTransition()
|
||||
} else {
|
||||
collapsedContainerElementChangeTransition()
|
||||
} using
|
||||
SizeTransform { old, new ->
|
||||
keyframes {
|
||||
IntSize(new.width, old.height) at NudgeAnimationConstants.DURATION
|
||||
durationMillis = NudgeAnimationConstants.DURATION
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun collapsedContainerRankChangeTransition(): ContentTransform {
|
||||
return slideInVertically(defaultAnimationSpec()) { it }
|
||||
.togetherWith(fadeOut(defaultAnimationSpec()))
|
||||
}
|
||||
|
||||
private fun collapsedContainerElementChangeTransition(): ContentTransform {
|
||||
val animationSpec: FiniteAnimationSpec<IntOffset> =
|
||||
tween(
|
||||
durationMillis = NudgeAnimationConstants.DURATION,
|
||||
easing = NudgeAnimationConstants.AnimationEasing,
|
||||
delayMillis = NudgeAnimationConstants.COLLAPSED_NUDGE_ELEMENT_TRANSITION_DELAY,
|
||||
)
|
||||
|
||||
return slideInHorizontally(animationSpec) { it }
|
||||
.togetherWith(slideOutHorizontally(animationSpec) { -it })
|
||||
}
|
||||
|
||||
// --------------------- Nudge descriptive view Transitions ---------------------
|
||||
fun nudgeDescriptiveViewEnterAnimation(firstWidgetHeight: Int): EnterTransition {
|
||||
return slideInVertically(defaultAnimationSpec()) { 0 } +
|
||||
expandVertically(defaultAnimationSpec(), expandFrom = Alignment.Top) { firstWidgetHeight }
|
||||
}
|
||||
|
||||
fun nudgeDescriptiveViewExitAnimation(firstWidgetHeight: Int): ExitTransition {
|
||||
return slideOutVertically(defaultAnimationSpec()) { it - firstWidgetHeight } +
|
||||
shrinkVertically(defaultAnimationSpec(), shrinkTowards = Alignment.Bottom) {
|
||||
firstWidgetHeight
|
||||
}
|
||||
}
|
||||
|
||||
fun nudgeExitTransitionInDescriptiveView(): ExitTransition {
|
||||
return shrinkVertically(defaultAnimationSpec(), shrinkTowards = Alignment.Bottom) { -it }
|
||||
}
|
||||
|
||||
// --------------------- Front Layer transitions ---------------------
|
||||
fun nudgeFrontLayerExitAnimationSpec(): ExitTransition {
|
||||
return slideOutHorizontally(defaultAnimationSpec()) { -it }
|
||||
}
|
||||
|
||||
// --------------------- Nudge header visibility transitions ---------------------
|
||||
fun nudgeHeaderEnterAnimation(): EnterTransition {
|
||||
return fadeIn(defaultAnimationSpec())
|
||||
.plus(slideInVertically(defaultAnimationSpec()) { it })
|
||||
.plus(expandVertically(defaultAnimationSpec()))
|
||||
}
|
||||
|
||||
fun nudgeHeaderExitAnimation(): ExitTransition {
|
||||
return fadeOut(defaultAnimationSpec())
|
||||
.plus(slideOutVertically(defaultAnimationSpec()) { it })
|
||||
.plus(shrinkVertically(defaultAnimationSpec()))
|
||||
}
|
||||
|
||||
// --------------------- Nudge Middle Pill transitions ---------------------
|
||||
fun nudgeMiddlePillEnterAnimationSpec(delayMillis: Int = 0): EnterTransition {
|
||||
return slideInVertically(defaultAnimationSpec(delayMillis)) { -it }
|
||||
.plus(expandVertically(defaultAnimationSpec(delayMillis)))
|
||||
}
|
||||
|
||||
fun nudgeMiddlePillExitAnimationSpec(): ExitTransition {
|
||||
return slideOutVertically(defaultAnimationSpec()) { -it }
|
||||
.plus(shrinkVertically(defaultAnimationSpec()) { 0 })
|
||||
}
|
||||
|
||||
// --------------------- Nudge Dismissible Actions transition ------------------
|
||||
fun nudgeDismissibleActionExitFadeAnimation(): ExitTransition = fadeOut(defaultAnimationSpec())
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils.constants
|
||||
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.TweenSpec
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
object NudgeAnimationConstants {
|
||||
const val DURATION = 400
|
||||
const val SUCCESS_DELAY = 1300L
|
||||
val AnimationEasing = CubicBezierEasing(0.8f, 0.09f, 0.14f, 1f)
|
||||
const val COLLAPSED_NUDGE_ELEMENT_TRANSITION_DELAY = 200
|
||||
|
||||
fun <T> defaultAnimationSpec(delayMillis: Int = 0): TweenSpec<T> {
|
||||
return tween(durationMillis = DURATION, easing = AnimationEasing, delayMillis = delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
object NudgeColor {
|
||||
val borderColor = Color(0xFFE3E5E5)
|
||||
val darkTextColor = Color(0xFF1F002A)
|
||||
val shadowColor = Color(0xFF535252)
|
||||
}
|
||||
|
||||
object NudgeConstants {
|
||||
const val MIDDLE_PILL_CLICKED = "nudge_middle_pill_clicked"
|
||||
val dismissibleActionHorizontalPadding = 26.dp
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils.extensions
|
||||
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeStatus
|
||||
|
||||
fun List<NudgeData>?.filterDeletedNudges(): List<NudgeData> {
|
||||
return this?.filterNot { it.nudgeStatus == NudgeStatus.DISMISSED }.orEmpty()
|
||||
}
|
||||
|
||||
fun List<NudgeData>?.getFirstNonDismissedNudge(): NudgeData? {
|
||||
return this?.firstOrNull { it.nudgeStatus != NudgeStatus.DISMISSED }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils.mapper
|
||||
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
|
||||
fun mapToTransitionState(overlayItemState: String): OverlayItemTransitionState {
|
||||
return when (overlayItemState) {
|
||||
OverlayItemTransitionState.SUCCESS_ACKNOWLEDGED.name -> {
|
||||
OverlayItemTransitionState.SUCCESS_ACKNOWLEDGED
|
||||
}
|
||||
|
||||
OverlayItemTransitionState.COMPLETED.name -> {
|
||||
OverlayItemTransitionState.COMPLETED
|
||||
}
|
||||
|
||||
else -> OverlayItemTransitionState.PAUSED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils.measurement
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextMeasurer
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.navi.design.font.FontWeightEnum
|
||||
import com.navi.design.font.getFontWeight
|
||||
import com.navi.design.font.naviFontFamily
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeDismissibleActionData
|
||||
|
||||
@Composable
|
||||
fun getMaxWidthForDismissibleActionItem(actions: List<NudgeDismissibleActionData>?): Int {
|
||||
val density = LocalDensity.current
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val baseWidthPx = with(density) { 56.dp.toPx().toInt() }
|
||||
|
||||
if (actions.isNullOrEmpty()) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return remember(
|
||||
key1 = actions.map { it.actionText },
|
||||
calculation = {
|
||||
val textWidths =
|
||||
actions
|
||||
.filter { it.actionText != null }
|
||||
.map { action -> measureActionWidth(textMeasurer, action.actionText!!) }
|
||||
val maxTextWidth = textWidths.maxOrNull() ?: 0
|
||||
maxOf(maxTextWidth, baseWidthPx)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun measureActionWidth(textMeasurer: TextMeasurer, content: String): Int =
|
||||
textMeasurer.measure(text = content, style = actionTextStyle).size.width
|
||||
|
||||
private val actionTextStyle =
|
||||
TextStyle(
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 18.sp,
|
||||
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
|
||||
fontFamily = naviFontFamily,
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils.modifier
|
||||
|
||||
import androidx.compose.ui.draw.DrawModifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
data class BorderSides(
|
||||
val top: Boolean = false,
|
||||
val bottom: Boolean = false,
|
||||
val start: Boolean = false,
|
||||
val end: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
val NONE = BorderSides()
|
||||
val ALL = BorderSides(top = true, bottom = true, start = true, end = true)
|
||||
val HORIZONTAL = BorderSides(start = true, end = true)
|
||||
val VERTICAL = BorderSides(top = true, bottom = true)
|
||||
}
|
||||
}
|
||||
|
||||
class DirectionalBorderModifier(
|
||||
private val directionBorders: BorderSides,
|
||||
private val width: Dp,
|
||||
private val color: Color,
|
||||
) : DrawModifier {
|
||||
override fun ContentDrawScope.draw() {
|
||||
drawContent()
|
||||
|
||||
val strokeWidth = width.toPx()
|
||||
|
||||
if (directionBorders.top) {
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(0f, 0f),
|
||||
end = Offset(size.width, 0f),
|
||||
strokeWidth = strokeWidth,
|
||||
)
|
||||
}
|
||||
|
||||
if (directionBorders.bottom) {
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(0f, size.height),
|
||||
end = Offset(size.width, size.height),
|
||||
strokeWidth = strokeWidth,
|
||||
)
|
||||
}
|
||||
|
||||
if (directionBorders.start) {
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(0f, 0f),
|
||||
end = Offset(0f, size.height),
|
||||
strokeWidth = strokeWidth,
|
||||
)
|
||||
}
|
||||
|
||||
if (directionBorders.end) {
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(size.width, 0f),
|
||||
end = Offset(size.width, size.height),
|
||||
strokeWidth = strokeWidth,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.nudge.utils.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.naviapp.screenOverlay.model.OverlayItemTransitionState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeAnimationConstants
|
||||
import com.naviapp.screenOverlay.nudge.utils.constants.NudgeAnimationConstants.SUCCESS_DELAY
|
||||
import com.naviapp.screenOverlay.nudge.utils.extensions.filterDeletedNudges
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
fun HandleExpandedStateByListSize(state: () -> NudgeState, onEvent: (event: NudgeEvent) -> Unit) {
|
||||
val nudgeList = state().nudgeList.filterDeletedNudges()
|
||||
LaunchedEffect(
|
||||
key1 = nudgeList.size,
|
||||
block = {
|
||||
if (state().descriptiveViewEnabled && nudgeList.size <= 1) {
|
||||
delay(NudgeAnimationConstants.DURATION.toLong())
|
||||
onEvent(NudgeEvent.ToggleDescriptiveView(enabled = false))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: When the status is set to SUCCESS, a 1300ms(300ms is success animation time) delay is
|
||||
* introduced before triggering the deletion of the nudge element to allow for a delete animation.
|
||||
*/
|
||||
@Composable
|
||||
fun HandleNudgeStatusChange(
|
||||
nudgeData: NudgeData,
|
||||
onNudgeEvent: (nudgeEvent: NudgeEvent) -> Unit,
|
||||
onNudgeEffect: (effect: NudgeEffect) -> Unit,
|
||||
) {
|
||||
LaunchedEffect(
|
||||
key1 = nudgeData.nudgeStatus,
|
||||
block = {
|
||||
if (nudgeData.nudgeStatus == NudgeStatus.SUCCESS) {
|
||||
delay(SUCCESS_DELAY)
|
||||
onNudgeEvent(NudgeEvent.DismissNudge(id = nudgeData.nudgeId.orEmpty()))
|
||||
onNudgeEffect(
|
||||
NudgeEffect.OnNudgeStateUpdate(
|
||||
id = nudgeData.nudgeId.orEmpty(),
|
||||
transitionState = OverlayItemTransitionState.SUCCESS_ACKNOWLEDGED,
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * Copyright © 2024-2025 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.naviapp.screenOverlay.utils
|
||||
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
object PopupConstants {
|
||||
const val CLOSE_ALL = "Close all"
|
||||
const val POPUP = "popup"
|
||||
@@ -19,19 +17,10 @@ object PopupConstants {
|
||||
}
|
||||
|
||||
object NudgeConstants {
|
||||
const val DISMISS = "Dismiss"
|
||||
const val HOME_NUDGE = "HOME_NUDGE"
|
||||
const val YOUR_PAYMENTS = "Your payments"
|
||||
const val MORE = "more"
|
||||
const val UNPAID_BILLS_RECHARGES = "Unpaid bills/recharges"
|
||||
const val UNPAID = " unpaid"
|
||||
const val NUDGE = "nudge"
|
||||
const val NUDGE_ID = "nudgeId"
|
||||
const val NUDGE_DISMISSED_EVENT = "home_page_nudge_dismissed"
|
||||
val ANCHOR_DRAG_WIDTH = 70.dp
|
||||
|
||||
object NudgeAnimationConstants {
|
||||
const val DISMISS_TEXT_SYNC_ANIMATION = "DismissTextSyncAnimation"
|
||||
const val REPLACE_NUDGE_IN_COLLAPSED_STATE = "replaceNudgeInCollapsedState"
|
||||
const val COLLAPSED_NUDGE_ELEMENT_TRANSITION_DURATION = 300
|
||||
const val MIDDLE_PILL_CONTENT_ANIMATION = "MiddlePillContentAnimation"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ import com.naviapp.screenOverlay.model.OverlayItemActionData
|
||||
import com.naviapp.screenOverlay.model.OverlayItemStateUpdate
|
||||
import com.naviapp.screenOverlay.model.OverlayItemsStateUpdates
|
||||
import com.naviapp.screenOverlay.model.ScreenOverlayActionUpdateRequest
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeListData
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.model.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.reducer.NudgeReducer
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeListData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.NudgeStatus
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.data.StaticNudgeData
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.domain.reducer.NudgeReducer
|
||||
import com.naviapp.screenOverlay.popup.model.PopupEvent
|
||||
import com.naviapp.screenOverlay.popup.model.PopupListData
|
||||
import com.naviapp.screenOverlay.popup.model.PopupState
|
||||
@@ -186,7 +186,8 @@ constructor(
|
||||
private fun onActionTriggered(uiTronAction: UiTronAction?) {
|
||||
uiTronAction?.let {
|
||||
screenOverlayUitronActionHandler.onActionTriggered(
|
||||
it,
|
||||
state = nudgeState.value,
|
||||
uiTronAction = it,
|
||||
sendEvent = { event -> sendEvent(event) },
|
||||
lastClickedNudgeId = { nudgeId -> updateLastClickedNudgeId(nudgeId = nudgeId) },
|
||||
ctaAction = { ctaData ->
|
||||
|
||||
@@ -15,9 +15,9 @@ import com.navi.common.viewmodel.BaseVM
|
||||
import com.naviapp.screenOverlay.bottomsheet.model.BottomSheetEffect
|
||||
import com.naviapp.screenOverlay.bottomsheet.model.BottomSheetEvent
|
||||
import com.naviapp.screenOverlay.bottomsheet.model.BottomSheetState
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.model.NudgeState
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.effect.NudgeEffect
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.event.NudgeEvent
|
||||
import com.naviapp.screenOverlay.nudge.domain.model.state.NudgeState
|
||||
import com.naviapp.screenOverlay.popup.model.PopupEffect
|
||||
import com.naviapp.screenOverlay.popup.model.PopupEvent
|
||||
import com.naviapp.screenOverlay.popup.model.PopupState
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.google.gson.JsonElement
|
||||
import com.navi.common.uitron.deserializer.UiTronActionDeserializer
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeClickAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeCtaAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeDragAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeUiTronActions
|
||||
import com.naviapp.screenOverlay.popup.uitronAction.PopupDismissAction
|
||||
@@ -32,6 +33,8 @@ class HomeCustomActionDeserializer : UiTronActionDeserializer() {
|
||||
context?.deserialize(jsonObject, NudgeDragAction::class.java)
|
||||
NudgeUiTronActions.NUDGE_CLICKED.name ->
|
||||
context?.deserialize(jsonObject, NudgeClickAction::class.java)
|
||||
NudgeUiTronActions.NUDGE_CTA_ACTION.name ->
|
||||
context?.deserialize(jsonObject, NudgeCtaAction::class.java)
|
||||
PopupUitronActions.DISMISS_POPUP.name ->
|
||||
context?.deserialize(jsonObject, PopupDismissAction::class.java)
|
||||
else -> super.deserialize(json, typeOfT, context)
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.google.gson.JsonSerializationContext
|
||||
import com.navi.common.uitron.serializer.UiTronActionSerializer
|
||||
import com.navi.uitron.model.data.UiTronAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeClickAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeCtaAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeDragAction
|
||||
import com.naviapp.screenOverlay.nudge.uitronAction.NudgeUiTronActions
|
||||
import com.naviapp.screenOverlay.popup.uitronAction.PopupDismissAction
|
||||
@@ -30,6 +31,8 @@ class HomeCustomActionSerializer : UiTronActionSerializer() {
|
||||
context?.serialize(src as NudgeDragAction, NudgeDragAction::class.java)
|
||||
NudgeUiTronActions.NUDGE_CLICKED.name ->
|
||||
context?.serialize(src as NudgeDragAction, NudgeClickAction::class.java)
|
||||
NudgeUiTronActions.NUDGE_CTA_ACTION.name ->
|
||||
context?.serialize(src as NudgeCtaAction, NudgeCtaAction::class.java)
|
||||
PopupUitronActions.DISMISS_POPUP.name ->
|
||||
context?.serialize(src as PopupDismissAction, PopupDismissAction::class.java)
|
||||
else -> super.serialize(src, typeOfSrc, context)
|
||||
|
||||
10
android/app/src/main/res/drawable/cheveron_up.xml
Normal file
10
android/app/src/main/res/drawable/cheveron_up.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="13dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="13"
|
||||
android:viewportHeight="12">
|
||||
<path
|
||||
android:pathData="M3.148,7.853C2.953,7.658 2.857,7.241 3.052,7.046L6.052,4.046C6.247,3.851 6.756,3.851 6.951,4.046L9.951,7.046C10.146,7.241 10.05,7.658 9.855,7.853C9.66,8.048 9.247,8.148 9.052,7.953L6.501,5.406L3.951,7.953C3.756,8.148 3.343,8.048 3.148,7.853Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
Reference in New Issue
Block a user