NTP-4792 | Mehul | project nexus rcbp new landing page (#12882)
Co-authored-by: kishan kumar <kishan.kumar@navi.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user