From 28523ca2777493768cad35d5aa14915da4a1d609 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Mon, 7 Jul 2025 16:10:53 +0530 Subject: [PATCH] Improved the caching mechanism for the language list. * Improved the language list UI according to new approach. * Fixed: codeFactor and lint issues. --- .../language/composables/LanguageItemRow.kt | 1 - .../language/viewmodel/LanguageViewModel.kt | 38 +++++++++++------ .../library/online/OnlineLibraryScreen.kt | 2 +- .../zimManager/OnlineLibraryManager.kt | 9 ++-- .../zimManager/ZimManageViewModel.kt | 3 +- .../core/data/remote/LanguageFeed.kt | 1 - .../core/utils/SharedPreferenceUtil.kt | 42 +++++++++++++++++++ 7 files changed, 76 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt index 6eaa51ccb..6e5c70506 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/composables/LanguageItemRow.kt @@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Checkbox import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Text diff --git a/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModel.kt index 62c2b000d..751cfa30b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/language/viewmodel/LanguageViewModel.kt @@ -125,20 +125,30 @@ class LanguageViewModel @Inject constructor( private fun observeLanguages() = viewModelScope.launch { state.value = Loading - if (connectivityBroadcastReceiver.networkStates.value == NetworkState.NOT_CONNECTED) { - actions.emit(Error(context.getString(R.string.no_network_connection))) + + val cachedLanguageList = sharedPreferenceUtil.getCachedLanguageList() + val isOnline = connectivityBroadcastReceiver.networkStates.value != NetworkState.NOT_CONNECTED + + if (LanguageSessionCache.hasFetched && !cachedLanguageList.isNullOrEmpty()) { + actions.emit(UpdateLanguages(cachedLanguageList)) return@launch } - try { - val languages = fetchLanguages() - if (languages?.isNotEmpty() == true) { - actions.emit(UpdateLanguages(languages)) - } else { - Log.w("LanguageViewModel", "Fetched empty language list.") - actions.emit(Error(context.getString(R.string.no_language_available))) - } - } catch (e: Exception) { - Log.e("LanguageViewModel", "Error fetching languages", e) + + if (isOnline) { + runCatching { + val fetched = fetchLanguages() + if (!fetched.isNullOrEmpty()) { + sharedPreferenceUtil.saveLanguageList(fetched) + LanguageSessionCache.hasFetched = true + actions.emit(UpdateLanguages(fetched)) + return@launch + } + }.onFailure { it.printStackTrace() } + } + + if (!cachedLanguageList.isNullOrEmpty()) { + actions.emit(UpdateLanguages(cachedLanguageList)) + } else { actions.emit(Error(context.getString(R.string.no_language_available))) } } @@ -222,3 +232,7 @@ class LanguageViewModel @Inject constructor( .addNetworkInterceptor(UserAgentInterceptor(USER_AGENT)) .build() } + +object LanguageSessionCache { + var hasFetched: Boolean = false +} 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 5a4937cc0..3343cff4f 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 @@ -219,7 +219,7 @@ private fun OnlineLibraryList(state: OnlineLibraryScreenState, lazyListState: La val layoutInfo = lazyListState.layoutInfo val totalItems = layoutInfo.totalItemsCount val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: ZERO - (totalItems > ZERO && lastVisibleItemIndex >= (totalItems - FIVE)) to totalItems + (totalItems > ZERO && lastVisibleItemIndex >= totalItems.minus(FIVE)) to totalItems }.value } .distinctUntilChanged() 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 6e6d79916..bb09cdec1 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/OnlineLibraryManager.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/OnlineLibraryManager.kt @@ -18,9 +18,9 @@ package org.kiwix.kiwixmobile.zimManager +import kotlinx.coroutines.CoroutineDispatcher 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 @@ -34,11 +34,11 @@ import java.io.StringReader import javax.inject.Inject import javax.inject.Named +@Suppress("UnusedPrivateProperty") class OnlineLibraryManager @Inject constructor( @Named(ONLINE_BOOKS_LIBRARY) private val library: Library, @Named(ONLINE_BOOKS_MANAGER) private val manager: Manager, ) { - var totalResult = 0 suspend fun parseOPDSStreamAndGetBooks( content: String?, @@ -124,7 +124,10 @@ class OnlineLibraryManager @Inject constructor( */ fun getStartOffset(pageIndex: Int, pageSize: Int): Int = pageIndex * pageSize - private suspend fun extractTotalResults(xml: String): Int = withContext(Dispatchers.IO) { + private suspend fun extractTotalResults( + xml: String, + dispatcher: CoroutineDispatcher = Dispatchers.IO + ): Int = withContext(dispatcher) { val factory = XmlPullParserFactory.newInstance() val parser = factory.newPullParser() parser.setInput(StringReader(xml)) 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 878bfc5ad..2b9e232d1 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt @@ -38,7 +38,6 @@ import kotlinx.coroutines.flow.asStateFlow 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 @@ -134,7 +133,7 @@ const val MAX_PROGRESS = 100 const val THREE = 3 const val FOUR = 4 -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") class ZimManageViewModel @Inject constructor( private val downloadDao: DownloadRoomDao, private val libkiwixBookOnDisk: LibkiwixBookOnDisk, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/remote/LanguageFeed.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/remote/LanguageFeed.kt index a9cd88cea..a421b7890 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/data/remote/LanguageFeed.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/data/remote/LanguageFeed.kt @@ -45,4 +45,3 @@ class LanguageEntry { @Namespace(prefix = "thr", reference = "http://purl.org/syndication/thread/1.0") var count: Int = ZERO } - diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt index 3fa98c041..d5d84ae7a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt @@ -30,10 +30,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.runBlocking +import org.json.JSONArray +import org.json.JSONObject import org.kiwix.kiwixmobile.core.DarkModeConfig import org.kiwix.kiwixmobile.core.DarkModeConfig.Mode.Companion.from import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.extensions.isFileExist +import org.kiwix.kiwixmobile.core.zim_manager.Language import java.io.File import java.util.Locale import javax.inject.Inject @@ -205,6 +208,40 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { fun putPrefExternalLinkPopup(externalLinkPopup: Boolean) = sharedPreferences.edit { putBoolean(PREF_EXTERNAL_LINK_POPUP, externalLinkPopup) } + fun saveLanguageList(languages: List) { + runCatching { + val jsonArray = JSONArray() + languages.forEach { lang -> + val obj = JSONObject().apply { + put(KEY_LANGUAGE_CODE, lang.languageCode) + put(KEY_OCCURRENCES_OF_LANGUAGE, lang.occurencesOfLanguage) + put(KEY_LANGUAGE_ACTIVE, lang.active) + put(KEY_LANGUAGE_ID, lang.id) + } + jsonArray.put(obj) + } + sharedPreferences.edit { + putString(CACHED_LANGUAGE_CODES, jsonArray.toString()) + } + }.onFailure { it.printStackTrace() } + } + + fun getCachedLanguageList(): List? = + runCatching { + val jsonString = + sharedPreferences.getString(CACHED_LANGUAGE_CODES, null) ?: return@runCatching null + val jsonArray = JSONArray(jsonString) + List(jsonArray.length()) { i -> + val obj = jsonArray.getJSONObject(i) + Language( + languageCode = obj.getString(KEY_LANGUAGE_CODE), + occurrencesOfLanguage = obj.getInt(KEY_OCCURRENCES_OF_LANGUAGE), + active = selectedOnlineContentLanguage == obj.getString(KEY_LANGUAGE_CODE), + id = obj.getLong(KEY_LANGUAGE_ID) + ) + } + }.onFailure { it.printStackTrace() }.getOrNull() + fun showIntro(): Boolean = sharedPreferences.getBoolean(PREF_SHOW_INTRO, true) fun setIntroShown() = sharedPreferences.edit { putBoolean(PREF_SHOW_INTRO, false) } @@ -372,5 +409,10 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { const val PREF_LAST_DONATION_POPUP_SHOWN_IN_MILLISECONDS = "pref_last_donation_shown_in_milliseconds" private const val SELECTED_ONLINE_CONTENT_LANGUAGE = "selectedOnlineContentLanguage" + private const val CACHED_LANGUAGE_CODES = "cachedLanguageCodes" + private const val KEY_LANGUAGE_CODE = "languageCode" + private const val KEY_OCCURRENCES_OF_LANGUAGE = "occurrencesOfLanguage" + private const val KEY_LANGUAGE_ACTIVE = "languageActive" + private const val KEY_LANGUAGE_ID = "languageId" } }