Improved the caching mechanism for the language list.

* Improved the language list UI according to new approach.
* Fixed: codeFactor and lint issues.
This commit is contained in:
MohitMaliFtechiz 2025-07-07 16:10:53 +05:30
parent 9d3db72aaa
commit 28523ca277
7 changed files with 76 additions and 20 deletions

View File

@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text import androidx.compose.material3.Text

View File

@ -125,20 +125,30 @@ class LanguageViewModel @Inject constructor(
private fun observeLanguages() = viewModelScope.launch { private fun observeLanguages() = viewModelScope.launch {
state.value = Loading 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 return@launch
} }
try {
val languages = fetchLanguages() if (isOnline) {
if (languages?.isNotEmpty() == true) { runCatching {
actions.emit(UpdateLanguages(languages)) val fetched = fetchLanguages()
} else { if (!fetched.isNullOrEmpty()) {
Log.w("LanguageViewModel", "Fetched empty language list.") sharedPreferenceUtil.saveLanguageList(fetched)
actions.emit(Error(context.getString(R.string.no_language_available))) LanguageSessionCache.hasFetched = true
} actions.emit(UpdateLanguages(fetched))
} catch (e: Exception) { return@launch
Log.e("LanguageViewModel", "Error fetching languages", e) }
}.onFailure { it.printStackTrace() }
}
if (!cachedLanguageList.isNullOrEmpty()) {
actions.emit(UpdateLanguages(cachedLanguageList))
} else {
actions.emit(Error(context.getString(R.string.no_language_available))) actions.emit(Error(context.getString(R.string.no_language_available)))
} }
} }
@ -222,3 +232,7 @@ class LanguageViewModel @Inject constructor(
.addNetworkInterceptor(UserAgentInterceptor(USER_AGENT)) .addNetworkInterceptor(UserAgentInterceptor(USER_AGENT))
.build() .build()
} }
object LanguageSessionCache {
var hasFetched: Boolean = false
}

View File

@ -219,7 +219,7 @@ private fun OnlineLibraryList(state: OnlineLibraryScreenState, lazyListState: La
val layoutInfo = lazyListState.layoutInfo val layoutInfo = lazyListState.layoutInfo
val totalItems = layoutInfo.totalItemsCount val totalItems = layoutInfo.totalItemsCount
val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: ZERO val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: ZERO
(totalItems > ZERO && lastVisibleItemIndex >= (totalItems - FIVE)) to totalItems (totalItems > ZERO && lastVisibleItemIndex >= totalItems.minus(FIVE)) to totalItems
}.value }.value
} }
.distinctUntilChanged() .distinctUntilChanged()

View File

@ -18,9 +18,9 @@
package org.kiwix.kiwixmobile.zimManager package org.kiwix.kiwixmobile.zimManager
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext 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.data.remote.KiwixService.Companion.OPDS_LIBRARY_ENDPOINT
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
@ -34,11 +34,11 @@ import java.io.StringReader
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
@Suppress("UnusedPrivateProperty")
class OnlineLibraryManager @Inject constructor( class OnlineLibraryManager @Inject constructor(
@Named(ONLINE_BOOKS_LIBRARY) private val library: Library, @Named(ONLINE_BOOKS_LIBRARY) private val library: Library,
@Named(ONLINE_BOOKS_MANAGER) private val manager: Manager, @Named(ONLINE_BOOKS_MANAGER) private val manager: Manager,
) { ) {
var totalResult = 0 var totalResult = 0
suspend fun parseOPDSStreamAndGetBooks( suspend fun parseOPDSStreamAndGetBooks(
content: String?, content: String?,
@ -124,7 +124,10 @@ class OnlineLibraryManager @Inject constructor(
*/ */
fun getStartOffset(pageIndex: Int, pageSize: Int): Int = pageIndex * pageSize 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 factory = XmlPullParserFactory.newInstance()
val parser = factory.newPullParser() val parser = factory.newPullParser()
parser.setInput(StringReader(xml)) parser.setInput(StringReader(xml))

View File

@ -38,7 +38,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
@ -134,7 +133,7 @@ const val MAX_PROGRESS = 100
const val THREE = 3 const val THREE = 3
const val FOUR = 4 const val FOUR = 4
@Suppress("LongParameterList") @Suppress("LongParameterList", "LargeClass")
class ZimManageViewModel @Inject constructor( class ZimManageViewModel @Inject constructor(
private val downloadDao: DownloadRoomDao, private val downloadDao: DownloadRoomDao,
private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val libkiwixBookOnDisk: LibkiwixBookOnDisk,

View File

@ -45,4 +45,3 @@ class LanguageEntry {
@Namespace(prefix = "thr", reference = "http://purl.org/syndication/thread/1.0") @Namespace(prefix = "thr", reference = "http://purl.org/syndication/thread/1.0")
var count: Int = ZERO var count: Int = ZERO
} }

View File

@ -30,10 +30,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.json.JSONArray
import org.json.JSONObject
import org.kiwix.kiwixmobile.core.DarkModeConfig import org.kiwix.kiwixmobile.core.DarkModeConfig
import org.kiwix.kiwixmobile.core.DarkModeConfig.Mode.Companion.from import org.kiwix.kiwixmobile.core.DarkModeConfig.Mode.Companion.from
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.zim_manager.Language
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -205,6 +208,40 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
fun putPrefExternalLinkPopup(externalLinkPopup: Boolean) = fun putPrefExternalLinkPopup(externalLinkPopup: Boolean) =
sharedPreferences.edit { putBoolean(PREF_EXTERNAL_LINK_POPUP, externalLinkPopup) } sharedPreferences.edit { putBoolean(PREF_EXTERNAL_LINK_POPUP, externalLinkPopup) }
fun saveLanguageList(languages: List<Language>) {
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<Language>? =
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 showIntro(): Boolean = sharedPreferences.getBoolean(PREF_SHOW_INTRO, true)
fun setIntroShown() = sharedPreferences.edit { putBoolean(PREF_SHOW_INTRO, false) } 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 = const val PREF_LAST_DONATION_POPUP_SHOWN_IN_MILLISECONDS =
"pref_last_donation_shown_in_milliseconds" "pref_last_donation_shown_in_milliseconds"
private const val SELECTED_ONLINE_CONTENT_LANGUAGE = "selectedOnlineContentLanguage" 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"
} }
} }