Refactored the LanguageViewModel to fetch the language list from an online source.

This commit is contained in:
MohitMaliFtechiz 2025-07-05 00:01:10 +05:30
parent 8b02f96c1c
commit 014ef2bcef
23 changed files with 242 additions and 219 deletions

View File

@ -29,6 +29,7 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -37,6 +38,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.CollectSideEffectWithActivity import org.kiwix.kiwixmobile.core.extensions.CollectSideEffectWithActivity
@ -45,11 +49,13 @@ import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixSearchView import org.kiwix.kiwixmobile.core.ui.components.KiwixSearchView
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
import org.kiwix.kiwixmobile.language.composables.LanguageList import org.kiwix.kiwixmobile.language.composables.LanguageList
import org.kiwix.kiwixmobile.language.viewmodel.Action import org.kiwix.kiwixmobile.language.viewmodel.Action
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
import org.kiwix.kiwixmobile.language.viewmodel.State import org.kiwix.kiwixmobile.language.viewmodel.State
import org.kiwix.kiwixmobile.language.viewmodel.State.Content import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.kiwixmobile.nav.destination.library.online.NO_CONTENT_VIEW_TEXT_TESTING_TAG
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("ComposableLambdaParameterNaming") @SuppressLint("ComposableLambdaParameterNaming")
@ -115,11 +121,29 @@ fun LanguageScreen(
} }
) )
} }
is State.Error -> ShowErrorMessage((state as State.Error).errorMessage)
} }
} }
} }
} }
@Composable
private fun ShowErrorMessage(errorMessage: String) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = errorMessage,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(horizontal = FOUR_DP)
.semantics { testTag = NO_CONTENT_VIEW_TEXT_TESTING_TAG }
)
}
}
@Composable @Composable
fun LoadingScreen() { fun LoadingScreen() {
Box( Box(

View File

@ -25,5 +25,6 @@ sealed class Action {
data class UpdateLanguages(val languages: List<Language>) : Action() data class UpdateLanguages(val languages: List<Language>) : Action()
data class Filter(val filter: String) : Action() data class Filter(val filter: String) : Action()
data class Select(val language: LanguageItem) : Action() data class Select(val language: LanguageItem) : Action()
data class Error(val errorMessage: String) : Action()
object SaveAll : Action() object SaveAll : Action()
} }

View File

@ -18,19 +18,40 @@
package org.kiwix.kiwixmobile.language.viewmodel package org.kiwix.kiwixmobile.language.viewmodel
import android.app.Application
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC
import okhttp3.logging.HttpLoggingInterceptor.Level.NONE
import org.kiwix.kiwixmobile.core.BuildConfig
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.data.remote.UserAgentInterceptor
import org.kiwix.kiwixmobile.core.di.modules.CALL_TIMEOUT
import org.kiwix.kiwixmobile.core.di.modules.CONNECTION_TIMEOUT
import org.kiwix.kiwixmobile.core.di.modules.KIWIX_LANGUAGE_URL
import org.kiwix.kiwixmobile.core.di.modules.READ_TIMEOUT
import org.kiwix.kiwixmobile.core.di.modules.USER_AGENT
import org.kiwix.kiwixmobile.core.extensions.registerReceiver
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX
import org.kiwix.kiwixmobile.core.utils.files.Log
import org.kiwix.kiwixmobile.core.zim_manager.ConnectivityBroadcastReceiver
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.core.zim_manager.NetworkState
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.viewmodel.Action.Error
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
@ -38,10 +59,14 @@ import org.kiwix.kiwixmobile.language.viewmodel.Action.UpdateLanguages
import org.kiwix.kiwixmobile.language.viewmodel.State.Content import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.kiwixmobile.language.viewmodel.State.Loading import org.kiwix.kiwixmobile.language.viewmodel.State.Loading
import org.kiwix.kiwixmobile.language.viewmodel.State.Saving import org.kiwix.kiwixmobile.language.viewmodel.State.Saving
import java.util.concurrent.TimeUnit.SECONDS
import javax.inject.Inject import javax.inject.Inject
class LanguageViewModel @Inject constructor( class LanguageViewModel @Inject constructor(
private val languageRoomDao: LanguageRoomDao private val context: Application,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private var kiwixService: KiwixService,
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver
) : ViewModel() { ) : ViewModel() {
val state = MutableStateFlow<State>(Loading) val state = MutableStateFlow<State>(Loading)
val actions = MutableSharedFlow<Action>(extraBufferCapacity = Int.MAX_VALUE) val actions = MutableSharedFlow<Action>(extraBufferCapacity = Int.MAX_VALUE)
@ -49,6 +74,7 @@ class LanguageViewModel @Inject constructor(
private val coroutineJobs = mutableListOf<Job>() private val coroutineJobs = mutableListOf<Job>()
init { init {
context.registerReceiver(connectivityBroadcastReceiver)
coroutineJobs.apply { coroutineJobs.apply {
add(observeActions()) add(observeActions())
add(observeLanguages()) add(observeLanguages())
@ -62,17 +88,64 @@ class LanguageViewModel @Inject constructor(
.onEach { newState -> state.value = newState } .onEach { newState -> state.value = newState }
.launchIn(viewModelScope) .launchIn(viewModelScope)
private fun observeLanguages() = private suspend fun fetchLanguages(): List<Language>? =
languageRoomDao.languages() runCatching {
.filter { it.isNotEmpty() } kiwixService =
.onEach { languages -> actions.tryEmit(UpdateLanguages(languages)) } KiwixService.ServiceCreator.newHackListService(getOkHttpClient(), KIWIX_LANGUAGE_URL)
.launchIn(viewModelScope) val feed = kiwixService.getLanguages()
buildList {
// Add default item to show all language.
add(
Language(
languageCode = "",
active = sharedPreferenceUtil.selectedOnlineContentLanguage.isEmpty(),
occurrencesOfLanguage = 0,
id = 0L
)
)
// Add the rest of the fetched languages
feed.entries.orEmpty().mapIndexedNotNull { index, languageEntry ->
runCatching {
Language(
languageCode = languageEntry.languageCode,
active = sharedPreferenceUtil.selectedOnlineContentLanguage == languageEntry.languageCode,
occurrencesOfLanguage = languageEntry.count,
id = (index + 1).toLong()
)
}.onFailure {
Log.w(TAG_KIWIX, "Unsupported locale code: ${languageEntry.languageCode}", it)
}.getOrNull()
}.forEach { add(it) }
}
}.onFailure { it.printStackTrace() }.getOrNull()
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)))
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)
actions.emit(Error(context.getString(R.string.no_language_available)))
}
}
override fun onCleared() { override fun onCleared() {
coroutineJobs.forEach { coroutineJobs.forEach {
it.cancel() it.cancel()
} }
coroutineJobs.clear() coroutineJobs.clear()
context.unregisterReceiver(connectivityBroadcastReceiver)
super.onCleared() super.onCleared()
} }
@ -81,6 +154,8 @@ class LanguageViewModel @Inject constructor(
currentState: State currentState: State
): State { ): State {
return when (action) { return when (action) {
is Error -> State.Error(action.errorMessage)
is UpdateLanguages -> is UpdateLanguages ->
when (currentState) { when (currentState) {
Loading -> Content(action.languages) Loading -> Content(action.languages)
@ -111,8 +186,8 @@ class LanguageViewModel @Inject constructor(
private fun saveAll(currentState: Content): State { private fun saveAll(currentState: Content): State {
effects.tryEmit( effects.tryEmit(
SaveLanguagesAndFinish( SaveLanguagesAndFinish(
currentState.items, currentState.items.first(),
languageRoomDao, sharedPreferenceUtil,
viewModelScope viewModelScope
) )
) )
@ -128,4 +203,18 @@ class LanguageViewModel @Inject constructor(
filter: String, filter: String,
currentState: Content currentState: Content
) = currentState.updateFilter(filter) ) = currentState.updateFilter(filter)
private fun getOkHttpClient() = OkHttpClient().newBuilder()
.followRedirects(true)
.followSslRedirects(true)
.connectTimeout(CONNECTION_TIMEOUT, SECONDS)
.readTimeout(READ_TIMEOUT, SECONDS)
.callTimeout(CALL_TIMEOUT, SECONDS)
.addNetworkInterceptor(
HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) BASIC else NONE
}
)
.addNetworkInterceptor(UserAgentInterceptor(USER_AGENT))
.build()
} }

View File

@ -19,25 +19,21 @@ package org.kiwix.kiwixmobile.language.viewmodel
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.zim_manager.Language import org.kiwix.kiwixmobile.core.zim_manager.Language
@Suppress("InjectDispatcher") @Suppress("InjectDispatcher")
data class SaveLanguagesAndFinish( data class SaveLanguagesAndFinish(
private val languages: List<Language>, private val languages: Language,
private val languageRoomDao: LanguageRoomDao, private val sharedPreferenceUtil: SharedPreferenceUtil,
private val lifecycleScope: CoroutineScope private val lifecycleScope: CoroutineScope
) : SideEffect<Unit> { ) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) { override fun invokeWith(activity: AppCompatActivity) {
lifecycleScope.launch { lifecycleScope.launch {
runCatching { runCatching {
withContext(Dispatchers.IO) { sharedPreferenceUtil.selectedOnlineContentLanguage = languages.languageCode
languageRoomDao.insert(languages)
}
activity.onBackPressedDispatcher.onBackPressed() activity.onBackPressedDispatcher.onBackPressed()
}.onFailure { }.onFailure {
it.printStackTrace() it.printStackTrace()

View File

@ -24,6 +24,7 @@ import org.kiwix.kiwixmobile.language.composables.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
sealed class State { sealed class State {
data class Error(val errorMessage: String) : State()
object Loading : State() object Loading : State()
object Saving : State() object Saving : State()
data class Content( data class Content(

View File

@ -68,7 +68,6 @@ import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.isWifi import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.isWifi
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.data.remote.KiwixService
@ -139,7 +138,6 @@ const val FOUR = 4
class ZimManageViewModel @Inject constructor( class ZimManageViewModel @Inject constructor(
private val downloadDao: DownloadRoomDao, private val downloadDao: DownloadRoomDao,
private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val libkiwixBookOnDisk: LibkiwixBookOnDisk,
private val languageRoomDao: LanguageRoomDao,
private val storageObserver: StorageObserver, private val storageObserver: StorageObserver,
private var kiwixService: KiwixService, private var kiwixService: KiwixService,
val context: Application, val context: Application,
@ -164,9 +162,9 @@ class ZimManageViewModel @Inject constructor(
} }
data class OnlineLibraryRequest( data class OnlineLibraryRequest(
val query: String?, val query: String? = null,
val category: String?, val category: String? = null,
val lang: String?, val lang: String? = null,
val isLoadMoreItem: Boolean, val isLoadMoreItem: Boolean,
val page: Int val page: Int
) )
@ -327,16 +325,17 @@ class ZimManageViewModel @Inject constructor(
private fun observeCoroutineFlows(dispatcher: CoroutineDispatcher = Dispatchers.IO) { private fun observeCoroutineFlows(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
val downloads = downloadDao.downloads() val downloads = downloadDao.downloads()
val booksFromDao = books() val booksFromDao = books()
val languages = languageRoomDao.languages() val selectedLanguage = sharedPreferenceUtil.onlineContentLanguage
coroutineJobs.apply { coroutineJobs.apply {
add(scanBooksFromStorage(dispatcher)) add(scanBooksFromStorage(dispatcher))
add(updateBookItems()) add(updateBookItems())
add(fileSelectActions()) add(fileSelectActions())
add(updateLibraryItems(booksFromDao, downloads, networkLibrary, languages)) add(updateLibraryItems(booksFromDao, downloads, networkLibrary, selectedLanguage))
add(updateLanguagesInDao(networkLibrary, languages)) // add(updateLanguagesInDao(networkLibrary, selectedLanguage))
add(updateNetworkStates()) add(updateNetworkStates())
add(requestsAndConnectivityChangesToLibraryRequests(networkLibrary)) add(requestsAndConnectivityChangesToLibraryRequests(networkLibrary))
add(onlineLibraryRequest()) add(onlineLibraryRequest())
add(observeLanguageChanges())
} }
} }
@ -350,6 +349,16 @@ class ZimManageViewModel @Inject constructor(
super.onCleared() super.onCleared()
} }
private fun observeLanguageChanges(dispatcher: CoroutineDispatcher = Dispatchers.IO) =
sharedPreferenceUtil.onlineContentLanguage
.onEach {
updateOnlineLibraryFilters(
OnlineLibraryRequest(lang = it, page = ZERO, isLoadMoreItem = false)
)
}
.flowOn(dispatcher)
.launchIn(viewModelScope)
fun updateOnlineLibraryFilters(newRequest: OnlineLibraryRequest) { fun updateOnlineLibraryFilters(newRequest: OnlineLibraryRequest) {
onlineLibraryRequest.update { current -> onlineLibraryRequest.update { current ->
current.copy( current.copy(
@ -589,7 +598,7 @@ class ZimManageViewModel @Inject constructor(
localBooksFromLibkiwix: Flow<List<Book>>, localBooksFromLibkiwix: Flow<List<Book>>,
downloads: Flow<List<DownloadModel>>, downloads: Flow<List<DownloadModel>>,
library: MutableStateFlow<List<LibkiwixBook>>, library: MutableStateFlow<List<LibkiwixBook>>,
languages: Flow<List<Language>>, languages: StateFlow<String>,
dispatcher: CoroutineDispatcher = Dispatchers.IO dispatcher: CoroutineDispatcher = Dispatchers.IO
) = viewModelScope.launch(dispatcher) { ) = viewModelScope.launch(dispatcher) {
val requestFilteringFlow = merge( val requestFilteringFlow = merge(
@ -632,23 +641,25 @@ class ZimManageViewModel @Inject constructor(
.collect { _libraryItems.emit(it) } .collect { _libraryItems.emit(it) }
} }
private fun updateLanguagesInDao( // private fun updateLanguagesInDao(
library: MutableStateFlow<List<LibkiwixBook>>, // library: MutableStateFlow<List<LibkiwixBook>>,
languages: Flow<List<Language>>, // languages: StateFlow<String>,
dispatcher: CoroutineDispatcher = Dispatchers.IO // dispatcher: CoroutineDispatcher = Dispatchers.IO
) = // ) =
combine( // combine(
library, // library,
languages // languages
) { books, existingLanguages -> // ) { books, existingLanguages ->
combineToLanguageList(books, existingLanguages) // combineToLanguageList(books, existingLanguages)
}.map { it.sortedBy(Language::language) } // }.map { it.sortedBy(Language::language) }
.filter { it.isNotEmpty() } // .filter { it.isNotEmpty() }
.distinctUntilChanged() // .distinctUntilChanged()
.catch { it.printStackTrace() } // .catch { it.printStackTrace() }
.onEach { languageRoomDao.insert(it) } // .onEach {
.flowOn(dispatcher) // // languageRoomDao.insert(it)
.launchIn(viewModelScope) // }
// .flowOn(dispatcher)
// .launchIn(viewModelScope)
private suspend fun combineToLanguageList( private suspend fun combineToLanguageList(
booksFromNetwork: List<LibkiwixBook>, booksFromNetwork: List<LibkiwixBook>,

View File

@ -28,7 +28,6 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao
import org.kiwix.kiwixmobile.core.zim_manager.Language import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.language.composables.LanguageListItem import org.kiwix.kiwixmobile.language.composables.LanguageListItem
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter

View File

@ -27,7 +27,6 @@ import io.mockk.verify
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao
import org.kiwix.kiwixmobile.core.zim_manager.Language import org.kiwix.kiwixmobile.core.zim_manager.Language
class SaveLanguagesAndFinishTest { class SaveLanguagesAndFinishTest {

View File

@ -54,7 +54,6 @@ import org.junit.jupiter.api.extension.ExtendWith
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.data.remote.KiwixService

View File

@ -1,50 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.kiwix.kiwixmobile.core.dao.entities.LanguageRoomEntity
import org.kiwix.kiwixmobile.core.zim_manager.Language
@Dao
abstract class LanguageRoomDao {
@Query("SELECT * FROM LanguageRoomEntity")
abstract fun languageAsEntity(): Flow<List<LanguageRoomEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun saveLanguages(languageRoomEntityList: List<LanguageRoomEntity>)
@Query("DELETE FROM LanguageRoomEntity")
abstract fun deleteAllLanguages()
fun languages(): Flow<List<Language>> =
languageAsEntity().map { it.map(LanguageRoomEntity::toLanguageModel) }
@Transaction
open fun insert(languages: List<Language>) {
deleteAllLanguages()
saveLanguages(languages.map(::LanguageRoomEntity))
}
}

View File

@ -1,56 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.dao.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.zim_manager.Language
import java.util.Locale
@Entity
data class LanguageRoomEntity(
@PrimaryKey(autoGenerate = true) var id: Long = 0,
@TypeConverters(StringToLocalRoomConverter::class)
var locale: Locale = Locale.ENGLISH,
var active: Boolean = false,
var occurencesOfLanguage: Int = 0
) {
constructor(language: Language) : this(
0,
language.languageCode.convertToLocal(),
language.active,
language.occurencesOfLanguage
)
fun toLanguageModel() =
Language(locale, active, occurencesOfLanguage, id)
}
class StringToLocalRoomConverter {
@TypeConverter
fun convertToDatabaseValue(entityProperty: Locale?): String =
entityProperty?.isO3Language ?: Locale.ENGLISH.isO3Language
@TypeConverter
fun convertToEntityProperty(databaseValue: String?): Locale =
databaseValue?.convertToLocal() ?: Locale.ENGLISH
}

View File

@ -23,7 +23,6 @@ import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem
import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import org.kiwix.libkiwix.Book import org.kiwix.libkiwix.Book
@ -34,7 +33,6 @@ interface DataSource {
fun getLanguageCategorizedBooks(): Flow<List<BooksOnDiskListItem>> fun getLanguageCategorizedBooks(): Flow<List<BooksOnDiskListItem>>
suspend fun saveBook(book: Book) suspend fun saveBook(book: Book)
suspend fun saveBooks(book: List<Book>) suspend fun saveBooks(book: List<Book>)
suspend fun saveLanguages(languages: List<Language>)
suspend fun saveHistory(history: HistoryItem) suspend fun saveHistory(history: HistoryItem)
suspend fun deleteHistory(historyList: List<HistoryListItem>) suspend fun deleteHistory(historyList: List<HistoryListItem>)
suspend fun clearHistory() suspend fun clearHistory()

View File

@ -28,17 +28,14 @@ import androidx.sqlite.db.SupportSQLiteDatabase
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao
import org.kiwix.kiwixmobile.core.dao.HistoryRoomDaoCoverts import org.kiwix.kiwixmobile.core.dao.HistoryRoomDaoCoverts
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao
import org.kiwix.kiwixmobile.core.dao.NotesRoomDao import org.kiwix.kiwixmobile.core.dao.NotesRoomDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.dao.WebViewHistoryRoomDao import org.kiwix.kiwixmobile.core.dao.WebViewHistoryRoomDao
import org.kiwix.kiwixmobile.core.dao.entities.BundleRoomConverter import org.kiwix.kiwixmobile.core.dao.entities.BundleRoomConverter
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.dao.entities.HistoryRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.HistoryRoomEntity
import org.kiwix.kiwixmobile.core.dao.entities.LanguageRoomEntity
import org.kiwix.kiwixmobile.core.dao.entities.NotesRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.NotesRoomEntity
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity
import org.kiwix.kiwixmobile.core.dao.entities.StringToLocalRoomConverter
import org.kiwix.kiwixmobile.core.dao.entities.WebViewHistoryEntity import org.kiwix.kiwixmobile.core.dao.entities.WebViewHistoryEntity
import org.kiwix.kiwixmobile.core.dao.entities.ZimSourceRoomConverter import org.kiwix.kiwixmobile.core.dao.entities.ZimSourceRoomConverter
@ -49,17 +46,15 @@ import org.kiwix.kiwixmobile.core.dao.entities.ZimSourceRoomConverter
HistoryRoomEntity::class, HistoryRoomEntity::class,
NotesRoomEntity::class, NotesRoomEntity::class,
DownloadRoomEntity::class, DownloadRoomEntity::class,
WebViewHistoryEntity::class, WebViewHistoryEntity::class
LanguageRoomEntity::class
], ],
version = 9, version = 8,
exportSchema = false exportSchema = false
) )
@TypeConverters( @TypeConverters(
HistoryRoomDaoCoverts::class, HistoryRoomDaoCoverts::class,
ZimSourceRoomConverter::class, ZimSourceRoomConverter::class,
BundleRoomConverter::class, BundleRoomConverter::class
StringToLocalRoomConverter::class
) )
abstract class KiwixRoomDatabase : RoomDatabase() { abstract class KiwixRoomDatabase : RoomDatabase() {
abstract fun recentSearchRoomDao(): RecentSearchRoomDao abstract fun recentSearchRoomDao(): RecentSearchRoomDao
@ -67,7 +62,6 @@ abstract class KiwixRoomDatabase : RoomDatabase() {
abstract fun notesRoomDao(): NotesRoomDao abstract fun notesRoomDao(): NotesRoomDao
abstract fun downloadRoomDao(): DownloadRoomDao abstract fun downloadRoomDao(): DownloadRoomDao
abstract fun webViewHistoryRoomDao(): WebViewHistoryRoomDao abstract fun webViewHistoryRoomDao(): WebViewHistoryRoomDao
abstract fun languageRoomDao(): LanguageRoomDao
companion object { companion object {
private var db: KiwixRoomDatabase? = null private var db: KiwixRoomDatabase? = null
@ -84,8 +78,7 @@ abstract class KiwixRoomDatabase : RoomDatabase() {
MIGRATION_4_5, MIGRATION_4_5,
MIGRATION_5_6, MIGRATION_5_6,
MIGRATION_6_7, MIGRATION_6_7,
MIGRATION_7_8, MIGRATION_7_8
MIGRATION_8_9
) )
.build().also { db = it } .build().also { db = it }
} }
@ -312,23 +305,6 @@ abstract class KiwixRoomDatabase : RoomDatabase() {
} }
} }
@Suppress("MagicNumber")
private val MIGRATION_8_9 =
object : Migration(8, 9) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `LanguageRoomEntity` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`locale` TEXT NOT NULL DEFAULT 'eng',
`active` INTEGER NOT NULL DEFAULT 0,
`occurencesOfLanguage` INTEGER NOT NULL DEFAULT 0
)
""".trimIndent()
)
}
}
fun destroyInstance() { fun destroyInstance() {
db = null db = null
} }

View File

@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NotesRoomDao import org.kiwix.kiwixmobile.core.dao.NotesRoomDao
@ -37,7 +36,6 @@ import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem
import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.LanguageItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.LanguageItem
@ -56,7 +54,6 @@ class Repository @Inject internal constructor(
private val historyRoomDao: HistoryRoomDao, private val historyRoomDao: HistoryRoomDao,
private val webViewHistoryRoomDao: WebViewHistoryRoomDao, private val webViewHistoryRoomDao: WebViewHistoryRoomDao,
private val notesRoomDao: NotesRoomDao, private val notesRoomDao: NotesRoomDao,
private val languageRoomDao: LanguageRoomDao,
private val recentSearchRoomDao: RecentSearchRoomDao, private val recentSearchRoomDao: RecentSearchRoomDao,
private val zimReaderContainer: ZimReaderContainer private val zimReaderContainer: ZimReaderContainer
) : DataSource { ) : DataSource {
@ -99,12 +96,6 @@ class Repository @Inject internal constructor(
libkiwixBookOnDisk.insert(listOf(book)) libkiwixBookOnDisk.insert(listOf(book))
} }
@Suppress("InjectDispatcher")
override suspend fun saveLanguages(languages: List<Language>) =
withContext(Dispatchers.IO) {
languageRoomDao.insert(languages)
}
@Suppress("InjectDispatcher") @Suppress("InjectDispatcher")
override suspend fun saveHistory(history: HistoryItem) = withContext(Dispatchers.IO) { override suspend fun saveHistory(history: HistoryItem) = withContext(Dispatchers.IO) {
historyRoomDao.saveHistory(history) historyRoomDao.saveHistory(history)

View File

@ -39,6 +39,9 @@ interface KiwixService {
@Url url: String @Url url: String
): MetaLinkNetworkEntity? ): MetaLinkNetworkEntity?
@GET("catalog/v2/languages")
suspend fun getLanguages(): LanguageFeed
/******** Helper class that sets up new services */ /******** Helper class that sets up new services */
object ServiceCreator { object ServiceCreator {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")

View File

@ -0,0 +1,48 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.data.remote
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.simpleframework.xml.Element
import org.simpleframework.xml.ElementList
import org.simpleframework.xml.Namespace
import org.simpleframework.xml.Root
@Root(name = "feed", strict = false)
@Namespace(reference = "http://www.w3.org/2005/Atom")
class LanguageFeed {
@field:ElementList(name = "entry", inline = true, required = false)
var entries: List<LanguageEntry>? = null
}
@Root(name = "entry", strict = false)
@Namespace(reference = "http://www.w3.org/2005/Atom")
class LanguageEntry {
@field:Element(name = "title", required = false)
var title: String = ""
@field:Element(name = "language", required = false)
@Namespace(prefix = "dc", reference = "http://purl.org/dc/terms/")
var languageCode: String = ""
@field:Element(name = "count", required = false)
@Namespace(prefix = "thr", reference = "http://purl.org/syndication/thread/1.0")
var count: Int = ZERO
}

View File

@ -23,7 +23,6 @@ import io.objectbox.BoxStore
import io.objectbox.kotlin.boxFor import io.objectbox.kotlin.boxFor
import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.dao.entities.HistoryEntity import org.kiwix.kiwixmobile.core.dao.entities.HistoryEntity
import org.kiwix.kiwixmobile.core.dao.entities.LanguageEntity
import org.kiwix.kiwixmobile.core.dao.entities.NotesEntity import org.kiwix.kiwixmobile.core.dao.entities.NotesEntity
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity
import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase
@ -50,9 +49,6 @@ class ObjectBoxToRoomMigrator {
if (!sharedPreferenceUtil.prefIsNotesMigrated) { if (!sharedPreferenceUtil.prefIsNotesMigrated) {
migrateNotes(boxStore.boxFor()) migrateNotes(boxStore.boxFor())
} }
if (!sharedPreferenceUtil.prefLanguageListMigrated) {
migrateLanguages(boxStore.boxFor())
}
// TODO we will migrate here for other entities // TODO we will migrate here for other entities
} }
@ -91,12 +87,4 @@ class ObjectBoxToRoomMigrator {
} }
sharedPreferenceUtil.putPrefNotesMigrated(true) sharedPreferenceUtil.putPrefNotesMigrated(true)
} }
suspend fun migrateLanguages(box: Box<LanguageEntity>) {
kiwixRoomDatabase.languageRoomDao()
.insert(
box.all.map { it.toLanguageModel() }
)
sharedPreferenceUtil.putPrefLanguageListMigrated(true)
}
} }

View File

@ -30,7 +30,6 @@ import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.HistoryDao import org.kiwix.kiwixmobile.core.dao.HistoryDao
import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewBookDao
@ -108,7 +107,6 @@ interface CoreComponent {
fun historyRoomDao(): HistoryRoomDao fun historyRoomDao(): HistoryRoomDao
fun webViewHistoryRoomDao(): WebViewHistoryRoomDao fun webViewHistoryRoomDao(): WebViewHistoryRoomDao
fun noteRoomDao(): NotesRoomDao fun noteRoomDao(): NotesRoomDao
fun languageRoomDao(): LanguageRoomDao
fun objectBoxToRoomMigrator(): ObjectBoxToRoomMigrator fun objectBoxToRoomMigrator(): ObjectBoxToRoomMigrator
fun context(): Context fun context(): Context
fun downloader(): Downloader fun downloader(): Downloader

View File

@ -100,8 +100,4 @@ open class DatabaseModule {
db.downloadRoomDao().also { db.downloadRoomDao().also {
it.libkiwixBookOnDisk = libkiwixBookOnDisk it.libkiwixBookOnDisk = libkiwixBookOnDisk
} }
@Singleton
@Provides
fun provideLanguageRoomDao(db: KiwixRoomDatabase) = db.languageRoomDao()
} }

View File

@ -39,6 +39,7 @@ const val READ_TIMEOUT = 300L
const val CALL_TIMEOUT = 300L const val CALL_TIMEOUT = 300L
const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}" const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}"
const val KIWIX_OPDS_LIBRARY_URL = "https://opds.library.kiwix.org/" const val KIWIX_OPDS_LIBRARY_URL = "https://opds.library.kiwix.org/"
const val KIWIX_LANGUAGE_URL = "https://library.kiwix.org/"
@Module @Module
class NetworkModule { class NetworkModule {

View File

@ -60,6 +60,9 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
val prefWifiOnly: Boolean val prefWifiOnly: Boolean
get() = sharedPreferences.getBoolean(PREF_WIFI_ONLY, true) get() = sharedPreferences.getBoolean(PREF_WIFI_ONLY, true)
private val _onlineContentLanguage = MutableStateFlow("")
val onlineContentLanguage = _onlineContentLanguage.asStateFlow()
val prefIsFirstRun: Boolean val prefIsFirstRun: Boolean
get() = sharedPreferences.getBoolean(PREF_IS_FIRST_RUN, true) get() = sharedPreferences.getBoolean(PREF_IS_FIRST_RUN, true)
@ -117,9 +120,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
val prefIsBookOnDiskMigrated: Boolean val prefIsBookOnDiskMigrated: Boolean
get() = sharedPreferences.getBoolean(PREF_BOOK_ON_DISK_MIGRATED, false) get() = sharedPreferences.getBoolean(PREF_BOOK_ON_DISK_MIGRATED, false)
val prefLanguageListMigrated: Boolean
get() = sharedPreferences.getBoolean(PREF_LANGUAGE_LIST_MIGRATED, false)
val prefStorage: String val prefStorage: String
get() { get() {
val storage = sharedPreferences.getString(PREF_STORAGE, null) val storage = sharedPreferences.getString(PREF_STORAGE, null)
@ -172,9 +172,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
fun putPrefBookOnDiskMigrated(isMigrated: Boolean) = fun putPrefBookOnDiskMigrated(isMigrated: Boolean) =
sharedPreferences.edit { putBoolean(PREF_BOOK_ON_DISK_MIGRATED, isMigrated) } sharedPreferences.edit { putBoolean(PREF_BOOK_ON_DISK_MIGRATED, isMigrated) }
fun putPrefLanguageListMigrated(isMigrated: Boolean) =
sharedPreferences.edit { putBoolean(PREF_LANGUAGE_LIST_MIGRATED, isMigrated) }
fun putPrefLanguage(language: String) = fun putPrefLanguage(language: String) =
sharedPreferences.edit { putString(PREF_LANG, language) } sharedPreferences.edit { putString(PREF_LANG, language) }
@ -308,6 +305,18 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
} }
} }
var selectedOnlineContentLanguage: String
get() = sharedPreferences.getString(SELECTED_ONLINE_CONTENT_LANGUAGE, "").orEmpty()
set(selectedOnlineContentLanguage) {
sharedPreferences.edit {
putString(
SELECTED_ONLINE_CONTENT_LANGUAGE,
selectedOnlineContentLanguage
)
}
_onlineContentLanguage.tryEmit(selectedOnlineContentLanguage)
}
fun getPublicDirectoryPath(path: String): String = fun getPublicDirectoryPath(path: String): String =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
path path
@ -358,10 +367,10 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
const val PREF_NOTES_MIGRATED = "pref_notes_migrated" const val PREF_NOTES_MIGRATED = "pref_notes_migrated"
const val PREF_APP_DIRECTORY_TO_PUBLIC_MIGRATED = "pref_app_directory_to_public_migrated" const val PREF_APP_DIRECTORY_TO_PUBLIC_MIGRATED = "pref_app_directory_to_public_migrated"
const val PREF_BOOK_ON_DISK_MIGRATED = "pref_book_on_disk_migrated" const val PREF_BOOK_ON_DISK_MIGRATED = "pref_book_on_disk_migrated"
const val PREF_LANGUAGE_LIST_MIGRATED = "pref_language_list_migrated"
const val PREF_SHOW_COPY_MOVE_STORAGE_SELECTION_DIALOG = "pref_show_copy_move_storage_dialog" const val PREF_SHOW_COPY_MOVE_STORAGE_SELECTION_DIALOG = "pref_show_copy_move_storage_dialog"
private const val PREF_LATER_CLICKED_MILLIS = "pref_later_clicked_millis" private const val PREF_LATER_CLICKED_MILLIS = "pref_later_clicked_millis"
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"
} }
} }

View File

@ -51,8 +51,9 @@ data class Language constructor(
constructor( constructor(
languageCode: String, languageCode: String,
active: Boolean, active: Boolean,
occurrencesOfLanguage: Int occurrencesOfLanguage: Int,
) : this(languageCode.convertToLocal(), active, occurrencesOfLanguage) id: Long = 0
) : this(languageCode.convertToLocal(), active, occurrencesOfLanguage, id)
override fun equals(other: Any?): Boolean = override fun equals(other: Any?): Boolean =
(other as Language).language == language && other.active == active (other as Language).language == language && other.active == active

View File

@ -210,6 +210,7 @@
<string name="table_of_contents">Table of contents</string> <string name="table_of_contents">Table of contents</string>
<string name="select_languages" tools:keep="@string/select_languages">Select languages</string> <string name="select_languages" tools:keep="@string/select_languages">Select languages</string>
<string name="save_languages" tools:keep="@string/save_languages">Save languages</string> <string name="save_languages" tools:keep="@string/save_languages">Save languages</string>
<string name="no_language_available" tools:keep="@string/no_language_available">No languages available</string>
<string name="expand">Expand</string> <string name="expand">Expand</string>
<string name="history">History</string> <string name="history">History</string>
<string name="history_from_current_book">View History From All Books</string> <string name="history_from_current_book">View History From All Books</string>