From 1822ce6da84432e07408fa7bc6f17c675e676eda Mon Sep 17 00:00:00 2001 From: Mehul Garg Date: Tue, 2 Apr 2024 15:24:27 +0530 Subject: [PATCH] TP-59832 | Mehul | Biller list caching E2E (#10255) --- .../com/navi/bbps/common/NaviBbpsConstants.kt | 5 + .../navi/bbps/common/model/NaviBbpsManager.kt | 51 +++++ .../common/repository/BbpsCommonRepository.kt | 22 +- .../bbps/common/usecase/BillerListUseCase.kt | 66 ++++++ .../com/navi/bbps/db/NaviBbpsAppDatabase.kt | 31 ++- .../com/navi/bbps/db/di/NaviBbpsDbModule.kt | 8 + .../navi/bbps/entry/NaviBbpsMainViewModel.kt | 20 +- .../BillerItemResponseToEntityMapper.kt | 30 +++ .../feature/billerlist/BillerListViewModel.kt | 193 ++++++++++++---- .../feature/billerlist/dao/BillerListDao.kt | 36 +++ .../model/network/BillerItemListResponse.kt | 14 ++ .../model/network/BillerListResponse.kt | 6 +- .../billerlist/model/view/BillerListEntity.kt | 10 +- .../billerlist/model/view/BillerListState.kt | 2 - .../feature/billerlist/ui/BillerListScreen.kt | 10 +- .../billerlist/ui/RenderBillerListScreen.kt | 216 +++++++++--------- .../BillHistoryDetailsViewModel.kt | 8 +- .../category/BillCategoriesViewModel.kt | 4 +- .../model/network/BillCategoriesResponse.kt | 4 +- .../model/view/BillCategoryGroupEntity.kt | 4 +- .../common/ui/BbpsRoutingLauncherScreen.kt | 8 +- .../contactlist/ContactListViewModel.kt | 2 - .../navi/bbps/feature/mybills/MyBillsDao.kt | 2 +- .../bbps/feature/mybills/MyBillsRepository.kt | 3 + .../PrepaidRechargeViewModel.kt | 4 +- .../service/NaviBbpsRetrofitService.kt | 4 + .../navi-bbps/src/main/res/values/strings.xml | 4 + 27 files changed, 585 insertions(+), 182 deletions(-) create mode 100644 android/navi-bbps/src/main/kotlin/com/navi/bbps/common/model/NaviBbpsManager.kt create mode 100644 android/navi-bbps/src/main/kotlin/com/navi/bbps/common/usecase/BillerListUseCase.kt create mode 100644 android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerItemResponseToEntityMapper.kt create mode 100644 android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/dao/BillerListDao.kt create mode 100644 android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/network/BillerItemListResponse.kt diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsConstants.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsConstants.kt index c730e45f9c..769bc70382 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsConstants.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsConstants.kt @@ -16,9 +16,12 @@ internal const val BBPS_CATEGORY_TITLE = "BBPS_CATEGORY_TITLE" internal const val BBPS_CATEGORY_ICON_URL = "BBPS_CATEGORY_ICON_URL" internal const val BBPS_BILLER_SEARCH_BOX_PLACEHOLDER_TEXT = "BBPS_BILLER_SEARCH_BOX_PLACEHOLDER_TEXT" +internal const val BBPS_BILLER_LIST_STATE_HEADING = "BBPS_BILLER_LIST_STATE_HEADING" +internal const val BBPS_BILLER_LIST_ALL_HEADING = "BBPS_BILLER_LIST_ALL_HEADING" // Values const val CONFIG_IN_DB_REFRESH_MIN_TIMESTAMP = 86400000L // 1 day +const val BILLER_LIST_REFRESH_TIMESTAMP = 86400000L // 1 day // Date time format const val DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR = "dd MMM yyyy" @@ -61,6 +64,8 @@ const val ICON_FASTAG_BOTTOMSHEET = // Preference Keys const val KEY_CONFIG_DB_LAST_REFRESHED_TIMESTAMP = "KEY_CONFIG_DB_LAST_REFRESHED_TIMESTAMP" +const val KEY_BBPS_MY_BILLS_DB_LAST_REFRESHED_TIMESTAMP = + "KEY_BBPS_MY_BILLS_DB_LAST_REFRESHED_TIMESTAMP" // Fixed values const val NOTES_REGEX_CONDITION = "^[a-zA-Z0-9 \\-]*\$" diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/model/NaviBbpsManager.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/model/NaviBbpsManager.kt new file mode 100644 index 0000000000..bc444fc835 --- /dev/null +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/model/NaviBbpsManager.kt @@ -0,0 +1,51 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.common.model + +import com.navi.bbps.common.usecase.BbpsRefreshConfigUseCase +import com.navi.bbps.common.usecase.BillerListUseCase +import com.navi.bbps.feature.mybills.MyBillsSyncJob +import com.navi.common.di.CoroutineDispatcherProvider +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.launch + +class NaviBbpsManager +@Inject +constructor( + private val dispatcherProvider: CoroutineDispatcherProvider, + private val billerListUseCase: BillerListUseCase, + private val bbpsRefreshConfigUseCase: BbpsRefreshConfigUseCase, + private val myBillsSyncJob: MyBillsSyncJob, +) { + private var isInitInProgress = false + + fun init() { + CoroutineScope(dispatcherProvider.io).launch { + if (isInitInProgress) { + return@launch + } + + isInitInProgress = true + + myBillsSyncJob.refreshBillsAsync() + + val taskList = mutableListOf>() + taskList.add(async { myBillsSyncJob.refreshBills() }) + taskList.add(async { bbpsRefreshConfigUseCase.execute() }) + taskList.add(async { billerListUseCase.execute() }) + + taskList.awaitAll() + + isInitInProgress = false + } + } +} diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/repository/BbpsCommonRepository.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/repository/BbpsCommonRepository.kt index 5417428685..c3aa0f9574 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/repository/BbpsCommonRepository.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/repository/BbpsCommonRepository.kt @@ -7,6 +7,9 @@ package com.navi.bbps.common.repository +import com.navi.bbps.feature.billerlist.dao.BillerListDao +import com.navi.bbps.feature.billerlist.model.network.BillerItemListResponse +import com.navi.bbps.feature.billerlist.model.view.BillerItemEntity import com.navi.bbps.feature.customerinput.model.network.BillDetailsRequest import com.navi.bbps.feature.customerinput.model.network.BillDetailsResponse import com.navi.bbps.feature.mybills.MyBillsSyncJob @@ -20,7 +23,8 @@ class BbpsCommonRepository @Inject constructor( private val naviBbpsRetrofitService: NaviBbpsRetrofitService, - private val myBillsSyncJob: MyBillsSyncJob + private val myBillsSyncJob: MyBillsSyncJob, + private val billerListDao: BillerListDao ) : ResponseCallback() { suspend fun fetchBillDetails( @@ -41,4 +45,20 @@ constructor( naviBbpsRetrofitService.getConfig(commaSeparatedConfigKeys = commaSeparatedConfigKeys) ) } + + suspend fun fetchAllBillers(): RepoResult { + return apiResponseCallback(naviBbpsRetrofitService.getFullBillerList()) + } + + suspend fun getBillersFromLocalDb(): List { + return billerListDao.getAllBillers() + } + + suspend fun refreshBillers(billers: List) { + billerListDao.refreshBillers(billers) + } + + suspend fun getBillersByCategoryFromLocalDb(categoryId: String): List { + return billerListDao.getBillersByCategory(categoryId) + } } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/usecase/BillerListUseCase.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/usecase/BillerListUseCase.kt new file mode 100644 index 0000000000..d018b3c003 --- /dev/null +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/usecase/BillerListUseCase.kt @@ -0,0 +1,66 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.common.usecase + +import com.navi.bbps.common.BILLER_LIST_REFRESH_TIMESTAMP +import com.navi.bbps.common.BbpsSharedPreferences +import com.navi.bbps.common.KEY_BBPS_MY_BILLS_DB_LAST_REFRESHED_TIMESTAMP +import com.navi.bbps.common.repository.BbpsCommonRepository +import com.navi.bbps.feature.billerlist.BillerItemResponseToEntityMapper +import com.navi.bbps.feature.billerlist.model.view.BillerItemEntity +import javax.inject.Inject +import org.joda.time.DateTime + +class BillerListUseCase +@Inject +constructor( + private val bbpsCommonRepository: BbpsCommonRepository, + private val bbpsSharedPreferences: BbpsSharedPreferences, + private val billerItemResponseToEntityMapper: BillerItemResponseToEntityMapper +) { + suspend fun execute() { + val existingBillers = bbpsCommonRepository.getBillersFromLocalDb() + + val lastFetchBillerListTimeStamp = + bbpsSharedPreferences.getLong( + key = KEY_BBPS_MY_BILLS_DB_LAST_REFRESHED_TIMESTAMP, + defValue = -1L + ) + + val shouldFetchBillerList = + existingBillers.isEmpty() || + (DateTime.now().millis - lastFetchBillerListTimeStamp > + BILLER_LIST_REFRESH_TIMESTAMP) + + if (shouldFetchBillerList) { + val billerListResponse = bbpsCommonRepository.fetchAllBillers() + + val billers = billerListResponse.data?.billers.orEmpty() + + if (billers.isNullOrEmpty()) { + return + } + + refreshBillers( + billers = + billerItemResponseToEntityMapper.mapBillerItemResponseToEntityList( + billers = billers + ) + ) + + bbpsSharedPreferences.saveLong( + key = KEY_BBPS_MY_BILLS_DB_LAST_REFRESHED_TIMESTAMP, + value = System.currentTimeMillis() + ) + } + } + + private suspend fun refreshBillers(billers: List) { + bbpsCommonRepository.refreshBillers(billers) + } +} diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/NaviBbpsAppDatabase.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/NaviBbpsAppDatabase.kt index 42f225d07f..412aa67b28 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/NaviBbpsAppDatabase.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/NaviBbpsAppDatabase.kt @@ -10,19 +10,48 @@ package com.navi.bbps.db import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.navi.bbps.db.NaviBbpsAppDatabase.Companion.NAVI_BBPS_DATABASE_BILLERS import com.navi.bbps.db.converter.PaymentAmountExactnessConverter import com.navi.bbps.db.converter.StringMapConverter +import com.navi.bbps.feature.billerlist.dao.BillerListDao +import com.navi.bbps.feature.billerlist.model.view.BillerItemEntity import com.navi.bbps.feature.mybills.MyBillsDao import com.navi.bbps.feature.mybills.model.view.MyBillEntity -@Database(entities = [MyBillEntity::class], version = 1, exportSchema = false) +@Database( + entities = [MyBillEntity::class, BillerItemEntity::class], + version = 2, + exportSchema = false +) @TypeConverters(PaymentAmountExactnessConverter::class, StringMapConverter::class) abstract class NaviBbpsAppDatabase : RoomDatabase() { abstract fun myBillsDao(): MyBillsDao + abstract fun billerListDao(): BillerListDao + companion object { const val NAVI_BBPS_DATABASE_NAME = "navi-bbps.db" const val NAVI_BBPS_TABLE_MY_SAVED_BILLS = "my_saved_bills" + const val NAVI_BBPS_DATABASE_BILLERS = "billers" } } + +val NAVI_BBPS_DATABASE_MIGRATION_1_2 = + object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + // Create the new table for BillerItemEntity + database.execSQL( + "CREATE TABLE IF NOT EXISTS $NAVI_BBPS_DATABASE_BILLERS (" + + "`billerId` TEXT NOT NULL PRIMARY KEY, " + + "`billerName` TEXT NOT NULL, " + + "`billerLogoUrl` TEXT, " + + "`status` TEXT NOT NULL, " + + "`isAdhoc` INTEGER NOT NULL, " + + "`state` TEXT NOT NULL, " + + "`categoryId` TEXT NOT NULL)" + ) + } + } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/di/NaviBbpsDbModule.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/di/NaviBbpsDbModule.kt index 31692f00b1..f3984652a2 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/di/NaviBbpsDbModule.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/di/NaviBbpsDbModule.kt @@ -12,6 +12,7 @@ import androidx.room.Room import com.navi.bbps.common.BbpsSharedPreferences import com.navi.bbps.common.utils.BbpsSessionCache import com.navi.bbps.common.utils.BbpsSessionCacheImpl +import com.navi.bbps.db.NAVI_BBPS_DATABASE_MIGRATION_1_2 import com.navi.bbps.db.NaviBbpsAppDatabase import dagger.Binds import dagger.Module @@ -34,12 +35,19 @@ class NaviBbpsDbModule { NaviBbpsAppDatabase::class.java, NaviBbpsAppDatabase.NAVI_BBPS_DATABASE_NAME ) + .addMigrations(NAVI_BBPS_DATABASE_MIGRATION_1_2) + .fallbackToDestructiveMigration() .build() @ActivityRetainedScoped @Provides fun providesMyBillsDao(naviBbpsAppDatabase: NaviBbpsAppDatabase) = naviBbpsAppDatabase.myBillsDao() + + @ActivityRetainedScoped + @Provides + fun providesBillerListDao(naviBbpsAppDatabase: NaviBbpsAppDatabase) = + naviBbpsAppDatabase.billerListDao() } @Module diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/entry/NaviBbpsMainViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/entry/NaviBbpsMainViewModel.kt index 6ea0f98833..118190aa9b 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/entry/NaviBbpsMainViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/entry/NaviBbpsMainViewModel.kt @@ -7,31 +7,19 @@ package com.navi.bbps.entry -import androidx.lifecycle.viewModelScope import com.navi.bbps.common.NaviBbpsScreen +import com.navi.bbps.common.model.NaviBbpsManager import com.navi.bbps.common.model.NaviBbpsVmData -import com.navi.bbps.common.usecase.BbpsRefreshConfigUseCase import com.navi.bbps.common.viewmodel.NaviBbpsBaseVM -import com.navi.bbps.feature.mybills.MyBillsSyncJob import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject -import kotlinx.coroutines.Dispatchers @HiltViewModel -class NaviBbpsMainViewModel -@Inject -constructor( - private val bbpsRefreshConfigUseCase: BbpsRefreshConfigUseCase, - private val myBillsSyncJob: MyBillsSyncJob -) : NaviBbpsBaseVM(naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_MAIN)) { +class NaviBbpsMainViewModel @Inject constructor(private val naviBbpsManager: NaviBbpsManager) : + NaviBbpsBaseVM(naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_MAIN)) { init { - refreshConfig() - } - - private fun refreshConfig() { - viewModelScope.safeLaunch(Dispatchers.IO) { bbpsRefreshConfigUseCase.execute() } - myBillsSyncJob.refreshBillsAsync() + naviBbpsManager.init() } fun getDefaultScreenName() = "" diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerItemResponseToEntityMapper.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerItemResponseToEntityMapper.kt new file mode 100644 index 0000000000..fc41047e20 --- /dev/null +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerItemResponseToEntityMapper.kt @@ -0,0 +1,30 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.feature.billerlist + +import com.navi.bbps.feature.billerlist.model.network.BillerItemResponse +import com.navi.bbps.feature.billerlist.model.view.BillerItemEntity +import javax.inject.Inject + +class BillerItemResponseToEntityMapper @Inject constructor() { + fun mapBillerItemResponseToEntityList( + billers: List + ): List { + return billers.map { response -> + BillerItemEntity( + billerId = response.billerId, + billerName = response.billerName, + billerLogoUrl = response.billerLogoUrl, + status = response.status ?: "", + isAdhoc = response.isAdhoc ?: false, + state = response.state ?: "", + categoryId = response.categoryId + ) + } + } +} diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerListViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerListViewModel.kt index d2c33040b5..435f6dffd4 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerListViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerListViewModel.kt @@ -12,6 +12,7 @@ import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.FirebaseCrashlytics import com.navi.base.utils.BaseUtils import com.navi.base.utils.NaviNetworkConnectivity +import com.navi.bbps.R import com.navi.bbps.common.CATEGORY_ID_FASTAG import com.navi.bbps.common.CATEGORY_ID_MOBILE_POSTPAID import com.navi.bbps.common.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR @@ -40,6 +41,7 @@ import com.navi.bbps.feature.customerinput.model.network.DeviceDetails import com.navi.bbps.feature.customerinput.model.view.BillDetailsEntity import com.navi.bbps.feature.customerinput.model.view.BillerDetailsEntity import com.navi.bbps.feature.destinations.PayBillScreenDestination +import com.navi.bbps.feature.mybills.MyBillsRepository import com.navi.bbps.feature.paybill.model.network.PaymentAmountExactness import com.navi.bbps.feature.paybill.model.view.PayBillSource import com.navi.common.di.CoroutineDispatcherProvider @@ -78,6 +80,7 @@ constructor( private val bbpsCommonRepository: BbpsCommonRepository, private val naviNetworkConnectivity: NaviNetworkConnectivity, private val naviBbpsDateUtils: NaviBbpsDateUtils, + private val myBillsRepository: MyBillsRepository, private val deviceLocationProvider: DeviceLocationProvider ) : NaviBbpsBaseVM(naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_BILLER_LIST)) { @@ -95,6 +98,9 @@ constructor( private val _navigateToNextScreen = MutableSharedFlow() val navigateToNextScreen = _navigateToNextScreen.asSharedFlow() + private val _isPayBillShimmerVisible = MutableStateFlow(false) + val isPayBillShimmerVisible = _isPayBillShimmerVisible.asStateFlow() + val phoneNumber = BaseUtils.getPhoneNumber().toString() private val _myNumberState = @@ -151,6 +157,14 @@ constructor( initSearchFlow() } + private fun replaceIfPresent(billerListStateHeading: String?, stateValue: String): String { + if (billerListStateHeading.isNullOrEmpty()) + return resourceProvider + .get() + .getString(resId = R.string.bbps_state_bills_heading, stateValue) + return billerListStateHeading.replace(STATE_PLACEHOLDER, stateValue) + } + fun updateSearchQueryStringState(searchQuery: String) { _searchQuery.update { searchQuery } } @@ -159,6 +173,10 @@ constructor( _billerListState.update { billerListState } } + private fun updatePayBillShimmerVisibility(isVisible: Boolean) { + _isPayBillShimmerVisible.update { isVisible } + } + suspend fun navigateToNextScreen(direction: Direction) { _navigateToNextScreen.emit(direction) } @@ -166,7 +184,7 @@ constructor( val showSpaceBelowSearchBar = combine(_isScreenFastagRecharge, _billerListState) { isScreenFastagRecharge, billerListState -> - !isScreenFastagRecharge && (billerListState !is BillerListState.FetchBillLoading) + !isScreenFastagRecharge && !isPayBillShimmerVisible.value } .stateIn( scope = viewModelScope, @@ -176,7 +194,7 @@ constructor( fun onRecentBillItemClicked(billItemEntity: BillItemEntity) { viewModelScope.safeLaunch(dispatcherProvider.io) { - updateBillerListState(BillerListState.FetchBillLoading) + updatePayBillShimmerVisibility(true) val billDetailsRequest = BillDetailsRequest( billerId = billItemEntity.billerId, @@ -195,10 +213,11 @@ constructor( delay(300) // updating state to loaded after navigation + updatePayBillShimmerVisibility(false) updateBillerListState(createLoadedStateFromBillerResponse(billerListResponse)) } else { notifyError(billDetailsResponse) - updateBillerListState(createLoadedStateFromBillerResponse(billerListResponse)) + updatePayBillShimmerVisibility(false) } } } @@ -256,7 +275,7 @@ constructor( fun onBillerItemClickedForPostpaid(billerItemEntity: BillerItemEntity) { viewModelScope.safeLaunch(dispatcherProvider.io) { // If all fields are valid, navigate to next screen - updateBillerListState(BillerListState.FetchBillLoading) + updatePayBillShimmerVisibility(true) val billerDetailsResponse = billerListRepository.fetchBillerDetails(billerItemEntity = billerItemEntity) @@ -277,7 +296,7 @@ constructor( } } else { notifyError(billerDetailsResponse) - updateBillerListState(createLoadedStateFromBillerResponse(billerListResponse)) + updatePayBillShimmerVisibility(false) } } } @@ -312,11 +331,10 @@ constructor( ) delay(300) // updating state to loaded after navigation - - updateBillerListState(createLoadedStateFromBillerResponse(billerListResponse)) + updatePayBillShimmerVisibility(false) } else { notifyError(billDetailsResponse) - updateBillerListState(createLoadedStateFromBillerResponse(billerListResponse)) + updatePayBillShimmerVisibility(false) } } @@ -383,7 +401,7 @@ constructor( @OptIn(FlowPreview::class) private fun initSearchFlow() { searchQuery - .debounce(600) + .debounce { if (it.isEmpty()) 0 else 600 } .map { searchInput -> searchInput.trim() } .distinctUntilChanged() .flowOn(dispatcherProvider.io) @@ -392,7 +410,10 @@ constructor( searchJob?.cancel() searchJob = viewModelScope.launch { - fetchBillerList(isFromSearch = true, searchQuery = searchInput) + fetchBillerList( + isFromSearch = getIsFromSearch(searchQuery.value), + searchQuery = searchInput + ) } } catch (e: Exception) { FirebaseCrashlytics.getInstance().recordException(e) @@ -401,6 +422,11 @@ constructor( .launchIn(viewModelScope) } + // if search query is not empty, then it is from search + private fun getIsFromSearch(searchQuery: String): Boolean { + return searchQuery.isNotEmpty() + } + private fun BillerGroupItemResponse.toBillerGroupItemEntity(): BillerGroupItemEntity { return BillerGroupItemEntity( title = this.title, @@ -412,15 +438,20 @@ constructor( return BillerItemEntity( billerId = this.billerId, billerName = this.billerName, - billerLogoUrl = this.billerLogoUrl, - status = this.status, - isAdhoc = this.isAdhoc + billerLogoUrl = this.billerLogoUrl ?: "", + status = this.status ?: "", + isAdhoc = this.isAdhoc ?: false, + state = this.state ?: "", + categoryId = this.categoryId ) } private var billerListResponse: RepoResult = RepoResult() private suspend fun fetchBillerList(isFromSearch: Boolean = false, searchQuery: String = "") { + val categoryIdClicked = billCategoryEntity.categoryId + val cachedBillers = bbpsCommonRepository.getBillersByCategoryFromLocalDb(categoryIdClicked) + if (isFromSearch) { _isSearchBillerRunning.update { true } } else { @@ -428,37 +459,113 @@ constructor( } val deviceLocation = deviceLocationProvider.getDeviceLocation() - billerListResponse = - billerListRepository.fetchBillerList( - categoryId = billCategoryEntity.categoryId, - billerListRequest = - BillerListRequest( - latitude = deviceLocation.latitude, - longitude = deviceLocation.longitude, - state = deviceLocation.state, - searchParams = searchQuery - ) - ) - naviBbpsAnalytics.onBillersFetched( - deviceLocation = deviceLocation, - billerGroupsCount = billerListResponse.data?.billerGroups?.size ?: 0, - recentBillsCount = billerListResponse.data?.recentBills?.bills?.size ?: 0 - ) - delay(200) // To avoid jerky transition during plan refresh - _isSearchBillerRunning.update { false } - if (billerListResponse.isSuccessWithData()) { - val fullBillerList = - billerListResponse.data!!.billerGroups.orEmpty().flatMap { it.billers.orEmpty() } - updateBillerListState(createLoadedStateFromBillerResponse(billerListResponse)) - updateRecentBillsState() - _isQueriedListEmpty.update { fullBillerList.isEmpty() } + if (cachedBillers.isEmpty() || searchQuery.isNotEmpty()) { + billerListResponse = + billerListRepository.fetchBillerList( + categoryId = billCategoryEntity.categoryId, + billerListRequest = + BillerListRequest( + latitude = deviceLocation.latitude, + longitude = deviceLocation.longitude, + state = deviceLocation.state, + searchParams = searchQuery + ) + ) + naviBbpsAnalytics.onBillersFetched( + deviceLocation = deviceLocation, + billerGroupsCount = billerListResponse.data?.billerGroups?.size ?: 0, + recentBillsCount = billerListResponse.data?.recentBills?.bills?.size ?: 0 + ) + + _isSearchBillerRunning.update { false } + if (billerListResponse.isSuccessWithData()) { + val fullBillerList = + billerListResponse.data!!.billerGroups.orEmpty().flatMap { + it.billers.orEmpty() + } + + updateBillerListState(createLoadedStateFromBillerResponse(billerListResponse)) + updateRecentBillsState() + _isQueriedListEmpty.update { fullBillerList.isEmpty() } + } else { + updateBillerListState(BillerListState.Error) + notifyError(billerListResponse) + } } else { - updateBillerListState(BillerListState.Error) - notifyError(billerListResponse) + _isQueriedListEmpty.update { cachedBillers.isEmpty() } + + val stateBillers = cachedBillers.filter { it.state == deviceLocation.state } + + val billerGroupItemEntities = mutableListOf() + + if (stateBillers.isNotEmpty()) { + val billerGroupItem = + BillerGroupItemEntity( + title = + replaceIfPresent( + billerListStateHeading = billCategoryEntity.billerListStateHeading, + stateValue = deviceLocation.state + ), + billers = stateBillers + ) + billerGroupItemEntities.add(billerGroupItem) + } + + billerGroupItemEntities.add( + BillerGroupItemEntity( + title = + billCategoryEntity.billerListAllHeading.ifEmpty { + resourceProvider + .get() + .getString(R.string.bbps_all_biller_list_default_heading) + }, + billers = cachedBillers + ) + ) + + val recentBillsEntity: RecentBillsEntity = + if (categoryIdClicked != CATEGORY_ID_MOBILE_POSTPAID) { + getRecentBills() + } else { + RecentBillsEntity(title = "", bills = listOf()) + } + + updateBillerListState( + BillerListState.Loaded( + recentBills = recentBillsEntity, + billerGroups = billerGroupItemEntities + ) + ) + updateRecentBillsState() } } + private suspend fun getRecentBills(): RecentBillsEntity { + val savedBills = + myBillsRepository.fetchSavedBillsByCategory(category = billCategoryEntity.categoryId) + + val billItemEntities = + savedBills.map { myBillEntity -> + BillItemEntity( + billerId = myBillEntity.billerId, + billerName = myBillEntity.billerName, + billerLogoUrl = myBillEntity.billerLogoUrl, + status = myBillEntity.status, + isAdhoc = myBillEntity.isAdhoc, + paymentAmountExactness = myBillEntity.paymentAmountExactness, + formattedLastPaidDate = myBillEntity.formattedLastPaidDate, + primaryCustomerParam = myBillEntity.primaryCustomerParamValue, + customerParams = myBillEntity.customerParams + ) + } + + return RecentBillsEntity( + title = resourceProvider.get().getString(R.string.bbps_recent_bills), + bills = billItemEntities + ) + } + private fun createLoadedStateFromBillerResponse( billerListResponse: RepoResult ): BillerListState.Loaded { @@ -506,13 +613,19 @@ constructor( billerName = it.billerName, billerLogoUrl = it.billerLogoUrl, status = it.status, - isAdhoc = it.isAdhoc + isAdhoc = it.isAdhoc, + state = it.state, + categoryId = it.categoryId ) } ) } ) } + + companion object { + const val STATE_PLACEHOLDER = "{state}" + } } class MyNumberState(val isVisible: Boolean, val phoneContactEntity: PhoneContactEntity) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/dao/BillerListDao.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/dao/BillerListDao.kt new file mode 100644 index 0000000000..860828b14b --- /dev/null +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/dao/BillerListDao.kt @@ -0,0 +1,36 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.feature.billerlist.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import com.navi.bbps.db.NaviBbpsAppDatabase.Companion.NAVI_BBPS_DATABASE_BILLERS +import com.navi.bbps.feature.billerlist.model.view.BillerItemEntity + +@Dao +interface BillerListDao { + @Transaction + suspend fun refreshBillers(billers: List) { + deleteAll() + insertBillers(billers = billers) + } + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertBillers(billers: List) + + @Query("SELECT * FROM $NAVI_BBPS_DATABASE_BILLERS") + suspend fun getAllBillers(): List + + @Query("SELECT * FROM $NAVI_BBPS_DATABASE_BILLERS WHERE categoryId = :categoryId") + suspend fun getBillersByCategory(categoryId: String): List + + @Query("DELETE FROM $NAVI_BBPS_DATABASE_BILLERS") suspend fun deleteAll() +} diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/network/BillerItemListResponse.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/network/BillerItemListResponse.kt new file mode 100644 index 0000000000..0a10eaac0e --- /dev/null +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/network/BillerItemListResponse.kt @@ -0,0 +1,14 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.feature.billerlist.model.network + +import com.google.gson.annotations.SerializedName + +data class BillerItemListResponse( + @SerializedName("billers") val billers: List? +) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/network/BillerListResponse.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/network/BillerListResponse.kt index 269472df36..19d036ac7a 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/network/BillerListResponse.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/network/BillerListResponse.kt @@ -40,6 +40,8 @@ data class BillerItemResponse( @SerializedName("billerId") val billerId: String, @SerializedName("billerName") val billerName: String, @SerializedName("billerLogoUrl") val billerLogoUrl: String?, - @SerializedName("status") val status: String, - @SerializedName("isAdhoc") val isAdhoc: Boolean + @SerializedName("status") val status: String?, + @SerializedName("isAdhoc") val isAdhoc: Boolean?, + @SerializedName("state") val state: String?, + @SerializedName("categoryId") val categoryId: String, ) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListEntity.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListEntity.kt index 43b9c7d844..b15e2188b4 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListEntity.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListEntity.kt @@ -8,6 +8,9 @@ package com.navi.bbps.feature.billerlist.model.view import android.os.Parcelable +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.navi.bbps.db.NaviBbpsAppDatabase.Companion.NAVI_BBPS_DATABASE_BILLERS import com.navi.bbps.feature.paybill.model.network.PaymentAmountExactness import kotlinx.parcelize.Parcelize @@ -33,10 +36,13 @@ data class BillItemEntity( data class BillerGroupItemEntity(val title: String, val billers: List) @Parcelize +@Entity(tableName = NAVI_BBPS_DATABASE_BILLERS) data class BillerItemEntity( - val billerId: String, + @PrimaryKey val billerId: String, val billerName: String, val billerLogoUrl: String?, val status: String, - val isAdhoc: Boolean + val isAdhoc: Boolean, + val state: String, + val categoryId: String ) : Parcelable diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListState.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListState.kt index eafee2f64f..2f0324cda4 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListState.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/model/view/BillerListState.kt @@ -15,7 +15,5 @@ sealed class BillerListState { val billerGroups: List, ) : BillerListState() - data object FetchBillLoading : BillerListState() - data object Error : BillerListState() } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListScreen.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListScreen.kt index 106951763e..2eeec56b49 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListScreen.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/BillerListScreen.kt @@ -92,6 +92,8 @@ fun BillerListScreen( val phoneNumberDetailState by billerListViewModel.myNumberState.collectAsStateWithLifecycle() val showSpaceBelowSearchBar by billerListViewModel.showSpaceBelowSearchBar.collectAsStateWithLifecycle() + val isPayBillShimmerVisible by + billerListViewModel.isPayBillShimmerVisible.collectAsStateWithLifecycle() val bottomSheetState = rememberModalBottomSheetState( @@ -185,7 +187,7 @@ fun BillerListScreen( title = billCategoryEntity.title, onNavigationIconClick = { onBackClick() }, ) - if (billerListState !is BillerListState.FetchBillLoading) { + if (!isPayBillShimmerVisible) { RenderSearchBarSection( modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), phoneNumberDetailState = phoneNumberDetailState, @@ -219,12 +221,10 @@ fun BillerListScreen( ) onRecentBillItemClicked(it) }, - isSearchBillerRunning = isSearchBillerRunning + isSearchBillerRunning = isSearchBillerRunning, + isPayBillShimmerVisible = isPayBillShimmerVisible ) } - is BillerListState.FetchBillLoading -> { - RenderPayBillScreenLoading() - } is BillerListState.Error -> {} } } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/RenderBillerListScreen.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/RenderBillerListScreen.kt index 253c46ab17..396fcf5718 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/RenderBillerListScreen.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/ui/RenderBillerListScreen.kt @@ -74,7 +74,8 @@ fun RenderBillerListScreen( openSheet: () -> Unit, onBillerItemClicked: (BillerItemEntity) -> Unit, onRecentBillItemClicked: (BillItemEntity) -> Unit, - isSearchBillerRunning: Boolean + isSearchBillerRunning: Boolean, + isPayBillShimmerVisible: Boolean ) { val isScreenFastagRecharge by billerListViewModel.isScreenFastagRecharge.collectAsStateWithLifecycle() @@ -94,126 +95,137 @@ fun RenderBillerListScreen( } } - Column( - modifier = - Modifier.fillMaxSize() - .padding( - start = NaviBbpsDimens.horizontalMargin, - end = NaviBbpsDimens.horizontalMargin, - bottom = 16.dp - ) - .nestedScroll(nestedScrollConnection) - ) { - if (isScreenFastagRecharge) { - Spacer(modifier = Modifier.height(8.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Bottom - ) { - Text( - text = stringResource(id = R.string.bbps_how_to_find_your_fastag_bank), - fontFamily = ttComposeFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - fontSize = 12.sp, - color = NaviBbpsColor.textTertiary, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.bbps_view), - fontFamily = ttComposeFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), - fontSize = 14.sp, - color = NaviBbpsColor.textPrimary, - textDecoration = TextDecoration.Underline, - modifier = - Modifier.align(Alignment.CenterVertically).noRippleClickableWithDebounce { - keyboardController?.customHide(context = context, view = view) - openSheet() - } - ) - } - Spacer(modifier = Modifier.height(24.dp)) - } - - AnimatedVisibility( - modifier = Modifier.padding(vertical = 16.dp), - visible = isSearchBillerRunning - ) { - Column { - Row(modifier = Modifier.fillMaxWidth()) { - NaviBbpsLottieAnimation( - modifier = Modifier.size(16.dp), - lottieFileName = GENERIC_LOADER_LOTTIE_FILE_NAME, - showLottieInfiniteTimes = true + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = + Modifier.fillMaxSize() + .padding( + start = NaviBbpsDimens.horizontalMargin, + end = NaviBbpsDimens.horizontalMargin, + bottom = 16.dp ) - Spacer(modifier = Modifier.width(8.dp)) + .nestedScroll(nestedScrollConnection) + ) { + if (isScreenFastagRecharge) { + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + ) { Text( - text = stringResource(id = R.string.bbps_searching), + text = stringResource(id = R.string.bbps_how_to_find_your_fastag_bank), + fontFamily = ttComposeFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + fontSize = 12.sp, + color = NaviBbpsColor.textTertiary, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.bbps_view), fontFamily = ttComposeFontFamily, fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), - fontSize = 12.sp, - color = NaviBbpsColor.textTertiary + fontSize = 14.sp, + color = NaviBbpsColor.textPrimary, + textDecoration = TextDecoration.Underline, + modifier = + Modifier.align(Alignment.CenterVertically) + .noRippleClickableWithDebounce { + keyboardController?.customHide(context = context, view = view) + openSheet() + } ) } + Spacer(modifier = Modifier.height(24.dp)) } - } - if (isQueriedListEmpty) { - Column( - modifier = - Modifier.fillMaxSize().align(Alignment.CenterHorizontally).padding(top = 32.dp) + AnimatedVisibility( + modifier = Modifier.padding(vertical = 16.dp), + visible = isSearchBillerRunning ) { - Spacer(modifier = Modifier.height(16.dp)) - Image( - painter = painterResource(id = R.drawable.ic_bbps_no_billers_icon), - contentDescription = stringResource(id = R.string.bbps_no_billers_found), - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = stringResource(id = R.string.bbps_no_billers_found), - fontFamily = ttComposeFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), - fontSize = 14.sp, - color = NaviBbpsColor.textTertiary, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) + Column { + Row(modifier = Modifier.fillMaxWidth()) { + NaviBbpsLottieAnimation( + modifier = Modifier.size(16.dp), + lottieFileName = GENERIC_LOADER_LOTTIE_FILE_NAME, + showLottieInfiniteTimes = true + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(id = R.string.bbps_searching), + fontFamily = ttComposeFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), + fontSize = 12.sp, + color = NaviBbpsColor.textTertiary + ) + } + } } - } - LazyColumn(modifier = Modifier.fillMaxSize(), flingBehavior = maxScrollFlingBehavior()) { - if (isSearchQueryEmpty and isRecentBillsMoreThanZero) { - item { RecentBillTitle(billerListState = billerListState) } - items(count = billerListState.recentBills.bills.size) { item -> - val billItem = billerListState.recentBills.bills[item] - RecentBillerItem( - billItemEntity = billItem, - primaryCustomerParamValue = billItem.primaryCustomerParam, - isLastPaidDateVisible = - billItem.formattedLastPaidDate.isNotNullAndNotEmpty(), - onRecentBillItemClicked = { onRecentBillItemClicked(billItem) } + if (isQueriedListEmpty) { + Column( + modifier = + Modifier.fillMaxSize() + .align(Alignment.CenterHorizontally) + .padding(top = 32.dp) + ) { + Spacer(modifier = Modifier.height(16.dp)) + Image( + painter = painterResource(id = R.drawable.ic_bbps_no_billers_icon), + contentDescription = stringResource(id = R.string.bbps_no_billers_found), + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = R.string.bbps_no_billers_found), + fontFamily = ttComposeFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), + fontSize = 14.sp, + color = NaviBbpsColor.textTertiary, + modifier = Modifier.align(Alignment.CenterHorizontally) ) } } - billerListState.billerGroups.forEach { group -> - if (isSearchQueryEmpty) { - item { BillerTitle(billerGroupItemEntity = group) } + LazyColumn( + modifier = Modifier.fillMaxSize(), + flingBehavior = maxScrollFlingBehavior() + ) { + if (isSearchQueryEmpty and isRecentBillsMoreThanZero) { + item { RecentBillTitle(billerListState = billerListState) } + items(count = billerListState.recentBills.bills.size) { item -> + val billItem = billerListState.recentBills.bills[item] + RecentBillerItem( + billItemEntity = billItem, + primaryCustomerParamValue = billItem.primaryCustomerParam, + isLastPaidDateVisible = + billItem.formattedLastPaidDate.isNotNullAndNotEmpty(), + onRecentBillItemClicked = { onRecentBillItemClicked(billItem) } + ) + } } - items( - count = group.billers.size, - key = { billerItem -> "${group.billers[billerItem].billerId}${group.title}" } - ) { billerItem -> - val item = group.billers[billerItem] - BillerItem( - billerItemEntity = item, - onItemClicked = { onBillerItemClicked(item) } - ) + + billerListState.billerGroups.forEach { group -> + if (isSearchQueryEmpty) { + item { BillerTitle(billerGroupItemEntity = group) } + } + items( + count = group.billers.size, + key = { billerItem -> + "${group.billers[billerItem].billerId}${group.title}" + } + ) { billerItem -> + val item = group.billers[billerItem] + BillerItem( + billerItemEntity = item, + onItemClicked = { onBillerItemClicked(item) } + ) + } } } } + if (isPayBillShimmerVisible) RenderPayBillScreenLoading() } } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/BillHistoryDetailsViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/BillHistoryDetailsViewModel.kt index a6bb4371c8..11021ef99c 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/BillHistoryDetailsViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/BillHistoryDetailsViewModel.kt @@ -263,7 +263,9 @@ constructor( categoryId = myBillEntity.categoryId, title = myBillEntity.categoryName, iconUrl = "", - searchBoxPlaceholderText = "" + searchBoxPlaceholderText = "", + billerListStateHeading = "", + billerListAllHeading = "" ), payBillScreenSource = PayBillSource.Others( @@ -323,7 +325,9 @@ constructor( categoryId = myBillEntity.categoryId, title = myBillEntity.categoryName, iconUrl = "", - searchBoxPlaceholderText = "" + searchBoxPlaceholderText = "", + billerListStateHeading = "", + billerListAllHeading = "" ) ) ) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoriesViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoriesViewModel.kt index 969f195c0f..29bea67134 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoriesViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoriesViewModel.kt @@ -164,7 +164,9 @@ constructor( .getString( R.string.bbps_default_searchbox_placeholder ) - } + }, + billerListStateHeading = category.billerListStateHeading ?: "", + billerListAllHeading = category.billerListAllHeading ?: "" ) } ) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/model/network/BillCategoriesResponse.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/model/network/BillCategoriesResponse.kt index 8f00d77b10..990be6a9c6 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/model/network/BillCategoriesResponse.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/model/network/BillCategoriesResponse.kt @@ -25,5 +25,7 @@ data class CategoryItemResponse( @SerializedName("title") val title: String, @SerializedName("iconUrl") val iconUrl: String?, @SerializedName("order") val order: Int? = 0, - @SerializedName("searchBoxPlaceholderText") val searchBoxPlaceholderText: String? + @SerializedName("searchBoxPlaceholderText") val searchBoxPlaceholderText: String?, + @SerializedName("billerListStateHeading") val billerListStateHeading: String?, + @SerializedName("billerListAllHeading") val billerListAllHeading: String?, ) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/model/view/BillCategoryGroupEntity.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/model/view/BillCategoryGroupEntity.kt index 8681969ac3..9b7e0c6b5d 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/model/view/BillCategoryGroupEntity.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/model/view/BillCategoryGroupEntity.kt @@ -26,7 +26,9 @@ data class BillCategoryEntity( @PrimaryKey val categoryId: String, val title: String, val iconUrl: String, - val searchBoxPlaceholderText: String + val searchBoxPlaceholderText: String, + val billerListStateHeading: String, + val billerListAllHeading: String ) : Parcelable { val fallbackImageResId get() = R.drawable.ic_bbps_biller_placeholder diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/common/ui/BbpsRoutingLauncherScreen.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/common/ui/BbpsRoutingLauncherScreen.kt index c5190c6343..e8d805df7f 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/common/ui/BbpsRoutingLauncherScreen.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/common/ui/BbpsRoutingLauncherScreen.kt @@ -56,7 +56,9 @@ fun BbpsRoutingLauncherScreen( title = bundle.getString(BBPS_CATEGORY_TITLE) ?: "", iconUrl = bundle.getString(BBPS_CATEGORY_ICON_URL) ?: "", searchBoxPlaceholderText = - bundle.getString(BBPS_BILLER_SEARCH_BOX_PLACEHOLDER_TEXT) ?: "" + bundle.getString(BBPS_BILLER_SEARCH_BOX_PLACEHOLDER_TEXT) ?: "", + billerListStateHeading = "", + billerListAllHeading = "", ), isRootScreen = true ) @@ -69,7 +71,9 @@ fun BbpsRoutingLauncherScreen( title = bundle.getString(BBPS_CATEGORY_TITLE) ?: "", iconUrl = bundle.getString(BBPS_CATEGORY_ICON_URL) ?: "", searchBoxPlaceholderText = - bundle.getString(BBPS_BILLER_SEARCH_BOX_PLACEHOLDER_TEXT) ?: "" + bundle.getString(BBPS_BILLER_SEARCH_BOX_PLACEHOLDER_TEXT) ?: "", + billerListStateHeading = "", + billerListAllHeading = "" ), isRootScreen = true ) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ContactListViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ContactListViewModel.kt index 36cb1a1ca9..af13397006 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ContactListViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ContactListViewModel.kt @@ -31,7 +31,6 @@ import com.ramcosta.composedestinations.spec.Direction import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -215,7 +214,6 @@ constructor( listOf() } } - delay(300) // a bit lesser than transition animation duration updateContactListUIState( contactListState = ContactListState.Loaded(allContactList = _allContactList.value) ) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsDao.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsDao.kt index 0a6ee12fb2..e7712ba4ae 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsDao.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsDao.kt @@ -22,7 +22,7 @@ interface MyBillsDao { fun getAllBills(): Flow> @Query("SELECT * FROM $NAVI_BBPS_TABLE_MY_SAVED_BILLS WHERE categoryId == :categoryId") - fun getBillsByCategory(categoryId: String): Flow> + suspend fun getBillsByCategory(categoryId: String): List @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(myBills: List) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsRepository.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsRepository.kt index 173a6915f5..e037db8020 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsRepository.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsRepository.kt @@ -24,4 +24,7 @@ constructor( } fun fetchMySavedBills() = myBillsDao.getAllBills() + + suspend fun fetchSavedBillsByCategory(category: String) = + myBillsDao.getBillsByCategory(category) } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/prepaidrecharge/PrepaidRechargeViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/prepaidrecharge/PrepaidRechargeViewModel.kt index c40981339f..17286fad47 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/prepaidrecharge/PrepaidRechargeViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/prepaidrecharge/PrepaidRechargeViewModel.kt @@ -584,7 +584,9 @@ constructor( categoryId = billCategoryEntity.categoryId, title = billCategoryEntity.title, iconUrl = billCategoryEntity.iconUrl, - searchBoxPlaceholderText = billCategoryEntity.searchBoxPlaceholderText + searchBoxPlaceholderText = billCategoryEntity.searchBoxPlaceholderText, + billerListStateHeading = billCategoryEntity.billerListStateHeading, + billerListAllHeading = billCategoryEntity.billerListAllHeading ) ) ) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/network/service/NaviBbpsRetrofitService.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/network/service/NaviBbpsRetrofitService.kt index e5529195a9..4a2ebc2b3f 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/network/service/NaviBbpsRetrofitService.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/network/service/NaviBbpsRetrofitService.kt @@ -8,6 +8,7 @@ package com.navi.bbps.network.service import com.navi.bbps.common.model.network.BbpsGenericResponse +import com.navi.bbps.feature.billerlist.model.network.BillerItemListResponse import com.navi.bbps.feature.billerlist.model.network.BillerListRequest import com.navi.bbps.feature.billerlist.model.network.BillerListResponse import com.navi.bbps.feature.billhistorydetail.model.network.BillTransactionHistoryResponse @@ -102,4 +103,7 @@ interface NaviBbpsRetrofitService { suspend fun getConfig( @Query("configKeyList") commaSeparatedConfigKeys: String ): Response> + + @GET("/billpay-gateway/$NAVI_BBPS_API_VERSION/billpay/categories/billers") + suspend fun getFullBillerList(): Response> } diff --git a/android/navi-bbps/src/main/res/values/strings.xml b/android/navi-bbps/src/main/res/values/strings.xml index c1c0af7ec8..6dbe472227 100644 --- a/android/navi-bbps/src/main/res/values/strings.xml +++ b/android/navi-bbps/src/main/res/values/strings.xml @@ -79,6 +79,8 @@ Maximum accepted amount is ₹%s Only amount accepted is ₹%s Search… + Billers of your state + All billers My number Are you sure you want to delete? All your bill payment history will get removed. Are you sure you want to delete this account? @@ -118,4 +120,6 @@ Disclaimer- We support most of the recharges, but please check with your operator before you proceed. Disclaimer- View contacts + Recent bills + Billers in %s \ No newline at end of file