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.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

View File

@ -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
}

View File

@ -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()

View File

@ -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))

View File

@ -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,

View File

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

View File

@ -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<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 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"
}
}