mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 18:56:44 -04:00
Refactored the ZimManageViewModel to fetch the library reactively from the network and laid the groundwork for applying filters in network requests.
This commit is contained in:
parent
d9a10b30a6
commit
8b02f96c1c
@ -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() {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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<LibkiwixBook>? =
|
||||
runCatching {
|
||||
content?.let { totalResult = extractTotalResults(it) }
|
||||
if (content == null) return null
|
||||
totalResult = extractTotalResults(content)
|
||||
val onlineBooksList = arrayListOf<LibkiwixBook>()
|
||||
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
|
||||
|
@ -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<LibkiwixBook>
|
||||
)
|
||||
|
||||
private var isUnitTestCase: Boolean = false
|
||||
val sideEffects: MutableSharedFlow<SideEffect<*>> = MutableSharedFlow()
|
||||
private val _libraryItems = MutableStateFlow<List<LibraryListItem>>(emptyList())
|
||||
@ -168,20 +183,36 @@ class ZimManageViewModel @Inject constructor(
|
||||
val fileSelectListStates: MutableLiveData<FileSelectListState> = MutableLiveData()
|
||||
val deviceListScanningProgress = MutableLiveData<Int>()
|
||||
val libraryListIsRefreshing = MutableLiveData<Boolean>()
|
||||
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<Boolean>()
|
||||
val networkStates = MutableLiveData<NetworkState>()
|
||||
val networkLibrary = MutableSharedFlow<List<LibkiwixBook>>(replay = 0)
|
||||
val networkLibrary = MutableStateFlow<List<LibkiwixBook>>(emptyList())
|
||||
val requestFileSystemCheck = MutableSharedFlow<Unit>(replay = 0)
|
||||
val fileSelectActions = MutableSharedFlow<FileSelectActions>()
|
||||
private val requestDownloadLibrary = MutableSharedFlow<Unit>(
|
||||
private val requestDownloadLibrary = MutableSharedFlow<OnlineLibraryRequest>(
|
||||
replay = 0,
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
|
||||
@Volatile
|
||||
var isOnlineLibraryDownloading = false
|
||||
val onlineLibraryRequest: MutableStateFlow<OnlineLibraryRequest> =
|
||||
MutableStateFlow<OnlineLibraryRequest>(
|
||||
OnlineLibraryRequest(
|
||||
query = null,
|
||||
category = null,
|
||||
lang = null,
|
||||
isLoadMoreItem = false,
|
||||
page = 0
|
||||
)
|
||||
)
|
||||
val requestFiltering = MutableStateFlow("")
|
||||
val onlineBooksSearchedQuery = MutableLiveData<String>()
|
||||
private val coroutineJobs: MutableList<Job> = 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<List<LibkiwixBook>>,
|
||||
library: MutableStateFlow<List<LibkiwixBook>>,
|
||||
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<KiwixService> {
|
||||
private fun shouldProceedWithDownload(onlineLibraryRequest: OnlineLibraryRequest): Flow<KiwixService> {
|
||||
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<List<LibkiwixBook>> = 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<OnlineLibraryResult> = 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<String>.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<List<Book>>,
|
||||
downloads: Flow<List<DownloadModel>>,
|
||||
library: MutableSharedFlow<List<LibkiwixBook>>,
|
||||
library: MutableStateFlow<List<LibkiwixBook>>,
|
||||
languages: Flow<List<Language>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = viewModelScope.launch(dispatcher) {
|
||||
@ -527,7 +633,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun updateLanguagesInDao(
|
||||
library: MutableSharedFlow<List<LibkiwixBook>>,
|
||||
library: MutableStateFlow<List<LibkiwixBook>>,
|
||||
languages: Flow<List<Language>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) =
|
||||
|
Loading…
x
Reference in New Issue
Block a user