mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 10:46:53 -04:00
Refactored the LanguageViewModel to fetch the language list from an online source.
This commit is contained in:
parent
8b02f96c1c
commit
014ef2bcef
@ -29,6 +29,7 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.LocalLayoutDirection
|
||||
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 org.kiwix.kiwixmobile.core.R
|
||||
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.KiwixSearchView
|
||||
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.viewmodel.Action
|
||||
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
|
||||
import org.kiwix.kiwixmobile.language.viewmodel.State
|
||||
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)
|
||||
@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
|
||||
fun LoadingScreen() {
|
||||
Box(
|
||||
|
@ -25,5 +25,6 @@ sealed class Action {
|
||||
data class UpdateLanguages(val languages: List<Language>) : Action()
|
||||
data class Filter(val filter: String) : Action()
|
||||
data class Select(val language: LanguageItem) : Action()
|
||||
data class Error(val errorMessage: String) : Action()
|
||||
object SaveAll : Action()
|
||||
}
|
||||
|
@ -18,19 +18,40 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.language.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
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.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.viewmodel.Action.Error
|
||||
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
|
||||
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
|
||||
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.Loading
|
||||
import org.kiwix.kiwixmobile.language.viewmodel.State.Saving
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import javax.inject.Inject
|
||||
|
||||
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() {
|
||||
val state = MutableStateFlow<State>(Loading)
|
||||
val actions = MutableSharedFlow<Action>(extraBufferCapacity = Int.MAX_VALUE)
|
||||
@ -49,6 +74,7 @@ class LanguageViewModel @Inject constructor(
|
||||
private val coroutineJobs = mutableListOf<Job>()
|
||||
|
||||
init {
|
||||
context.registerReceiver(connectivityBroadcastReceiver)
|
||||
coroutineJobs.apply {
|
||||
add(observeActions())
|
||||
add(observeLanguages())
|
||||
@ -62,17 +88,64 @@ class LanguageViewModel @Inject constructor(
|
||||
.onEach { newState -> state.value = newState }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
private fun observeLanguages() =
|
||||
languageRoomDao.languages()
|
||||
.filter { it.isNotEmpty() }
|
||||
.onEach { languages -> actions.tryEmit(UpdateLanguages(languages)) }
|
||||
.launchIn(viewModelScope)
|
||||
private suspend fun fetchLanguages(): List<Language>? =
|
||||
runCatching {
|
||||
kiwixService =
|
||||
KiwixService.ServiceCreator.newHackListService(getOkHttpClient(), KIWIX_LANGUAGE_URL)
|
||||
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() {
|
||||
coroutineJobs.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
coroutineJobs.clear()
|
||||
context.unregisterReceiver(connectivityBroadcastReceiver)
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
@ -81,6 +154,8 @@ class LanguageViewModel @Inject constructor(
|
||||
currentState: State
|
||||
): State {
|
||||
return when (action) {
|
||||
is Error -> State.Error(action.errorMessage)
|
||||
|
||||
is UpdateLanguages ->
|
||||
when (currentState) {
|
||||
Loading -> Content(action.languages)
|
||||
@ -111,8 +186,8 @@ class LanguageViewModel @Inject constructor(
|
||||
private fun saveAll(currentState: Content): State {
|
||||
effects.tryEmit(
|
||||
SaveLanguagesAndFinish(
|
||||
currentState.items,
|
||||
languageRoomDao,
|
||||
currentState.items.first(),
|
||||
sharedPreferenceUtil,
|
||||
viewModelScope
|
||||
)
|
||||
)
|
||||
@ -128,4 +203,18 @@ class LanguageViewModel @Inject constructor(
|
||||
filter: String,
|
||||
currentState: Content
|
||||
) = 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()
|
||||
}
|
||||
|
@ -19,25 +19,21 @@ package org.kiwix.kiwixmobile.language.viewmodel
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
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
|
||||
|
||||
@Suppress("InjectDispatcher")
|
||||
data class SaveLanguagesAndFinish(
|
||||
private val languages: List<Language>,
|
||||
private val languageRoomDao: LanguageRoomDao,
|
||||
private val languages: Language,
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
private val lifecycleScope: CoroutineScope
|
||||
) : SideEffect<Unit> {
|
||||
override fun invokeWith(activity: AppCompatActivity) {
|
||||
lifecycleScope.launch {
|
||||
runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
languageRoomDao.insert(languages)
|
||||
}
|
||||
sharedPreferenceUtil.selectedOnlineContentLanguage = languages.languageCode
|
||||
activity.onBackPressedDispatcher.onBackPressed()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
|
@ -24,6 +24,7 @@ import org.kiwix.kiwixmobile.language.composables.LanguageListItem.HeaderItem
|
||||
import org.kiwix.kiwixmobile.language.composables.LanguageListItem.LanguageItem
|
||||
|
||||
sealed class State {
|
||||
data class Error(val errorMessage: String) : State()
|
||||
object Loading : State()
|
||||
object Saving : State()
|
||||
data class Content(
|
||||
|
@ -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.isWifi
|
||||
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.data.DataSource
|
||||
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
|
||||
@ -139,7 +138,6 @@ const val FOUR = 4
|
||||
class ZimManageViewModel @Inject constructor(
|
||||
private val downloadDao: DownloadRoomDao,
|
||||
private val libkiwixBookOnDisk: LibkiwixBookOnDisk,
|
||||
private val languageRoomDao: LanguageRoomDao,
|
||||
private val storageObserver: StorageObserver,
|
||||
private var kiwixService: KiwixService,
|
||||
val context: Application,
|
||||
@ -164,9 +162,9 @@ class ZimManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
data class OnlineLibraryRequest(
|
||||
val query: String?,
|
||||
val category: String?,
|
||||
val lang: String?,
|
||||
val query: String? = null,
|
||||
val category: String? = null,
|
||||
val lang: String? = null,
|
||||
val isLoadMoreItem: Boolean,
|
||||
val page: Int
|
||||
)
|
||||
@ -327,16 +325,17 @@ class ZimManageViewModel @Inject constructor(
|
||||
private fun observeCoroutineFlows(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
|
||||
val downloads = downloadDao.downloads()
|
||||
val booksFromDao = books()
|
||||
val languages = languageRoomDao.languages()
|
||||
val selectedLanguage = sharedPreferenceUtil.onlineContentLanguage
|
||||
coroutineJobs.apply {
|
||||
add(scanBooksFromStorage(dispatcher))
|
||||
add(updateBookItems())
|
||||
add(fileSelectActions())
|
||||
add(updateLibraryItems(booksFromDao, downloads, networkLibrary, languages))
|
||||
add(updateLanguagesInDao(networkLibrary, languages))
|
||||
add(updateLibraryItems(booksFromDao, downloads, networkLibrary, selectedLanguage))
|
||||
// add(updateLanguagesInDao(networkLibrary, selectedLanguage))
|
||||
add(updateNetworkStates())
|
||||
add(requestsAndConnectivityChangesToLibraryRequests(networkLibrary))
|
||||
add(onlineLibraryRequest())
|
||||
add(observeLanguageChanges())
|
||||
}
|
||||
}
|
||||
|
||||
@ -350,6 +349,16 @@ class ZimManageViewModel @Inject constructor(
|
||||
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) {
|
||||
onlineLibraryRequest.update { current ->
|
||||
current.copy(
|
||||
@ -589,7 +598,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
localBooksFromLibkiwix: Flow<List<Book>>,
|
||||
downloads: Flow<List<DownloadModel>>,
|
||||
library: MutableStateFlow<List<LibkiwixBook>>,
|
||||
languages: Flow<List<Language>>,
|
||||
languages: StateFlow<String>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = viewModelScope.launch(dispatcher) {
|
||||
val requestFilteringFlow = merge(
|
||||
@ -632,23 +641,25 @@ class ZimManageViewModel @Inject constructor(
|
||||
.collect { _libraryItems.emit(it) }
|
||||
}
|
||||
|
||||
private fun updateLanguagesInDao(
|
||||
library: MutableStateFlow<List<LibkiwixBook>>,
|
||||
languages: Flow<List<Language>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) =
|
||||
combine(
|
||||
library,
|
||||
languages
|
||||
) { books, existingLanguages ->
|
||||
combineToLanguageList(books, existingLanguages)
|
||||
}.map { it.sortedBy(Language::language) }
|
||||
.filter { it.isNotEmpty() }
|
||||
.distinctUntilChanged()
|
||||
.catch { it.printStackTrace() }
|
||||
.onEach { languageRoomDao.insert(it) }
|
||||
.flowOn(dispatcher)
|
||||
.launchIn(viewModelScope)
|
||||
// private fun updateLanguagesInDao(
|
||||
// library: MutableStateFlow<List<LibkiwixBook>>,
|
||||
// languages: StateFlow<String>,
|
||||
// dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
// ) =
|
||||
// combine(
|
||||
// library,
|
||||
// languages
|
||||
// ) { books, existingLanguages ->
|
||||
// combineToLanguageList(books, existingLanguages)
|
||||
// }.map { it.sortedBy(Language::language) }
|
||||
// .filter { it.isNotEmpty() }
|
||||
// .distinctUntilChanged()
|
||||
// .catch { it.printStackTrace() }
|
||||
// .onEach {
|
||||
// // languageRoomDao.insert(it)
|
||||
// }
|
||||
// .flowOn(dispatcher)
|
||||
// .launchIn(viewModelScope)
|
||||
|
||||
private suspend fun combineToLanguageList(
|
||||
booksFromNetwork: List<LibkiwixBook>,
|
||||
|
@ -28,7 +28,6 @@ import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
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.language.composables.LanguageListItem
|
||||
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
|
||||
|
@ -27,7 +27,6 @@ import io.mockk.verify
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.kiwix.kiwixmobile.core.dao.LanguageRoomDao
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
||||
|
||||
class SaveLanguagesAndFinishTest {
|
||||
|
@ -54,7 +54,6 @@ import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.StorageObserver
|
||||
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.data.DataSource
|
||||
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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.HistoryItem
|
||||
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.libkiwix.Book
|
||||
|
||||
@ -34,7 +33,6 @@ interface DataSource {
|
||||
fun getLanguageCategorizedBooks(): Flow<List<BooksOnDiskListItem>>
|
||||
suspend fun saveBook(book: Book)
|
||||
suspend fun saveBooks(book: List<Book>)
|
||||
suspend fun saveLanguages(languages: List<Language>)
|
||||
suspend fun saveHistory(history: HistoryItem)
|
||||
suspend fun deleteHistory(historyList: List<HistoryListItem>)
|
||||
suspend fun clearHistory()
|
||||
|
@ -28,17 +28,14 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
|
||||
import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao
|
||||
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.RecentSearchRoomDao
|
||||
import org.kiwix.kiwixmobile.core.dao.WebViewHistoryRoomDao
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.BundleRoomConverter
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
|
||||
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.RecentSearchRoomEntity
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.StringToLocalRoomConverter
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.WebViewHistoryEntity
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.ZimSourceRoomConverter
|
||||
|
||||
@ -49,17 +46,15 @@ import org.kiwix.kiwixmobile.core.dao.entities.ZimSourceRoomConverter
|
||||
HistoryRoomEntity::class,
|
||||
NotesRoomEntity::class,
|
||||
DownloadRoomEntity::class,
|
||||
WebViewHistoryEntity::class,
|
||||
LanguageRoomEntity::class
|
||||
WebViewHistoryEntity::class
|
||||
],
|
||||
version = 9,
|
||||
version = 8,
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(
|
||||
HistoryRoomDaoCoverts::class,
|
||||
ZimSourceRoomConverter::class,
|
||||
BundleRoomConverter::class,
|
||||
StringToLocalRoomConverter::class
|
||||
BundleRoomConverter::class
|
||||
)
|
||||
abstract class KiwixRoomDatabase : RoomDatabase() {
|
||||
abstract fun recentSearchRoomDao(): RecentSearchRoomDao
|
||||
@ -67,7 +62,6 @@ abstract class KiwixRoomDatabase : RoomDatabase() {
|
||||
abstract fun notesRoomDao(): NotesRoomDao
|
||||
abstract fun downloadRoomDao(): DownloadRoomDao
|
||||
abstract fun webViewHistoryRoomDao(): WebViewHistoryRoomDao
|
||||
abstract fun languageRoomDao(): LanguageRoomDao
|
||||
|
||||
companion object {
|
||||
private var db: KiwixRoomDatabase? = null
|
||||
@ -84,8 +78,7 @@ abstract class KiwixRoomDatabase : RoomDatabase() {
|
||||
MIGRATION_4_5,
|
||||
MIGRATION_5_6,
|
||||
MIGRATION_6_7,
|
||||
MIGRATION_7_8,
|
||||
MIGRATION_8_9
|
||||
MIGRATION_7_8
|
||||
)
|
||||
.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() {
|
||||
db = null
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
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.LibkiwixBookmarks
|
||||
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.notes.adapter.NoteListItem
|
||||
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.BookOnDisk
|
||||
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 webViewHistoryRoomDao: WebViewHistoryRoomDao,
|
||||
private val notesRoomDao: NotesRoomDao,
|
||||
private val languageRoomDao: LanguageRoomDao,
|
||||
private val recentSearchRoomDao: RecentSearchRoomDao,
|
||||
private val zimReaderContainer: ZimReaderContainer
|
||||
) : DataSource {
|
||||
@ -99,12 +96,6 @@ class Repository @Inject internal constructor(
|
||||
libkiwixBookOnDisk.insert(listOf(book))
|
||||
}
|
||||
|
||||
@Suppress("InjectDispatcher")
|
||||
override suspend fun saveLanguages(languages: List<Language>) =
|
||||
withContext(Dispatchers.IO) {
|
||||
languageRoomDao.insert(languages)
|
||||
}
|
||||
|
||||
@Suppress("InjectDispatcher")
|
||||
override suspend fun saveHistory(history: HistoryItem) = withContext(Dispatchers.IO) {
|
||||
historyRoomDao.saveHistory(history)
|
||||
|
@ -39,6 +39,9 @@ interface KiwixService {
|
||||
@Url url: String
|
||||
): MetaLinkNetworkEntity?
|
||||
|
||||
@GET("catalog/v2/languages")
|
||||
suspend fun getLanguages(): LanguageFeed
|
||||
|
||||
/******** Helper class that sets up new services */
|
||||
object ServiceCreator {
|
||||
@Suppress("DEPRECATION")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import io.objectbox.BoxStore
|
||||
import io.objectbox.kotlin.boxFor
|
||||
import org.kiwix.kiwixmobile.core.CoreApp
|
||||
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.RecentSearchEntity
|
||||
import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase
|
||||
@ -50,9 +49,6 @@ class ObjectBoxToRoomMigrator {
|
||||
if (!sharedPreferenceUtil.prefIsNotesMigrated) {
|
||||
migrateNotes(boxStore.boxFor())
|
||||
}
|
||||
if (!sharedPreferenceUtil.prefLanguageListMigrated) {
|
||||
migrateLanguages(boxStore.boxFor())
|
||||
}
|
||||
// TODO we will migrate here for other entities
|
||||
}
|
||||
|
||||
@ -91,12 +87,4 @@ class ObjectBoxToRoomMigrator {
|
||||
}
|
||||
sharedPreferenceUtil.putPrefNotesMigrated(true)
|
||||
}
|
||||
|
||||
suspend fun migrateLanguages(box: Box<LanguageEntity>) {
|
||||
kiwixRoomDatabase.languageRoomDao()
|
||||
.insert(
|
||||
box.all.map { it.toLanguageModel() }
|
||||
)
|
||||
sharedPreferenceUtil.putPrefLanguageListMigrated(true)
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ import org.kiwix.kiwixmobile.core.StorageObserver
|
||||
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
|
||||
import org.kiwix.kiwixmobile.core.dao.HistoryDao
|
||||
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.LibkiwixBookmarks
|
||||
import org.kiwix.kiwixmobile.core.dao.NewBookDao
|
||||
@ -108,7 +107,6 @@ interface CoreComponent {
|
||||
fun historyRoomDao(): HistoryRoomDao
|
||||
fun webViewHistoryRoomDao(): WebViewHistoryRoomDao
|
||||
fun noteRoomDao(): NotesRoomDao
|
||||
fun languageRoomDao(): LanguageRoomDao
|
||||
fun objectBoxToRoomMigrator(): ObjectBoxToRoomMigrator
|
||||
fun context(): Context
|
||||
fun downloader(): Downloader
|
||||
|
@ -100,8 +100,4 @@ open class DatabaseModule {
|
||||
db.downloadRoomDao().also {
|
||||
it.libkiwixBookOnDisk = libkiwixBookOnDisk
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLanguageRoomDao(db: KiwixRoomDatabase) = db.languageRoomDao()
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ const val READ_TIMEOUT = 300L
|
||||
const val CALL_TIMEOUT = 300L
|
||||
const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}"
|
||||
const val KIWIX_OPDS_LIBRARY_URL = "https://opds.library.kiwix.org/"
|
||||
const val KIWIX_LANGUAGE_URL = "https://library.kiwix.org/"
|
||||
|
||||
@Module
|
||||
class NetworkModule {
|
||||
|
@ -60,6 +60,9 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
|
||||
val prefWifiOnly: Boolean
|
||||
get() = sharedPreferences.getBoolean(PREF_WIFI_ONLY, true)
|
||||
|
||||
private val _onlineContentLanguage = MutableStateFlow("")
|
||||
val onlineContentLanguage = _onlineContentLanguage.asStateFlow()
|
||||
|
||||
val prefIsFirstRun: Boolean
|
||||
get() = sharedPreferences.getBoolean(PREF_IS_FIRST_RUN, true)
|
||||
|
||||
@ -117,9 +120,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
|
||||
val prefIsBookOnDiskMigrated: Boolean
|
||||
get() = sharedPreferences.getBoolean(PREF_BOOK_ON_DISK_MIGRATED, false)
|
||||
|
||||
val prefLanguageListMigrated: Boolean
|
||||
get() = sharedPreferences.getBoolean(PREF_LANGUAGE_LIST_MIGRATED, false)
|
||||
|
||||
val prefStorage: String
|
||||
get() {
|
||||
val storage = sharedPreferences.getString(PREF_STORAGE, null)
|
||||
@ -172,9 +172,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
|
||||
fun putPrefBookOnDiskMigrated(isMigrated: Boolean) =
|
||||
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) =
|
||||
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 =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
path
|
||||
@ -358,10 +367,10 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
|
||||
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_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"
|
||||
private const val PREF_LATER_CLICKED_MILLIS = "pref_later_clicked_millis"
|
||||
const val PREF_LAST_DONATION_POPUP_SHOWN_IN_MILLISECONDS =
|
||||
"pref_last_donation_shown_in_milliseconds"
|
||||
private const val SELECTED_ONLINE_CONTENT_LANGUAGE = "selectedOnlineContentLanguage"
|
||||
}
|
||||
}
|
||||
|
@ -51,8 +51,9 @@ data class Language constructor(
|
||||
constructor(
|
||||
languageCode: String,
|
||||
active: Boolean,
|
||||
occurrencesOfLanguage: Int
|
||||
) : this(languageCode.convertToLocal(), active, occurrencesOfLanguage)
|
||||
occurrencesOfLanguage: Int,
|
||||
id: Long = 0
|
||||
) : this(languageCode.convertToLocal(), active, occurrencesOfLanguage, id)
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
(other as Language).language == language && other.active == active
|
||||
|
@ -210,6 +210,7 @@
|
||||
<string name="table_of_contents">Table of contents</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="no_language_available" tools:keep="@string/no_language_available">No languages available</string>
|
||||
<string name="expand">Expand</string>
|
||||
<string name="history">History</string>
|
||||
<string name="history_from_current_book">View History From All Books</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user