NTP-4792 | Mehul | project nexus rcbp new landing page (#12882)

Co-authored-by: kishan kumar <kishan.kumar@navi.com>
This commit is contained in:
Mehul Garg
2024-10-08 17:43:06 +05:30
committed by GitHub
parent db12d67624
commit 4a17a68e63
24 changed files with 961 additions and 88 deletions

View File

@@ -11,11 +11,13 @@ import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import java.io.Serializable
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
@Parcelize
open class LineItem(
@SerializedName("key") val key: String? = null,
@SerializedName("value") var value: String? = null,
@SerializedName("payload") var payload: @RawValue Any? = null,
@SerializedName("expressionValue") val expressionValue: String? = null,
@SerializedName("subtitle") val subtitle: String? = null,
@SerializedName("subValue") val subValue: String? = null,

View File

@@ -66,7 +66,6 @@ dependencies {
implementation project(":navi-rr")
implementation libs.accompanist.permissions
implementation libs.accompanist.systemuicontroller
implementation libs.android.material
implementation libs.androidx.appcompat
implementation libs.androidx.compose.material3
implementation libs.androidx.constraintlayout

View File

@@ -14,13 +14,19 @@ import com.navi.base.AppServiceManager
import com.navi.bbps.R
import com.navi.common.network.models.ErrorMessage
import com.navi.common.network.models.RepoResult
import com.navi.common.utils.registerUiTronDeSerializers
import com.navi.common.utils.registerUiTronSerializer
import java.lang.reflect.Type
import java.nio.charset.StandardCharsets
import org.joda.time.DateTime
class MockUtils {
private val mockGson =
GsonBuilder().registerTypeAdapter(DateTime::class.java, DateTimeConverterAdapter()).create()
GsonBuilder()
.registerTypeAdapter(DateTime::class.java, DateTimeConverterAdapter())
.registerUiTronDeSerializers()
.registerUiTronSerializer()
.create()
fun <T> mockApiResponse(type: Type, jsonKey: String): RepoResult<T> {
val inputStream =

View File

@@ -78,6 +78,13 @@ class NaviBbpsAnalytics private constructor() {
)
}
fun logABExperimentFailure(source: String, error: String) {
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviBBPS_CategoryPage_FetchABExperimentFailed",
eventValues = mapOf(NAVI_BBPS_SOURCE to source, "error" to error)
)
}
fun onCategorySelected(
source: String,
categoryGroupId: String,

View File

@@ -103,6 +103,7 @@ const val AB_TESTING_LOAD_FROM_SERIES_MAPPING_EXPERIMENT_NAME = "bbps-loadcofrom
const val AB_TESTING_BILL_REMINDER_EXPERIMENT_NAME = "bbps-bill-reminder"
const val AB_TESTING_COIN_UTILISATION_EXPERIMENT_NAME = "bbps-coin-utilisation"
const val AB_TESTING_CREDIT_CARD_V2_EXPERIMENT_NAME = "bbps-credit-card-v2"
const val AB_NEW_LANDING_PAGE_ENABLED_EXPERIMENT_NAME = "NaviBBPS-Project-Nexus-Landing-Page"
// Navi Bbps Constants
const val NAVI_PAY_NUDGE_DETAILS_AMOUNT = "1000.0"
@@ -144,3 +145,15 @@ const val NAVI_BBPS_BURN_NUDGE_TEXT = "navi_bbps_burn_nudge_text"
// bbps rewards constants
const val NAVI_PAY_REWARDS_EXCHANGE_RATIO = "10 = ₹1"
// bbps uitron constants
const val NAVI_BBPS_NAVIGATION_CUSTOM = "CUSTOM"
const val NAVI_BBPS_NAVIGATION_BOTTOM_SHEET = "BOTTOM_SHEET"
const val NAVI_BBPS_NAVIGATION_FINISH = "FINISH"
// bbps bill category screen constants
const val NAVI_BBPS_CATEGORY_GROUP_ID = "categoryGroupId"
const val NAVI_BBPS_PAYLOAD = "payload"
// bbps experiments constants
const val NAVI_BBPS_LITMUS = "LITMUS"

View File

@@ -10,6 +10,7 @@ package com.navi.bbps.common
enum class NaviBbpsScreen {
NAVI_BBPS_MAIN,
NAVI_BBPS_BILL_CATEGORIES,
NAVI_BBPS_BILL_CATEGORIES_V2,
NAVI_BBPS_MY_SAVED_BILLS,
NAVI_BBPS_MY_BILL_HISTORY_DETAILS,
NAVI_BBPS_BILLER_LIST,

View File

@@ -35,7 +35,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
abstract class NaviBbpsBaseVM(val naviBbpsVmData: NaviBbpsVmData) : BaseVM() {
abstract class NaviBbpsBaseVM(open val naviBbpsVmData: NaviBbpsVmData) : BaseVM() {
private companion object {
const val ERROR_DEFAULT_TAG = "default"

View File

@@ -12,11 +12,14 @@ import android.content.Intent
import android.os.Bundle
import com.navi.base.deeplink.DeepLinkManager
import com.navi.base.model.CtaData
import com.navi.base.sharedpref.PreferenceManager
import com.navi.bbps.common.AB_NEW_LANDING_PAGE_ENABLED_EXPERIMENT_NAME
import com.navi.bbps.common.NaviBbpsScreen
import com.navi.bbps.common.SOURCE_MODULE
import com.navi.bbps.common.START_SCREEN_NAME
import com.navi.bbps.feature.destinations.BbpsRoutingLauncherScreenDestination
import com.navi.bbps.feature.destinations.BillCategoriesScreenDestination
import com.navi.bbps.feature.destinations.BillCategoriesScreenV2Destination
import com.ramcosta.composedestinations.spec.Route
object NaviBbpsRouter {
@@ -54,7 +57,7 @@ object NaviBbpsRouter {
fun getStartRoute(startScreenName: String): Route {
return when (startScreenName) {
NaviBbpsScreen.NAVI_BBPS_BILL_CATEGORIES.name -> BillCategoriesScreenDestination
NaviBbpsScreen.NAVI_BBPS_BILL_CATEGORIES.name -> getBillCategoriesRoute()
else -> BbpsRoutingLauncherScreenDestination
}
}
@@ -65,4 +68,14 @@ object NaviBbpsRouter {
?.navigateTo(activity = naviBbpsActivity, ctaData = it, finish = false)
}
}
private fun getBillCategoriesRoute(): Route {
val isNewLandingPageEnabled =
PreferenceManager.getBooleanPreference(AB_NEW_LANDING_PAGE_ENABLED_EXPERIMENT_NAME)
return if (isNewLandingPageEnabled) {
BillCategoriesScreenV2Destination
} else {
BillCategoriesScreenDestination
}
}
}

View File

@@ -7,11 +7,16 @@
package com.navi.bbps.feature.category
import com.navi.bbps.common.NAVI_BBPS_LITMUS
import com.navi.bbps.common.NaviBbpsScreen
import com.navi.bbps.feature.category.model.network.BillCategoriesResponse
import com.navi.bbps.feature.category.model.network.RewardDetailsResponse
import com.navi.bbps.network.service.NaviBbpsRetrofitService
import com.navi.common.alchemist.model.AlchemistScreenDefinition
import com.navi.common.model.ModuleNameV2
import com.navi.common.network.models.RepoResult
import com.navi.common.network.retrofit.ResponseCallback
import com.navi.common.utils.Constants.GZIP
import javax.inject.Inject
class BillCategoriesRepository
@@ -24,4 +29,17 @@ constructor(private val naviBbpsRetrofitService: NaviBbpsRetrofitService) : Resp
suspend fun fetchRewardDetails(): RepoResult<RewardDetailsResponse> {
return apiResponseCallback(naviBbpsRetrofitService.getRewardDetails())
}
suspend fun fetchAlchemistScreenConfig(): RepoResult<AlchemistScreenDefinition> {
return apiResponseCallback(
naviBbpsRetrofitService.fetchAlchemistScreenUiTronConfigs(
acceptEncoding = GZIP,
target = ModuleNameV2.ALCHEMIST.name,
screenId = NaviBbpsScreen.NAVI_BBPS_BILL_CATEGORIES_V2.name
)
)
}
suspend fun fetchLitmusABExperiment(experimentName: String) =
naviBbpsRetrofitService.fetchABExperiment(name = experimentName, header = NAVI_BBPS_LITMUS)
}

View File

@@ -17,12 +17,14 @@ import com.navi.base.cache.datastore.DataStoreInfoProvider
import com.navi.base.cache.model.NaviCacheAltSourceEntity
import com.navi.base.cache.repository.NaviCacheRepository
import com.navi.base.model.CtaData
import com.navi.base.sharedpref.PreferenceManager
import com.navi.base.utils.EMPTY
import com.navi.base.utils.ResourceProvider
import com.navi.base.utils.isNotNull
import com.navi.base.utils.orFalse
import com.navi.base.utils.orZero
import com.navi.bbps.R
import com.navi.bbps.common.AB_NEW_LANDING_PAGE_ENABLED_EXPERIMENT_NAME
import com.navi.bbps.common.AB_TESTING_BILL_REMINDER_EXPERIMENT_NAME
import com.navi.bbps.common.AB_TESTING_COIN_UTILISATION_EXPERIMENT_NAME
import com.navi.bbps.common.CASH_REWARDS
@@ -100,13 +102,13 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@HiltViewModel
class BillCategoriesViewModel
open class BillCategoriesViewModel
@Inject
constructor(
savedStateHandle: SavedStateHandle,
private val dispatcherProvider: CoroutineDispatcherProvider,
private val billCategoriesRepository: BillCategoriesRepository,
private val naviCacheRepository: NaviCacheRepository,
protected val naviCacheRepository: NaviCacheRepository,
private val myBillsSyncJob: MyBillsSyncJob,
private val myBillsRepository: MyBillsRepository,
private val bbpsResourceProvider: ResourceProvider,
@@ -147,9 +149,9 @@ constructor(
private val _navigateToNextScreen = MutableSharedFlow<Direction>()
val navigateToNextScreen = _navigateToNextScreen.asSharedFlow()
private val source = savedStateHandle.get<String>("source").orEmpty()
val source = savedStateHandle.get<String>("source").orEmpty()
private val initialSource = savedStateHandle.get<String>("initialSource").orEmpty()
val initialSource = savedStateHandle.get<String>("initialSource").orEmpty()
private val naviBbpsAnalytics: NaviBbpsAnalytics.BillCategories =
NaviBbpsAnalytics.INSTANCE.BillCategories()
@@ -160,19 +162,25 @@ constructor(
private val _isCoinUtilisationExperimentEnabled = MutableStateFlow(false)
val isCoinUtilisationExperimentEnabled = _isCoinUtilisationExperimentEnabled.asStateFlow()
var naviBbpsDefaultConfig = NaviBbpsDefaultConfig()
private var naviBbpsDefaultConfig = NaviBbpsDefaultConfig()
private val isNewLandingPageEnabled =
PreferenceManager.getBooleanPreference(AB_NEW_LANDING_PAGE_ENABLED_EXPERIMENT_NAME)
init {
viewModelScope.launch(dispatcherProvider.default) {
initConfig()
fetchRewardDetails()
observeMyBillCounts()
fetchBillCategories()
fetchMyBills()
fetchBillCategoriesABExperiment()
if (!isNewLandingPageEnabled) {
fetchRewardDetails()
fetchBillCategories()
}
}
}
private suspend fun initConfig() {
protected suspend fun initConfig() {
naviBbpsDefaultConfig = naviBbpsConfigUseCase.getDefaultConfig()
}
@@ -268,7 +276,7 @@ constructor(
)
}
private suspend fun navigateToNextScreen(
protected suspend fun navigateToNextScreen(
billDetails: BillDetailsResponse,
billerDetails: BillerDetailsEntity,
myBillEntity: MyBillEntity
@@ -749,7 +757,7 @@ constructor(
)
}
private fun coinBurnRewardEntityUpdate(
protected fun coinBurnRewardEntityUpdate(
rewardDetailsResponse: RepoResult<RewardDetailsResponse>
) {
updateRewardsDetailsEntity(
@@ -1005,4 +1013,24 @@ constructor(
private fun updateRewardsDetailsEntity(rewardDataEntity: RewardDataEntity) {
_rewardsDetailsEntity.update { rewardDataEntity }
}
private fun fetchBillCategoriesABExperiment() {
viewModelScope.launch(dispatcherProvider.io) {
val response =
billCategoriesRepository.fetchLitmusABExperiment(
AB_NEW_LANDING_PAGE_ENABLED_EXPERIMENT_NAME
)
if (response.isSuccessful) {
PreferenceManager.setBooleanPreference(
AB_NEW_LANDING_PAGE_ENABLED_EXPERIMENT_NAME,
response.body()?.result.orFalse()
)
} else {
naviBbpsAnalytics.logABExperimentFailure(
source = source,
error = response.errorBody()?.string().orEmpty()
)
}
}
}
}

View File

@@ -0,0 +1,158 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.category
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.navi.base.cache.datastore.DataStoreInfoProvider
import com.navi.base.cache.model.NaviCacheAltSourceEntity
import com.navi.base.cache.repository.NaviCacheRepository
import com.navi.base.utils.ResourceProvider
import com.navi.bbps.common.CoinsSyncManager
import com.navi.bbps.common.LocalJsonDataSource
import com.navi.bbps.common.NaviBbpsScreen
import com.navi.bbps.common.model.NaviBbpsVmData
import com.navi.bbps.common.model.view.NaviBbpsSessionHelper
import com.navi.bbps.common.usecase.GetABTestingExperimentUseCase
import com.navi.bbps.common.usecase.NaviBbpsConfigUseCase
import com.navi.bbps.common.utils.BillDetailsResponseToEntityMapper
import com.navi.bbps.common.utils.MyBillEntityToBillCategoryEntityMapper
import com.navi.bbps.common.utils.MyBillEntityToBillerDetailsEntityMapper
import com.navi.bbps.feature.category.model.view.BillCategoryStateV2
import com.navi.bbps.feature.contactlist.PhoneContactManager
import com.navi.bbps.feature.mybills.DismissBillHandler
import com.navi.bbps.feature.mybills.MyBillsRepository
import com.navi.bbps.feature.mybills.MyBillsSyncJob
import com.navi.bbps.network.di.NaviBbpsGsonBuilder
import com.navi.common.alchemist.model.AlchemistScreenDefinition
import com.navi.common.constants.DBCacheConstants
import com.navi.common.di.CoroutineDispatcherProvider
import com.navi.common.network.models.isSuccessWithData
import dagger.Lazy
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@HiltViewModel
class BillCategoriesViewModelV2
@Inject
constructor(
savedStateHandle: SavedStateHandle,
dispatcherProvider: CoroutineDispatcherProvider,
private val billCategoriesRepository: BillCategoriesRepository,
naviCacheRepository: NaviCacheRepository,
myBillsSyncJob: MyBillsSyncJob,
myBillsRepository: MyBillsRepository,
bbpsResourceProvider: ResourceProvider,
localJsonDataSource: LocalJsonDataSource,
dismissBillHandler: DismissBillHandler,
myBillEntityToBillerDetailsEntityMapper: MyBillEntityToBillerDetailsEntityMapper,
billDetailsResponseToEntityMapper: BillDetailsResponseToEntityMapper,
myBillEntityToBillCategoryEntityMapper: MyBillEntityToBillCategoryEntityMapper,
getABTestingExperimentUseCase: GetABTestingExperimentUseCase,
phoneContactManager: Lazy<PhoneContactManager>,
naviBbpsSessionHelper: NaviBbpsSessionHelper,
coinsSyncManager: CoinsSyncManager,
naviBbpsConfigUseCase: NaviBbpsConfigUseCase,
resProvider: ResourceProvider,
dataStoreInfoProvider: DataStoreInfoProvider,
@NaviBbpsGsonBuilder val naviBbpsGson: Gson
) :
BillCategoriesViewModel(
savedStateHandle = savedStateHandle,
dispatcherProvider = dispatcherProvider,
billCategoriesRepository = billCategoriesRepository,
myBillsSyncJob = myBillsSyncJob,
naviCacheRepository = naviCacheRepository,
myBillsRepository = myBillsRepository,
bbpsResourceProvider = bbpsResourceProvider,
localJsonDataSource = localJsonDataSource,
dismissBillHandler = dismissBillHandler,
myBillEntityToBillerDetailsEntityMapper = myBillEntityToBillerDetailsEntityMapper,
billDetailsResponseToEntityMapper = billDetailsResponseToEntityMapper,
myBillEntityToBillCategoryEntityMapper = myBillEntityToBillCategoryEntityMapper,
getABTestingExperimentUseCase = getABTestingExperimentUseCase,
phoneContactManager = phoneContactManager,
naviBbpsSessionHelper = naviBbpsSessionHelper,
coinsSyncManager = coinsSyncManager,
naviBbpsConfigUseCase = naviBbpsConfigUseCase,
resProvider = resProvider,
dataStoreInfoProvider = dataStoreInfoProvider,
naviBbpsGson = naviBbpsGson
),
BottomSheetController by BottomSheetControllerImpl() {
override val naviBbpsVmData: NaviBbpsVmData
get() = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_BILL_CATEGORIES_V2)
private val _billCategoriesStateV2 =
MutableStateFlow<BillCategoryStateV2>(BillCategoryStateV2.Loading)
val billCategoriesStateV2 = _billCategoriesStateV2.asStateFlow()
init {
viewModelScope.launch(dispatcherProvider.io) {
launch { fetchCategories() }
launch { billCategoriesStateV2.collect { initBottomSheetHandler() } }
}
}
private fun initBottomSheetHandler() {
val screenStructure =
(billCategoriesStateV2.value as? BillCategoryStateV2.Loaded)
?.billCategories
?.screenStructure
screenStructure?.bottomSheets?.let { bottomSheets -> initializeBottomSheets(bottomSheets) }
}
private suspend fun fetchCategories() {
naviCacheRepository
.getDataAndFetchFromAltSource(
key = DBCacheConstants.BBPS_BILL_CATEGORIES_LIST_ALCHEMIST_CACHE_KEY,
version = 1,
getDataFromAltSource = ::fetchCategoriesFromApi,
isCurrentAndAltDataSame = { currentData, altData -> false },
emitMultipleValues = false
)
.collect { categories ->
val parsedCategories =
naviBbpsGson.fromJson<AlchemistScreenDefinition>(
categories?.value,
object : TypeToken<AlchemistScreenDefinition>() {}.type
)
if (parsedCategories != null) {
_billCategoriesStateV2.update {
BillCategoryStateV2.Loaded(billCategories = parsedCategories)
}
} else {
_billCategoriesStateV2.update { BillCategoryStateV2.Error }
}
}
}
private suspend fun fetchCategoriesFromApi(): NaviCacheAltSourceEntity {
val billCategoriesResponseFromAlchemist =
billCategoriesRepository.fetchAlchemistScreenConfig()
if (!billCategoriesResponseFromAlchemist.isSuccessWithData()) {
return NaviCacheAltSourceEntity(isSuccess = false)
}
return NaviCacheAltSourceEntity(
value = naviBbpsGson.toJson(billCategoriesResponseFromAlchemist.data),
ttl = -1,
version = 1,
isSuccess = true
)
}
}

View File

@@ -0,0 +1,57 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.category
import com.navi.common.alchemist.model.AlchemistBottomSheetStructure
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
interface BottomSheetHolder {
val bottomSheetStructure: StateFlow<AlchemistBottomSheetStructure?>
val isBottomSheetVisible: StateFlow<Boolean>
}
/** Interface responsible for controlling the visibility and behavior of bottom sheets. */
interface BottomSheetController : BottomSheetHolder {
fun initializeBottomSheets(bottomSheetList: List<AlchemistBottomSheetStructure>?) {}
fun showBottomSheet(bottomSheetId: String)
fun dismissBottomSheet()
}
class BottomSheetControllerImpl : BottomSheetController {
private var bottomSheetList: List<AlchemistBottomSheetStructure>? = null
private val _bottomSheetStructure = MutableStateFlow<AlchemistBottomSheetStructure?>(null)
override val bottomSheetStructure: StateFlow<AlchemistBottomSheetStructure?> =
_bottomSheetStructure.asStateFlow()
private val _isBottomSheetVisible = MutableStateFlow(false)
override val isBottomSheetVisible = _isBottomSheetVisible.asStateFlow()
override fun initializeBottomSheets(bottomSheetList: List<AlchemistBottomSheetStructure>?) {
this.bottomSheetList = bottomSheetList
}
override fun dismissBottomSheet() {
_isBottomSheetVisible.update { false }
}
override fun showBottomSheet(bottomSheetId: String) {
bottomSheetList
?.find { it.screenId == bottomSheetId }
?.let { _bottomSheetStructure.value = it }
_isBottomSheetVisible.update { true }
}
}

View File

@@ -0,0 +1,41 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.category.action
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import com.navi.analytics.utils.NaviTrackEvent
import com.navi.bbps.feature.category.BillCategoriesViewModelV2
import com.navi.common.uitron.model.action.AnalyticsActionV2
@Composable
fun AnalyticsActionHandler(
viewModel: BillCategoriesViewModelV2,
) {
LaunchedEffect(Unit) {
viewModel.getActionCallback().collect { action ->
if (action !is AnalyticsActionV2 || action.eventName.isNullOrEmpty()) return@collect
val eventProperties =
action.predefinedEventProperties
?.associate { it?.propertyName.orEmpty() to it?.propertyValue.orEmpty() }
?.toMutableMap()
eventProperties?.apply {
put("source", viewModel.source)
put("initialSource", viewModel.initialSource)
}
NaviTrackEvent.trackEvent(
eventName = action.eventName.orEmpty(),
eventValues = eventProperties,
isNeededForAppsflyer = action.isNeededForAppsflyer,
isNeededForFirebase = action.isNeededForFirebase
)
}
}
}

View File

@@ -0,0 +1,26 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.category.action
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.navi.bbps.entry.NaviBbpsActivity
import com.navi.bbps.feature.category.BillCategoriesViewModelV2
import com.navi.common.uitron.model.action.CtaAction
import com.navi.uitron.handlers.HandlePermissionAction
@Composable
fun BbpsLandingScreenActionInitializers(
viewModel: BillCategoriesViewModelV2,
activity: NaviBbpsActivity,
customAction: (ctaAction: CtaAction?) -> Unit,
) {
BbpsLandingPageCtaActionHandler(viewModel = viewModel, activity = activity, customAction)
AnalyticsActionHandler(viewModel = viewModel)
HandlePermissionAction(context = LocalContext.current, viewModel = viewModel)
}

View File

@@ -0,0 +1,47 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.category.action
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import com.navi.bbps.common.NAVI_BBPS_NAVIGATION_BOTTOM_SHEET
import com.navi.bbps.common.NAVI_BBPS_NAVIGATION_CUSTOM
import com.navi.bbps.common.NAVI_BBPS_NAVIGATION_FINISH
import com.navi.bbps.entry.NaviBbpsActivity
import com.navi.bbps.entry.NaviBbpsRouter
import com.navi.bbps.feature.category.BillCategoriesViewModelV2
import com.navi.common.uitron.model.action.CtaAction
@Composable
fun BbpsLandingPageCtaActionHandler(
viewModel: BillCategoriesViewModelV2,
activity: NaviBbpsActivity,
customAction: (ctaAction: CtaAction?) -> Unit,
) {
LaunchedEffect(Unit) {
viewModel.getActionCallback().collect { cta ->
if (cta !is CtaAction) return@collect
when (cta.ctaData?.type) {
NAVI_BBPS_NAVIGATION_FINISH -> {
activity.finish()
}
NAVI_BBPS_NAVIGATION_BOTTOM_SHEET -> {
val bottomSheetId = cta.ctaData?.url.orEmpty()
viewModel.showBottomSheet(bottomSheetId)
}
NAVI_BBPS_NAVIGATION_CUSTOM -> {
customAction.invoke(cta)
}
else -> {
NaviBbpsRouter.onCtaClick(naviBbpsActivity = activity, ctaData = cta.ctaData)
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.category.model.view
import com.navi.common.alchemist.model.AlchemistScreenDefinition
sealed class BillCategoryStateV2 {
data object Loading : BillCategoryStateV2()
data class Loaded(val billCategories: AlchemistScreenDefinition) : BillCategoryStateV2()
data object Error : BillCategoryStateV2()
}
enum class BillCategoryWidgets(val widgetName: String) {
UI_TRON_WIDGET("UI_TRON_WIDGET"),
MY_BILLS_WIDGET("MY_BILLS_WIDGET")
}

View File

@@ -7,33 +7,17 @@
package com.navi.bbps.feature.category.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.navi.bbps.bbpsShimmerEffect
import com.navi.bbps.clickableDebounce
import com.navi.bbps.common.theme.NaviBbpsColor
import com.navi.common.utils.EMPTY
import com.navi.design.font.FontWeightEnum
import com.navi.design.theme.getFontWeight
import com.navi.design.theme.ttComposeFontFamily
import com.navi.naviwidgets.extensions.NaviText
@Composable
fun BillCategoriesShimmer() {
@@ -49,53 +33,35 @@ fun BillCategoriesShimmer() {
Spacer(modifier = Modifier.height(16.dp))
Box(modifier = Modifier.height(24.dp).width(50.dp).bbpsShimmerEffect())
Box(modifier = Modifier.height(24.dp).width(76.dp).bbpsShimmerEffect())
Spacer(modifier = Modifier.height(16.dp))
Box(modifier = Modifier.fillMaxWidth().height(208.dp).bbpsShimmerEffect())
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier =
Modifier.padding(horizontal = 16.dp).height(24.dp).width(76.dp).bbpsShimmerEffect()
Modifier.fillMaxWidth()
.height(208.dp)
.clip(shape = RoundedCornerShape(2.dp))
.bbpsShimmerEffect()
)
Spacer(modifier = Modifier.height(16.dp))
Box(modifier = Modifier.fillMaxWidth().height(124.dp).bbpsShimmerEffect())
}
@Composable
fun CoinUtilisationShimmer(
navigationIcon: Int = com.navi.common.R.drawable.ic_arrow_left_black_v2,
titleColor: Color = NaviBbpsColor.textPrimary,
actionIconText: String? = null,
onActionClick: (() -> Unit)? = null,
onNavigationIconClick: () -> Unit,
) {
Box(modifier = Modifier.height(height = 244.dp).fillMaxWidth().bbpsShimmerEffect()) {
Row(
modifier = Modifier.padding(all = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = navigationIcon),
contentDescription = EMPTY,
modifier =
Modifier.padding(end = 16.dp).clickableDebounce {
onNavigationIconClick.invoke()
}
)
Spacer(modifier = Modifier.weight(1f))
NaviText(
text = actionIconText ?: "",
fontSize = 14.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
color = titleColor,
textAlign = TextAlign.Center,
modifier =
Modifier.padding(end = 16.dp).clickableDebounce { onActionClick?.invoke() }
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Box(modifier = Modifier.height(24.dp).width(76.dp).bbpsShimmerEffect())
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier =
Modifier.fillMaxWidth()
.height(124.dp)
.clip(shape = RoundedCornerShape(2.dp))
.bbpsShimmerEffect()
)
Box(modifier = Modifier.height(24.dp).width(76.dp).bbpsShimmerEffect())
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier =
Modifier.fillMaxWidth()
.height(208.dp)
.clip(shape = RoundedCornerShape(2.dp))
.bbpsShimmerEffect()
)
}

View File

@@ -0,0 +1,443 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.category.ui
import android.annotation.SuppressLint
import androidx.activity.compose.BackHandler
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.DrawerDefaults.scrimColor
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.navi.alfred.AlfredManager
import com.navi.base.utils.orElse
import com.navi.base.utils.orFalse
import com.navi.base.utils.orTrue
import com.navi.bbps.common.CATEGORY_ID_MOBILE_POSTPAID
import com.navi.bbps.common.CATEGORY_ID_MOBILE_PREPAID
import com.navi.bbps.common.NAVI_BBPS_CATEGORY_GROUP_ID
import com.navi.bbps.common.NAVI_BBPS_NAVIGATION_CUSTOM
import com.navi.bbps.common.NAVI_BBPS_PAYLOAD
import com.navi.bbps.common.NAVI_HOME
import com.navi.bbps.common.NaviBbpsAnalytics
import com.navi.bbps.common.NaviBbpsScreen
import com.navi.bbps.common.theme.NaviBbpsColor
import com.navi.bbps.common.ui.SetStatusBarColor
import com.navi.bbps.entry.NaviBbpsActivity
import com.navi.bbps.feature.category.BillCategoriesViewModelV2
import com.navi.bbps.feature.category.action.BbpsLandingScreenActionInitializers
import com.navi.bbps.feature.category.model.view.BillCategoryEntity
import com.navi.bbps.feature.category.model.view.BillCategoryStateV2
import com.navi.bbps.feature.category.model.view.BillCategoryWidgets
import com.navi.bbps.feature.destinations.BillerListScreenDestination
import com.navi.bbps.feature.destinations.ContactListScreenDestination
import com.navi.bbps.feature.destinations.MyBillsScreenDestination
import com.navi.common.alchemist.model.AlchemistBottomSheetStructure
import com.navi.common.alchemist.model.AlchemistScreenStructure
import com.navi.common.alchemist.model.AlchemistWidgetModelDefinition
import com.navi.common.uitron.model.action.CtaAction
import com.navi.common.uitron.render.CommonCustomUiTronRenderer
import com.navi.rr.utils.ext.toJson
import com.navi.uitron.model.UiTronResponse
import com.navi.uitron.render.UiTronRenderer
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@SuppressLint("CoroutineCreationDuringComposition")
@Destination
@Composable
fun BillCategoriesScreenV2(
naviBbpsActivity: NaviBbpsActivity,
viewModel: BillCategoriesViewModelV2 = hiltViewModel(),
navigator: DestinationsNavigator,
source: String = NAVI_HOME,
initialSource: String = NaviBbpsScreen.NAVI_BBPS_BILL_CATEGORIES_V2.name,
naviBbpsAnalytics: NaviBbpsAnalytics.BillCategories =
NaviBbpsAnalytics.INSTANCE.BillCategories(),
) {
val billCategoryStateV2 by viewModel.billCategoriesStateV2.collectAsStateWithLifecycle()
val rewardsDetails by viewModel.rewardsDetails.collectAsStateWithLifecycle()
val isCoinUtilisationExperimentEnabled by
viewModel.isCoinUtilisationExperimentEnabled.collectAsStateWithLifecycle()
val isBottomSheetVisible by viewModel.isBottomSheetVisible.collectAsStateWithLifecycle()
val bottomSheetStructure by viewModel.bottomSheetStructure.collectAsStateWithLifecycle()
val lifecycleOwner = LocalLifecycleOwner.current
val scope = rememberCoroutineScope()
SetStatusBarColor(activity = naviBbpsActivity)
BackHandler {
if (isBottomSheetVisible.orFalse()) {
viewModel.dismissBottomSheet()
return@BackHandler
}
if (billCategoryStateV2 is BillCategoryStateV2.Loaded) {
val screenState = billCategoryStateV2 as BillCategoryStateV2.Loaded
screenState.billCategories.screenStructure?.systemBackCta?.let {
viewModel.handleActions(it)
}
return@BackHandler
} else {
naviBbpsActivity.finish()
}
}
LaunchedEffect(billCategoryStateV2) {
viewModel.navigateToNextScreen.collectLatest { navigator.navigate(it) }
}
LaunchedEffect(billCategoryStateV2) {
if (billCategoryStateV2 is BillCategoryStateV2.Loaded) {
val screenState = billCategoryStateV2 as BillCategoryStateV2.Loaded
screenState.billCategories.screenStructure?.renderActions?.postRenderAction?.let {
viewModel.handleActions(it)
}
}
}
LaunchedEffect(Unit) {
naviBbpsAnalytics.onLanded(
source = source,
sessionAttribute = viewModel.getNaviPaySessionAttributes(),
initialSource = initialSource,
coinUtilisationData = if (rewardsDetails != null) rewardsDetails else null
)
}
val onManageMyBillsClicked = {
navigator.navigate(
MyBillsScreenDestination(
isRootScreen = false,
source = NaviBbpsScreen.NAVI_BBPS_BILL_CATEGORIES.name,
initialSource = initialSource
)
)
}
val onCategorySelected = { categoryGroupId: String, billCategoryEntity: BillCategoryEntity ->
naviBbpsAnalytics.onCategorySelected(
source = source,
categoryGroupId = categoryGroupId,
billCategoryEntity = billCategoryEntity,
sessionAttribute = viewModel.getNaviPaySessionAttributes(),
initialSource = initialSource,
coinUtilisationData = rewardsDetails
)
if (
billCategoryEntity.categoryId.equals(
other = CATEGORY_ID_MOBILE_PREPAID,
ignoreCase = true
) ||
billCategoryEntity.categoryId.equals(
other = CATEGORY_ID_MOBILE_POSTPAID,
ignoreCase = true
)
) {
navigator.navigate(
ContactListScreenDestination(
billCategoryEntity = billCategoryEntity,
source = NaviBbpsScreen.NAVI_BBPS_BILL_CATEGORIES.name,
initialSource = initialSource
)
)
} else {
navigator.navigate(
BillerListScreenDestination(
billCategoryEntity = billCategoryEntity,
source = NaviBbpsScreen.NAVI_BBPS_BILLER_LIST.name,
initialSource = initialSource
)
)
}
}
fun handleCategoriesClicked(ctaAction: CtaAction) {
scope.launch {
val categoryGroupId =
ctaAction.ctaData
?.parameters
?.firstOrNull { it.key == NAVI_BBPS_CATEGORY_GROUP_ID }
?.value
val payload =
ctaAction.ctaData?.parameters?.firstOrNull { it.key == NAVI_BBPS_PAYLOAD }?.payload
onCategorySelected.invoke(
categoryGroupId.orEmpty(),
viewModel.naviBbpsGson.fromJson(payload.toJson(), BillCategoryEntity::class.java)
)
}
}
BbpsLandingScreenActionInitializers(viewModel = viewModel, activity = naviBbpsActivity) {
ctaAction ->
if (ctaAction?.type == null) return@BbpsLandingScreenActionInitializers
when (ctaAction.ctaData?.type) {
NAVI_BBPS_NAVIGATION_CUSTOM -> {
handleCategoriesClicked(ctaAction)
}
else -> Unit
}
}
Column(modifier = Modifier.fillMaxSize()) {
when (billCategoryStateV2) {
is BillCategoryStateV2.Loading -> {
BillCategoriesShimmer()
}
is BillCategoryStateV2.Loaded -> {
BillCategoryScreenRenderer(
viewModel = viewModel,
screenStructure =
(billCategoryStateV2 as BillCategoryStateV2.Loaded)
.billCategories
.screenStructure,
source = source,
initialSource = initialSource,
onManageMyBillsClicked = onManageMyBillsClicked,
naviBbpsAnalytics = naviBbpsAnalytics
)
}
is BillCategoryStateV2.Error -> {}
}
}
if (isCoinUtilisationExperimentEnabled) {
DisposableEffect(key1 = lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME,
Lifecycle.Event.ON_CREATE, -> {
naviBbpsActivity.window.statusBarColor = Color.Transparent.toArgb()
}
else -> Unit
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
}
if (isBottomSheetVisible && bottomSheetStructure != null) {
BbpsUiTronBottomSheet(structure = bottomSheetStructure!!, viewModel = viewModel) {
viewModel.dismissBottomSheet()
}
}
}
@Composable
fun BillCategoryScreenRenderer(
viewModel: BillCategoriesViewModelV2,
screenStructure: AlchemistScreenStructure? = null,
source: String,
initialSource: String,
onManageMyBillsClicked: () -> Unit,
naviBbpsAnalytics: NaviBbpsAnalytics.BillCategories,
) {
if (screenStructure == null) return
val scrollState = rememberScrollState()
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
screenStructure.header?.widgets?.forEachIndexed { index, widget ->
key(widget.widgetName.orElse(index.toString())) {
UiTronRenderer(
dataMap = widget.widgetData?.data,
uiTronViewModel = viewModel,
customUiTronRenderer = CommonCustomUiTronRenderer()
)
.Render(composeViews = widget.widgetData?.parentComposeView.orEmpty())
}
}
},
content = { innerPadding ->
Column(
modifier =
Modifier.fillMaxSize()
.verticalScroll(state = scrollState)
.background(color = NaviBbpsColor.bgDefault)
.padding(innerPadding)
) {
screenStructure.content?.widgets?.forEachIndexed { index, widget ->
key(widget.widgetName.orElse(index.toString())) {
BbpsLandingPageWidgetRenderer(
widget = widget,
viewModel = viewModel,
source = source,
initialSource = initialSource,
onManageMyBillsClicked = onManageMyBillsClicked,
naviBbpsAnalytics = naviBbpsAnalytics
)
}
}
}
}
)
}
@Composable
private fun BbpsLandingPageWidgetRenderer(
widget: AlchemistWidgetModelDefinition<UiTronResponse>,
viewModel: BillCategoriesViewModelV2,
source: String? = null,
initialSource: String? = null,
onManageMyBillsClicked: (() -> Unit)? = null,
naviBbpsAnalytics: NaviBbpsAnalytics.BillCategories? = null,
) {
when (widget.widgetName) {
BillCategoryWidgets.UI_TRON_WIDGET.name -> {
UiTronRenderer(
dataMap = widget.widgetData?.data,
uiTronViewModel = viewModel,
customUiTronRenderer = CommonCustomUiTronRenderer()
)
.Render(composeViews = widget.widgetData?.parentComposeView.orEmpty())
}
BillCategoryWidgets.MY_BILLS_WIDGET.name -> {
MyBillsWidget(
viewModel = viewModel,
source = source,
initialSource = initialSource,
onManageMyBillsClicked = onManageMyBillsClicked,
naviBbpsAnalytics = naviBbpsAnalytics
)
}
}
}
@Composable
private fun MyBillsWidget(
viewModel: BillCategoriesViewModelV2,
source: String?,
initialSource: String?,
onManageMyBillsClicked: (() -> Unit)?,
naviBbpsAnalytics: NaviBbpsAnalytics.BillCategories?,
) {
if (onManageMyBillsClicked == null) return
val accountsCountText by viewModel.accountsCountText.collectAsStateWithLifecycle()
val myBillCardInfoState by viewModel.myBillCardInfoState.collectAsStateWithLifecycle()
Column(
modifier =
Modifier.background(color = NaviBbpsColor.textWhite).animateContentSize { _, _ -> }
) {
if (viewModel.nonDismissedUnpaidBills.isEmpty()) {
ManageMyBillCard(
accountsCountText = accountsCountText,
onManageMyBillsClicked = onManageMyBillsClicked,
myBillCardInfoState = myBillCardInfoState
)
} else {
PendingCountSection(
nonDismissedUnpaidBills = viewModel.nonDismissedUnpaidBills,
onManageMyBillsClicked = onManageMyBillsClicked,
)
PendingBillCarouselSection(
nonDismissedUnpaidBills = viewModel.nonDismissedUnpaidBills,
onDismissBillClicked = { myBillEntity ->
naviBbpsAnalytics?.onPendingBillPayNowCrossClicked(
source = source.orEmpty(),
myBillEntity = myBillEntity,
initialSource = initialSource.orEmpty(),
sessionAttribute = viewModel.getNaviPaySessionAttributes()
)
viewModel.onDismissBillClicked(myBillEntity = myBillEntity)
},
onPendingBillClicked = { myBillEntity ->
naviBbpsAnalytics?.onPendingBillPayNowClicked(
source = source.orEmpty(),
myBillEntity = myBillEntity,
initialSource = initialSource.orEmpty(),
sessionAttribute = viewModel.getNaviPaySessionAttributes()
)
viewModel.onPendingBillClicked(myBillEntity = myBillEntity)
},
getReminderTitleFromMyBillEntity = viewModel::getReminderTitleFromMyBillEntity
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun BbpsUiTronBottomSheet(
structure: AlchemistBottomSheetStructure,
viewModel: BillCategoriesViewModelV2,
onDismiss: () -> Unit,
) {
val modifier: Modifier =
when (structure.uiStrategy?.name) {
AlchemistBottomSheetStructure.UiStrategy.FIX_PERCENTAGE_HEIGHT.name -> {
Modifier.fillMaxHeight(structure.bottomSheetPercentageHeight ?: 0.5f)
}
AlchemistBottomSheetStructure.UiStrategy.DEFAULT.name -> {
Modifier.height(IntrinsicSize.Max)
}
else -> {
Modifier.height(IntrinsicSize.Max)
}
}
val bottomSheetState =
rememberModalBottomSheetState(
confirmValueChange = { structure.isCancellable.orTrue() },
skipPartiallyExpanded = true
)
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = bottomSheetState,
dragHandle = {},
shape = RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp),
containerColor = Color.White,
scrimColor = scrimColor
) {
AlfredManager.setBottomSheetView(LocalView.current.rootView)
Column(
modifier = modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start
) {
structure.content?.widgets?.forEachIndexed { index, widget ->
key(widget.widgetName.orElse(index.toString())) {
BbpsLandingPageWidgetRenderer(widget = widget, viewModel = viewModel)
}
}
}
}
}

View File

@@ -114,13 +114,7 @@ fun RenderBillCategoriesScreen(
)
RewardCoinEarnCalloutStrip(rewardsDetails = rewardsDetails)
}
else -> {
CoinUtilisationShimmer(
onNavigationIconClick = onNavigationIconClicked,
actionIconText = helpCtaText,
onActionClick = onHelpCtaClicked
)
}
else -> {}
}
Spacer(modifier = Modifier.height(16.dp))
}
@@ -206,7 +200,7 @@ fun PendingCountSection(
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun PendingBillCarouselSection(
internal fun PendingBillCarouselSection(
nonDismissedUnpaidBills: List<MyBillEntity>,
onDismissBillClicked: (MyBillEntity) -> Unit,
onPendingBillClicked: (MyBillEntity) -> Unit,
@@ -356,7 +350,7 @@ private fun PendingBillItem(
}
@Composable
private fun ManageMyBillCard(
internal fun ManageMyBillCard(
accountsCountText: String,
onManageMyBillsClicked: () -> Unit,
myBillCardInfoState: MyBillCardInfoState

View File

@@ -22,6 +22,8 @@ import com.navi.common.model.ModuleName
import com.navi.common.model.ModuleNameV2
import com.navi.common.model.NetworkInfo
import com.navi.common.network.converter.EmptyBodyHandlingConverterFactory
import com.navi.common.utils.registerUiTronDeSerializers
import com.navi.common.utils.registerUiTronSerializer
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -62,7 +64,11 @@ object NaviBbpsNetworkModule {
@Provides
@NaviBbpsGsonBuilder
fun providesDeserializer(): Gson =
GsonBuilder().registerTypeAdapter(DateTime::class.java, DateTimeConverterAdapter()).create()
GsonBuilder()
.registerUiTronDeSerializers()
.registerUiTronSerializer()
.registerTypeAdapter(DateTime::class.java, DateTimeConverterAdapter())
.create()
@ActivityRetainedScoped
@Provides

View File

@@ -30,11 +30,14 @@ import com.navi.bbps.feature.prepaidrecharge.model.network.PhoneNumberToOperator
import com.navi.bbps.feature.prepaidrecharge.model.network.PhoneNumberToOperatorCircleCodeMappingResponse
import com.navi.bbps.feature.transactiondetails.model.network.BbpsBillAcknowledgmentStatusResponse
import com.navi.bbps.feature.transactiondetails.model.network.BbpsTransactionStatusResponse
import com.navi.common.alchemist.model.AlchemistScreenDefinition
import com.navi.common.network.models.GenericResponse
import com.navi.rr.common.models.ABSettings
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
@@ -132,4 +135,17 @@ interface NaviBbpsRetrofitService {
@GET("/billpay-gateway/$NAVI_BBPS_API_VERSION/billpay/reward-details")
suspend fun getRewardDetails(): Response<GenericResponse<RewardDetailsResponse>>
@GET("/alchemist/inflate/{screenId}")
suspend fun fetchAlchemistScreenUiTronConfigs(
@Header("Accept-Encoding") acceptEncoding: String,
@Header("X-Target") target: String,
@Path("screenId") screenId: String
): Response<GenericResponse<AlchemistScreenDefinition>>
@GET("/litmus-proxy/v1/proxy/experiment")
suspend fun fetchABExperiment(
@Query("name") name: String,
@Header("X-Target") header: String
): Response<ABSettings>
}

View File

@@ -35,8 +35,15 @@ data class AlchemistBottomSheetStructure(
val screenId: String? = null,
val content: AlchemistWidgetGroup? = null,
val isCancellable: Boolean? = null,
val onDismissAction: UiTronActionData? = null
)
val onDismissAction: UiTronActionData? = null,
@SerializedName("uiStrategy", alternate = ["strategy"]) val uiStrategy: UiStrategy? = null,
val bottomSheetPercentageHeight: Float? = null
) {
enum class UiStrategy {
FIX_PERCENTAGE_HEIGHT,
DEFAULT
}
}
data class AlchemistWidgetGroup(
val widgets: List<AlchemistWidgetModelDefinition<UiTronResponse>>? = null,

View File

@@ -33,4 +33,6 @@ object DBCacheConstants {
"PREPAID_RECHARGE_SCREEN_CIRCLE_DATA_CACHE_KEY"
const val PREPAID_RECHARGE_PLANS_CACHE_KEY = "PREPAID_RECHARGE_PLANS_CACHE_KEY"
const val BBPS_AB_TESTING_EXPERIMENTS_CACHE_KEY = "BBPS_AB_TESTING_EXPERIMENTS_CACHE_KEY"
const val BBPS_BILL_CATEGORIES_LIST_ALCHEMIST_CACHE_KEY =
"NAVI_BBPS_CATEGORIES_LIST_ALCHEMIST_CACHE_KEY"
}

View File

@@ -1,6 +1,6 @@
/*
*
* * Copyright © 2023 by Navi Technologies Limited
* * Copyright © 2023-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
@@ -26,7 +26,7 @@ data class AnalyticsActionV2(
@SerializedName("eventType") val eventType: String? = null,
@SerializedName("eventName") val eventName: String? = null,
@SerializedName("eventProperties") val eventProperties: List<EventProperty?>? = null,
@SerializedName("predefinedEventProperties")
@SerializedName("predefinedEventProperties", alternate = ["eventValues"])
val predefinedEventProperties: List<PredefinedEventProperty?>? = null,
@SerializedName("outputFields") val outputFields: List<WidgetOutputProperty?>? = null,
@SerializedName("isNeededForAppsflyer") val isNeededForAppsflyer: Boolean = true,