From 8b02f96c1ce6251ca5d494bb2e3cdd272c0a504c Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 4 Jul 2025 00:47:20 +0530 Subject: [PATCH] Refactored the ZimManageViewModel to fetch the library reactively from the network and laid the groundwork for applying filters in network requests. --- .../library/online/OnlineLibraryFragment.kt | 64 +++-- .../library/online/OnlineLibraryScreen.kt | 48 ++++ .../online/OnlineLibraryScreenState.kt | 10 +- .../zimManager/OnlineLibraryManager.kt | 8 +- .../zimManager/ZimManageViewModel.kt | 238 +++++++++++++----- 5 files changed, 284 insertions(+), 84 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryFragment.kt index 023d2f7cb..d3265c9c5 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryFragment.kt @@ -57,6 +57,7 @@ import org.kiwix.kiwixmobile.core.R.string import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions +import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.ITEMS_PER_PAGE import org.kiwix.kiwixmobile.core.downloader.Downloader import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission @@ -76,6 +77,7 @@ import org.kiwix.kiwixmobile.core.navigateToAppSettings import org.kiwix.kiwixmobile.core.navigateToSettings import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon +import org.kiwix.kiwixmobile.core.ui.components.ONE import org.kiwix.kiwixmobile.core.ui.components.rememberBottomNavigationVisibility import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem import org.kiwix.kiwixmobile.core.ui.models.IconItem @@ -89,11 +91,13 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower import org.kiwix.kiwixmobile.core.utils.dialog.DialogHost import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog +import org.kiwix.kiwixmobile.core.utils.files.Log import org.kiwix.kiwixmobile.core.zim_manager.NetworkState import org.kiwix.kiwixmobile.main.KiwixMainActivity import org.kiwix.kiwixmobile.storage.STORAGE_SELECT_STORAGE_TITLE_TEXTVIEW_SIZE import org.kiwix.kiwixmobile.storage.StorageSelectDialog import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel +import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.OnlineLibraryRequest import org.kiwix.kiwixmobile.zimManager.libraryView.AvailableSpaceCalculator import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem import javax.inject.Inject @@ -138,11 +142,37 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { isSearchActive = false, searchText = "", searchValueChangedListener = { onSearchValueChanged(it) }, - clearSearchButtonClickListener = { onSearchClear() } + clearSearchButtonClickListener = { onSearchClear() }, + onLoadMore = { totalItemShowingCount -> + loadMoreBooksFromNetwork(totalItemShowingCount) + }, + isLoadingMoreItem = false ) ) } + private fun loadMoreBooksFromNetwork(totalItemShowingCount: Int) { + val totalResults = zimManageViewModel.onlineLibraryManager.totalResult + val totalPages = + zimManageViewModel.onlineLibraryManager.calculateTotalPages( + totalResults, + ITEMS_PER_PAGE + ) + val currentPage = totalItemShowingCount / ITEMS_PER_PAGE + val nextPage = currentPage + ONE + + if (nextPage < totalPages) { + zimManageViewModel.updateOnlineLibraryFilters( + zimManageViewModel.onlineLibraryRequest.value.copy( + page = nextPage, + isLoadMoreItem = true + ) + ) + } else { + Log.d("OnlineLibrary", "All pages loaded") + } + } + private fun onSearchClear() { onlineLibraryScreenState.value.update { copy(searchText = "") @@ -250,9 +280,17 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { } observeViewModelData() showPreviouslySearchedTextInSearchView() - startDownloadingLibrary() + startDownloadingLibrary(getOnlineLibraryRequest()) } + private fun getOnlineLibraryRequest(): OnlineLibraryRequest = OnlineLibraryRequest( + null, + null, + null, + false, + 1 + ) + private fun observeViewModelData() { zimManageViewModel.apply { // Observe when library items changes. @@ -265,12 +303,13 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { } // Observe when online library downloading. onlineLibraryDownloading - .onEach { - if (it) { + .onEach { (initialLibraryDownloading, loadingMoreItem) -> + if (initialLibraryDownloading) { showProgressBarOfFetchingOnlineLibrary() } else { hideProgressBarOfFetchingOnlineLibrary() } + onlineLibraryScreenState.value.update { copy(isLoadingMoreItem = loadingMoreItem) } }.launchIn(viewLifecycleOwner.lifecycleScope) // Observe when library list refreshing e.g. applying filters. libraryListIsRefreshing.observe( @@ -369,7 +408,7 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { // User allowed downloading over mobile data. // Since the download flow now triggers only when appropriate, // we start the library download explicitly after updating the preference. - startDownloadingLibrary(true) + startDownloadingLibrary(getOnlineLibraryRequest()) }, { context.toast( @@ -456,18 +495,15 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { NetworkState.CONNECTED -> { when { NetworkUtils.isWiFi(requireContext()) -> { - if (!zimManageViewModel.isOnlineLibraryDownloading) { - refreshFragment(false) - } + refreshFragment(false) } noWifiWithWifiOnlyPreferenceSet -> { hideRecyclerviewAndShowSwipeDownForLibraryErrorText() } - onlineLibraryScreenState.value.value.onlineLibraryList.isNullOrEmpty() && - !zimManageViewModel.isOnlineLibraryDownloading -> { - startDownloadingLibrary(true) + onlineLibraryScreenState.value.value.onlineLibraryList.isNullOrEmpty() -> { + startDownloadingLibrary(getOnlineLibraryRequest()) showProgressBarOfFetchingOnlineLibrary() } } @@ -525,15 +561,15 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { if (isNotConnected) { showNoInternetConnectionError() } else { - startDownloadingLibrary(isExplicitRefresh) + startDownloadingLibrary(getOnlineLibraryRequest()) if (isExplicitRefresh) { showRecyclerviewAndHideSwipeDownForLibraryErrorText() } } } - private fun startDownloadingLibrary(isExplicitRefresh: Boolean = false) { - zimManageViewModel.requestOnlineLibraryIfNeeded(isExplicitRefresh) + private fun startDownloadingLibrary(onlineLibraryRequest: OnlineLibraryRequest) { + zimManageViewModel.updateOnlineLibraryFilters(onlineLibraryRequest) } private fun downloadFile() { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryScreen.kt index e0a1e717d..5a4937cc0 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryScreen.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState @@ -42,6 +43,9 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -51,7 +55,11 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import org.kiwix.kiwixmobile.core.R.string +import org.kiwix.kiwixmobile.core.downloader.downloadManager.FIVE +import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.extensions.hideKeyboardOnLazyColumnScroll import org.kiwix.kiwixmobile.core.main.reader.rememberScrollBehavior import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar @@ -194,6 +202,46 @@ private fun OnlineLibraryList(state: OnlineLibraryScreenState, lazyListState: La } } } + showLoadMoreProgressBar(state.isLoadingMoreItem) + } + + LaunchedEffect(state.isLoadingMoreItem) { + if (state.isLoadingMoreItem) { + // Scroll to the last item (i.e., the loading spinner) + val lastItemIndex = state.onlineLibraryList?.size ?: 0 + lazyListState.animateScrollToItem(lastItemIndex) + } + } + + LaunchedEffect(lazyListState) { + snapshotFlow { + derivedStateOf { + val layoutInfo = lazyListState.layoutInfo + val totalItems = layoutInfo.totalItemsCount + val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: ZERO + (totalItems > ZERO && lastVisibleItemIndex >= (totalItems - FIVE)) to totalItems + }.value + } + .distinctUntilChanged() + .filter { it.first } + .collect { (_, totalItems) -> + state.onLoadMore(totalItems) + } + } +} + +private fun LazyListScope.showLoadMoreProgressBar(isLoadingMoreItem: Boolean) { + if (isLoadingMoreItem) { + item { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(SIXTEEN_DP), + contentAlignment = Alignment.Center + ) { + ContentLoadingProgressBar() + } + } } } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryScreenState.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryScreenState.kt index 5dc750875..d30e848ed 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryScreenState.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/online/OnlineLibraryScreenState.kt @@ -99,5 +99,13 @@ data class OnlineLibraryScreenState( /** * Triggers when clear button clicked. */ - val clearSearchButtonClickListener: () -> Unit + val clearSearchButtonClickListener: () -> Unit, + /** + * Triggers when user at the end of the online content. + */ + val onLoadMore: (Int) -> Unit, + /** + * Manages the showing of progressBar at the end of book list when more items is loading. + */ + val isLoadingMoreItem: Boolean ) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/OnlineLibraryManager.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/OnlineLibraryManager.kt index b9b412c93..6e6d79916 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/OnlineLibraryManager.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/OnlineLibraryManager.kt @@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.zimManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.ITEMS_PER_PAGE import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.OPDS_LIBRARY_ENDPOINT import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.entity.LibkiwixBook @@ -44,7 +45,8 @@ class OnlineLibraryManager @Inject constructor( urlHost: String ): ArrayList? = runCatching { - content?.let { totalResult = extractTotalResults(it) } + if (content == null) return null + totalResult = extractTotalResults(content) val onlineBooksList = arrayListOf() val tempLibrary = Library() val tempManager = Manager(tempLibrary) @@ -83,8 +85,8 @@ class OnlineLibraryManager @Inject constructor( */ fun buildLibraryUrl( baseUrl: String, - start: Int = 0, - count: Int = 50, + start: Int, + count: Int, query: String? = null, lang: String? = null, category: String? = null diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt index b4a471bec..61d831c62 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first @@ -53,6 +54,7 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request @@ -71,7 +73,6 @@ import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.ITEMS_PER_PAGE -import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.OPDS_LIBRARY_ENDPOINT import org.kiwix.kiwixmobile.core.data.remote.ProgressResponseBody import org.kiwix.kiwixmobile.core.data.remote.UserAgentInterceptor import org.kiwix.kiwixmobile.core.di.modules.CALL_TIMEOUT @@ -123,6 +124,7 @@ import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem.Book import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem.DividerItem import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem.LibraryDownloadItem import org.kiwix.libkiwix.Book +import retrofit2.Response import java.util.Locale import java.util.concurrent.TimeUnit.SECONDS import javax.inject.Inject @@ -148,7 +150,7 @@ class ZimManageViewModel @Inject constructor( private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil, - private val onlineLibraryManager: OnlineLibraryManager + val onlineLibraryManager: OnlineLibraryManager ) : ViewModel() { sealed class FileSelectActions { data class RequestNavigateTo(val bookOnDisk: BookOnDisk) : FileSelectActions() @@ -161,6 +163,19 @@ class ZimManageViewModel @Inject constructor( object UserClickedDownloadBooksButton : FileSelectActions() } + data class OnlineLibraryRequest( + val query: String?, + val category: String?, + val lang: String?, + val isLoadMoreItem: Boolean, + val page: Int + ) + + data class OnlineLibraryResult( + val onlineLibraryRequest: OnlineLibraryRequest, + val books: List + ) + private var isUnitTestCase: Boolean = false val sideEffects: MutableSharedFlow> = MutableSharedFlow() private val _libraryItems = MutableStateFlow>(emptyList()) @@ -168,20 +183,36 @@ class ZimManageViewModel @Inject constructor( val fileSelectListStates: MutableLiveData = MutableLiveData() val deviceListScanningProgress = MutableLiveData() val libraryListIsRefreshing = MutableLiveData() - val onlineLibraryDownloading = MutableStateFlow(false) + + /** + * Manages the showing of downloading online library progress, + * and showing the progressBar at the end of content when loading more items. + * + * A [Pair] containing: + * - [Boolean]: When initial content is downloading. + * - [Boolean]: When loading more item. + */ + val onlineLibraryDownloading = MutableStateFlow(false to false) val shouldShowWifiOnlyDialog = MutableLiveData() val networkStates = MutableLiveData() - val networkLibrary = MutableSharedFlow>(replay = 0) + val networkLibrary = MutableStateFlow>(emptyList()) val requestFileSystemCheck = MutableSharedFlow(replay = 0) val fileSelectActions = MutableSharedFlow() - private val requestDownloadLibrary = MutableSharedFlow( + private val requestDownloadLibrary = MutableSharedFlow( replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST ) - - @Volatile - var isOnlineLibraryDownloading = false + val onlineLibraryRequest: MutableStateFlow = + MutableStateFlow( + OnlineLibraryRequest( + query = null, + category = null, + lang = null, + isLoadMoreItem = false, + page = 0 + ) + ) val requestFiltering = MutableStateFlow("") val onlineBooksSearchedQuery = MutableLiveData() private val coroutineJobs: MutableList = mutableListOf() @@ -194,14 +225,6 @@ class ZimManageViewModel @Inject constructor( context.registerReceiver(connectivityBroadcastReceiver) } - fun requestOnlineLibraryIfNeeded(isExplicitRefresh: Boolean) { - if (isOnlineLibraryDownloading && !isExplicitRefresh) return - isOnlineLibraryDownloading = true - viewModelScope.launch { - requestDownloadLibrary.tryEmit(Unit) - } - } - fun setIsUnitTestCase() { isUnitTestCase = true } @@ -210,9 +233,18 @@ class ZimManageViewModel @Inject constructor( this.alertDialogShower = alertDialogShower } - private fun createKiwixServiceWithProgressListener(): KiwixService { + private fun createKiwixServiceWithProgressListener( + baseUrl: String, + start: Int = ZERO, + count: Int = ITEMS_PER_PAGE, + query: String? = null, + lang: String? = null, + category: String? = null, + shouldTrackProgress: Boolean + ): KiwixService { if (isUnitTestCase) return kiwixService - val contentLength = getContentLengthOfLibraryXmlFile() + val contentLength = + getContentLengthOfLibraryXmlFile(baseUrl, start, count, query, lang, category) val customOkHttpClient = OkHttpClient().newBuilder() .followRedirects(true) @@ -228,22 +260,19 @@ class ZimManageViewModel @Inject constructor( .addNetworkInterceptor(UserAgentInterceptor(USER_AGENT)) .addNetworkInterceptor { chain -> val originalResponse = chain.proceed(chain.request()) - originalResponse.body?.let { responseBody -> + val body = originalResponse.body + if (shouldTrackProgress && body != null) { originalResponse.newBuilder() - .body( - ProgressResponseBody( - responseBody, - appProgressListener, - contentLength - ) - ) + .body(ProgressResponseBody(body, appProgressListener, contentLength)) .build() - } ?: originalResponse + } else { + originalResponse + } } .build() return KiwixService.ServiceCreator.newHackListService( customOkHttpClient, - KIWIX_OPDS_LIBRARY_URL + baseUrl ) .also { kiwixService = it @@ -252,10 +281,19 @@ class ZimManageViewModel @Inject constructor( private var appProgressListener: AppProgressListenerProvider? = AppProgressListenerProvider(this) - private fun getContentLengthOfLibraryXmlFile(): Long { + private fun getContentLengthOfLibraryXmlFile( + baseUrl: String, + start: Int = ZERO, + count: Int = ITEMS_PER_PAGE, + query: String? = null, + lang: String? = null, + category: String? = null + ): Long { + val requestUrl = + onlineLibraryManager.buildLibraryUrl(baseUrl, start, count, query, lang, category) val headRequest = Request.Builder() - .url("$KIWIX_OPDS_LIBRARY_URL$OPDS_LIBRARY_ENDPOINT?count=$ITEMS_PER_PAGE") + .url(requestUrl) .head() .header("Accept-Encoding", "identity") .build() @@ -298,6 +336,7 @@ class ZimManageViewModel @Inject constructor( add(updateLanguagesInDao(networkLibrary, languages)) add(updateNetworkStates()) add(requestsAndConnectivityChangesToLibraryRequests(networkLibrary)) + add(onlineLibraryRequest()) } } @@ -311,6 +350,25 @@ class ZimManageViewModel @Inject constructor( super.onCleared() } + fun updateOnlineLibraryFilters(newRequest: OnlineLibraryRequest) { + onlineLibraryRequest.update { current -> + current.copy( + query = newRequest.query.takeUnless { it.isNullOrEmpty() } ?: current.query, + category = newRequest.category.takeUnless { it.isNullOrEmpty() } ?: current.category, + lang = newRequest.lang.takeUnless { it.isNullOrEmpty() } ?: current.lang, + page = newRequest.page, + isLoadMoreItem = newRequest.isLoadMoreItem + ) + } + } + + private fun onlineLibraryRequest() = onlineLibraryRequest + .drop(1) + .onEach { request -> + requestDownloadLibrary.tryEmit(request) + } + .launchIn(viewModelScope) + private fun scanBooksFromStorage(dispatcher: CoroutineDispatcher = Dispatchers.IO) = checkFileSystemForBooksOnRequest(books()) .catch { it.printStackTrace() } @@ -392,87 +450,135 @@ class ZimManageViewModel @Inject constructor( return None } + private fun updateDownloadState(isInitial: Boolean) { + onlineLibraryDownloading.tryEmit(isInitial to !isInitial) + } + + private fun resetDownloadState() { + onlineLibraryDownloading.tryEmit(false to false) + } + @OptIn(ExperimentalCoroutinesApi::class) private fun requestsAndConnectivityChangesToLibraryRequests( - library: MutableSharedFlow>, + library: MutableStateFlow>, dispatcher: CoroutineDispatcher = Dispatchers.IO - ) = requestDownloadLibrary.flatMapConcat { + ) = requestDownloadLibrary.flatMapConcat { onlineLibraryRequest -> connectivityBroadcastReceiver.networkStates .filter { networkState -> networkState == CONNECTED } .take(1) .flatMapConcat { - shouldProceedWithDownload() + updateDownloadState(onlineLibraryRequest.isLoadMoreItem.not()) + shouldProceedWithDownload(onlineLibraryRequest) .flatMapConcat { kiwixService -> - downloadLibraryFlow(kiwixService).also { - onlineLibraryDownloading.tryEmit(true) - } + downloadLibraryFlow(kiwixService, onlineLibraryRequest) } } } .filterNotNull() .catch { it.printStackTrace().also { - isOnlineLibraryDownloading = false - onlineLibraryDownloading.tryEmit(false) + resetDownloadState() library.emit(emptyList()) } } - .onEach { - library.emit(it).also { - // Setting this to true because once library downloaded we don't need to download again - // until user wants to refresh the online library. - isOnlineLibraryDownloading = true - onlineLibraryDownloading.tryEmit(false) + .onEach { result -> + networkLibrary.value = if (result.onlineLibraryRequest.isLoadMoreItem) { + networkLibrary.value + result.books + } else { + result.books } + resetDownloadState() } .flowOn(dispatcher) .launchIn(viewModelScope) - private fun shouldProceedWithDownload(): Flow { + private fun shouldProceedWithDownload(onlineLibraryRequest: OnlineLibraryRequest): Flow { + val baseUrl = KIWIX_OPDS_LIBRARY_URL + val start = + onlineLibraryManager.getStartOffset(onlineLibraryRequest.page.minus(ONE), ITEMS_PER_PAGE) + val shouldTrackProgress = !onlineLibraryRequest.isLoadMoreItem return if (connectivityManager.isWifi()) { - flowOf(createKiwixServiceWithProgressListener()) + flowOf( + createKiwixServiceWithProgressListener( + baseUrl, + start, + ITEMS_PER_PAGE, + onlineLibraryRequest.query, + onlineLibraryRequest.lang, + onlineLibraryRequest.category, + shouldTrackProgress + ) + ) } else { flow { val wifiOnly = sharedPreferenceUtil.prefWifiOnlys.first() if (wifiOnly) { + onlineLibraryDownloading.emit(false to false) shouldShowWifiOnlyDialog.postValue(true) // Don't emit anything — just return return@flow } - emit(createKiwixServiceWithProgressListener()) + emit( + createKiwixServiceWithProgressListener( + baseUrl, + start, + ITEMS_PER_PAGE, + onlineLibraryRequest.query, + onlineLibraryRequest.lang, + onlineLibraryRequest.category, + shouldTrackProgress + ) + ) } } } private fun downloadLibraryFlow( - kiwixService: KiwixService - ): Flow> = flow { - downloadProgress.postValue(context.getString(R.string.starting_downloading_remote_library)) - // TODO get the filter from online library and pass it here to get the online content based on filters. + kiwixService: KiwixService, + request: OnlineLibraryRequest + ): Flow = flow { + updateDownloadProgressIfNeeded( + request, + R.string.starting_downloading_remote_library + ) + val start = + onlineLibraryManager.getStartOffset(request.page.minus(ONE), ITEMS_PER_PAGE) val buildUrl = onlineLibraryManager.buildLibraryUrl( KIWIX_OPDS_LIBRARY_URL, + start, + ITEMS_PER_PAGE, + request.query, + request.lang, + request.category, ) val response = kiwixService.getLibraryPage(buildUrl) - val resolvedUrl = response.raw().networkResponse?.request?.url - ?: response.raw().request.url - val baseHostUrl = "${resolvedUrl.scheme}://${resolvedUrl.host}" - downloadProgress.postValue(context.getString(R.string.parsing_remote_library)) - val libraryXml = response.body() - val onlineBooks = onlineLibraryManager.parseOPDSStreamAndGetBooks(libraryXml, baseHostUrl) - emit( - if (onlineBooks.isNullOrEmpty()) { - emptyList() - } else { - onlineBooks - } + val urlHost = response.getResolvedBaseUrl() + updateDownloadProgressIfNeeded( + request, + R.string.parsing_remote_library ) + val libraryXml = response.body() + val onlineBooks = + onlineLibraryManager.parseOPDSStreamAndGetBooks(libraryXml, urlHost).orEmpty() + emit(OnlineLibraryResult(request, onlineBooks)) } .retry(5) .catch { e -> e.printStackTrace() - emit(emptyList()) + emit(OnlineLibraryResult(request, emptyList())) } + private fun updateDownloadProgressIfNeeded(request: OnlineLibraryRequest, messageResId: Int) { + if (!request.isLoadMoreItem) { + downloadProgress.postValue(context.getString(messageResId)) + } + } + + private fun Response.getResolvedBaseUrl(): String { + val url = raw().networkResponse?.request?.url ?: raw().request.url + return "${url.scheme}://${url.host}" + } + private fun updateNetworkStates() = connectivityBroadcastReceiver.networkStates .onEach { state -> networkStates.postValue(state) } .launchIn(viewModelScope) @@ -482,7 +588,7 @@ class ZimManageViewModel @Inject constructor( private fun updateLibraryItems( localBooksFromLibkiwix: Flow>, downloads: Flow>, - library: MutableSharedFlow>, + library: MutableStateFlow>, languages: Flow>, dispatcher: CoroutineDispatcher = Dispatchers.IO ) = viewModelScope.launch(dispatcher) { @@ -527,7 +633,7 @@ class ZimManageViewModel @Inject constructor( } private fun updateLanguagesInDao( - library: MutableSharedFlow>, + library: MutableStateFlow>, languages: Flow>, dispatcher: CoroutineDispatcher = Dispatchers.IO ) =