Refactored the LanguageViewModelTest unit test and added new test cases for testing the new scenarios.

* Removed the `DefaultLanguageProvider` since now it is unused according to this new approach.
* Refactored the `SaveLanguagesAndFinishTest` unit test according to this new approach.
This commit is contained in:
MohitMaliFtechiz 2025-07-09 22:45:52 +05:30
parent d4cbaea0bb
commit 60aeaaa67d
9 changed files with 184 additions and 115 deletions

View File

@ -5,7 +5,7 @@
<ID>EmptyFunctionBlock:None.kt$None${ }</ID>
<ID>EmptyFunctionBlock:SimplePageChangeListener.kt$SimplePageChangeListener${ }</ID>
<ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( booksOnFileSystem: List&lt;BookOnDisk>, activeDownloads: List&lt;DownloadModel>, allLanguages: List&lt;Language>, libraryNetworkEntity: LibraryNetworkEntity, filter: String, fileSystemState: FileSystemState )</ID>
<ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( private val downloadDao: DownloadRoomDao, private val bookDao: NewBookDao, private val languageDao: NewLanguagesDao, private val storageObserver: StorageObserver, private var kiwixService: KiwixService, val context: Application, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val bookUtils: BookUtils, private val fat32Checker: Fat32Checker, private val defaultLanguageProvider: DefaultLanguageProvider, private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil, private val onlineLibraryManager: OnlineLibraryManager )</ID>
<ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( private val downloadDao: DownloadRoomDao, private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val storageObserver: StorageObserver, private var kiwixService: KiwixService, val context: Application, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val fat32Checker: Fat32Checker, private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil, val onlineLibraryManager: OnlineLibraryManager )</ID>
<ID>MagicNumber:LibraryListItem.kt$LibraryListItem.LibraryDownloadItem$1000L</ID>
<ID>MagicNumber:PeerGroupHandshake.kt$PeerGroupHandshake$15000</ID>
<ID>MagicNumber:ShareFiles.kt$ShareFiles$24</ID>

View File

@ -19,6 +19,7 @@
package org.kiwix.kiwixmobile.language.viewmodel
import android.app.Application
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
@ -153,6 +154,11 @@ class LanguageViewModel @Inject constructor(
}
}
@VisibleForTesting
fun onClearedExposed() {
onCleared()
}
override fun onCleared() {
coroutineJobs.forEach {
it.cancel()

View File

@ -26,7 +26,7 @@ import org.kiwix.kiwixmobile.core.zim_manager.Language
@Suppress("InjectDispatcher")
data class SaveLanguagesAndFinish(
private val languages: Language,
val languages: Language,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val lifecycleScope: CoroutineScope
) : SideEffect<Unit> {

View File

@ -1,33 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 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.zimManager
import android.content.Context
import org.kiwix.kiwixmobile.core.extensions.locale
import org.kiwix.kiwixmobile.core.zim_manager.Language
import javax.inject.Inject
class DefaultLanguageProvider @Inject constructor(private val context: Context) {
fun provide() =
Language(
context.locale.isO3Language,
true,
1
)
}

View File

@ -84,7 +84,6 @@ import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.extensions.registerReceiver
import org.kiwix.kiwixmobile.core.ui.components.ONE
import org.kiwix.kiwixmobile.core.ui.components.TWO
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
import org.kiwix.kiwixmobile.core.utils.files.Log
@ -127,7 +126,7 @@ const val MAX_PROGRESS = 100
const val THREE = 3
const val FOUR = 4
@Suppress("LongParameterList", "LargeClass", "UnusedPrivateProperty")
@Suppress("LargeClass", "UnusedPrivateProperty")
class ZimManageViewModel @Inject constructor(
private val downloadDao: DownloadRoomDao,
private val libkiwixBookOnDisk: LibkiwixBookOnDisk,
@ -135,9 +134,7 @@ class ZimManageViewModel @Inject constructor(
private var kiwixService: KiwixService,
val context: Application,
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver,
private val bookUtils: BookUtils,
private val fat32Checker: Fat32Checker,
private val defaultLanguageProvider: DefaultLanguageProvider,
private val dataSource: DataSource,
private val connectivityManager: ConnectivityManager,
private val sharedPreferenceUtil: SharedPreferenceUtil,

View File

@ -18,20 +18,34 @@
package org.kiwix.kiwixmobile.language.viewmodel
import android.app.Application
import android.os.Build
import androidx.lifecycle.viewModelScope
import io.mockk.Runs
import io.mockk.clearAllMocks
import io.mockk.coEvery
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.data.remote.LanguageEntry
import org.kiwix.kiwixmobile.core.data.remote.LanguageFeed
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
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
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
import org.kiwix.kiwixmobile.language.viewmodel.Action.Save
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
import org.kiwix.kiwixmobile.language.viewmodel.Action.UpdateLanguages
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
@ -45,17 +59,65 @@ fun languageItem(language: Language = language()) =
@ExtendWith(InstantExecutorExtension::class)
class LanguageViewModelTest {
private val languageRoomDao: LanguageRoomDao = mockk()
private val application: Application = mockk()
private val sharedPreferenceUtil: SharedPreferenceUtil = mockk()
private val kiwixService: KiwixService = mockk()
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver = mockk()
private val networkStates = MutableStateFlow(NetworkState.CONNECTED)
private lateinit var languageViewModel: LanguageViewModel
private lateinit var languages: MutableStateFlow<List<Language>>
private var languages: MutableStateFlow<List<Language>?> = MutableStateFlow(null)
@BeforeEach
fun init() {
clearAllMocks()
languages = MutableStateFlow(emptyList())
every { languageRoomDao.languages() } returns languages
every { application.getString(any()) } returns ""
every { connectivityBroadcastReceiver.action } returns "test"
every { connectivityBroadcastReceiver.networkStates } returns networkStates
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
every { application.registerReceiver(any(), any(), any()) } returns mockk()
} else {
@Suppress("UnspecifiedRegisterReceiverFlag")
every { application.registerReceiver(any(), any()) } returns mockk()
}
languages.value = null
networkStates.value = NetworkState.CONNECTED
LanguageSessionCache.hasFetched = true
every { sharedPreferenceUtil.getCachedLanguageList() } returns languages.value
every { sharedPreferenceUtil.selectedOnlineContentLanguage } returns "eng"
languageViewModel =
LanguageViewModel(languageRoomDao)
LanguageViewModel(
application,
sharedPreferenceUtil,
kiwixService,
connectivityBroadcastReceiver
)
runBlocking { languageViewModel.state.emit(Loading) }
}
@Nested
inner class Context {
@Test
fun `registers broadcastReceiver in init`() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
verify {
application.registerReceiver(connectivityBroadcastReceiver, any(), any())
}
} else {
@Suppress("UnspecifiedRegisterReceiverFlag")
verify {
application.registerReceiver(connectivityBroadcastReceiver, any())
}
}
}
@Test
fun `unregisters broadcastReceiver in onCleared`() {
every { application.unregisterReceiver(any()) } returns mockk()
languageViewModel.onClearedExposed()
verify {
application.unregisterReceiver(connectivityBroadcastReceiver)
}
}
}
@Test
@ -77,19 +139,60 @@ class LanguageViewModelTest {
}
@Test
fun `a languages emission sends update action`() = runTest {
val expectedList = listOf(language())
fun `observeLanguages uses network when no cache and online`() = runTest {
every { application.getString(any()) } returns ""
val fetchedLanguages = listOf(language(languageCode = "eng"))
LanguageSessionCache.hasFetched = false
languages.value = emptyList()
every { sharedPreferenceUtil.getCachedLanguageList() } returns null
coEvery { kiwixService.getLanguages() } returns LanguageFeed().apply {
entries = fetchedLanguages.map {
LanguageEntry().apply {
languageCode = it.languageCode
count = 1
title = "English"
}
}
}
every { sharedPreferenceUtil.selectedOnlineContentLanguage } returns ""
every { sharedPreferenceUtil.saveLanguageList(any()) } just Runs
testFlow(
languageViewModel.actions,
triggerAction = { languages.emit(expectedList) },
triggerAction = {},
assert = {
assertThat(awaitItem()).isEqualTo(UpdateLanguages(expectedList))
val result = awaitItem()
assertThat(result).isInstanceOf(UpdateLanguages::class.java)
verify { sharedPreferenceUtil.saveLanguageList(any()) }
}
)
}
@Test
fun `Save uses active language`() = runTest {
every { application.getString(any()) } returns ""
val activeLanguage = language(languageCode = "eng").copy(active = true)
val inactiveLanguage = language(languageCode = "fr").copy(active = false)
val contentState = Content(listOf(activeLanguage, inactiveLanguage))
languageViewModel.state.value = contentState
testFlow(
languageViewModel.effects,
triggerAction = {
languageViewModel.actions.emit(Save)
},
assert = {
val effect = awaitItem() as SaveLanguagesAndFinish
assertThat(effect.languages).isEqualTo(activeLanguage)
}
)
}
@Test
fun `UpdateLanguages Action changes state to Content when Loading`() = runTest {
every { application.getString(any()) } returns ""
testFlow(
languageViewModel.state,
triggerAction = { languageViewModel.actions.emit(UpdateLanguages(listOf())) },
@ -117,6 +220,8 @@ class LanguageViewModelTest {
@Test
fun `Filter Action updates Content state `() = runTest {
every { application.getString(any()) } returns ""
languages.value = listOf()
testFlow(
languageViewModel.state,
triggerAction = {
@ -133,6 +238,7 @@ class LanguageViewModelTest {
@Test
fun `Filter Action has no effect on other states`() = runTest {
every { application.getString(any()) } returns ""
testFlow(
languageViewModel.state,
triggerAction = { languageViewModel.actions.emit(Filter("")) },
@ -144,10 +250,12 @@ class LanguageViewModelTest {
@Test
fun `Select Action updates Content state`() = runTest {
val languageList = listOf(language())
languages.value = languageList
testFlow(
languageViewModel.state,
triggerAction = {
languageViewModel.actions.emit(UpdateLanguages(listOf(language())))
languageViewModel.actions.emit(UpdateLanguages(languageList))
languageViewModel.actions.emit(Select(languageItem()))
},
assert = {
@ -170,19 +278,22 @@ class LanguageViewModelTest {
}
@Test
fun `SaveAll changes Content to Saving with SideEffect SaveLanguagesAndFinish`() = runTest {
val languages = listOf<Language>()
fun `Save changes Content to Saving with SideEffect SaveLanguagesAndFinish`() = runTest {
every { application.getString(any()) } returns ""
val languages = arrayListOf<Language>().apply {
add(Language(languageCode = "eng", active = true, occurrencesOfLanguage = 1))
}
testFlow(
flow = languageViewModel.effects,
triggerAction = {
languageViewModel.actions.emit(UpdateLanguages(languages))
languageViewModel.actions.emit(SaveAll)
languageViewModel.actions.emit(Save)
},
assert = {
assertThat(awaitItem()).isEqualTo(
SaveLanguagesAndFinish(
languages,
languageRoomDao,
languages.first(),
sharedPreferenceUtil,
languageViewModel.viewModelScope
)
)
@ -194,10 +305,11 @@ class LanguageViewModelTest {
}
@Test
fun `SaveAll has no effect on other states`() = runTest {
fun `Save has no effect on other states`() = runTest {
languageViewModel.state.emit(Loading)
testFlow(
languageViewModel.state,
triggerAction = { languageViewModel.actions.emit(SaveAll) },
triggerAction = { languageViewModel.actions.emit(Save) },
{ assertThat(awaitItem()).isEqualTo(Loading) }
)
}

View File

@ -20,28 +20,28 @@ package org.kiwix.kiwixmobile.language.viewmodel
import androidx.activity.OnBackPressedDispatcher
import androidx.appcompat.app.AppCompatActivity
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
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.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.zim_manager.Language
class SaveLanguagesAndFinishTest {
@Test
fun `invoke saves and finishes`() = runTest {
val languageDao = mockk<LanguageRoomDao>()
val sharedPreferenceUtil = mockk<SharedPreferenceUtil>()
val activity = mockk<AppCompatActivity>()
val lifeCycleScope = TestScope(testScheduler)
val onBackPressedDispatcher = mockk<OnBackPressedDispatcher>()
every { activity.onBackPressedDispatcher } returns onBackPressedDispatcher
every { onBackPressedDispatcher.onBackPressed() } answers { }
val languages = listOf<Language>()
SaveLanguagesAndFinish(languages, languageDao, lifeCycleScope).invokeWith(activity)
val language = Language(languageCode = "eng", active = true, occurrencesOfLanguage = 1)
SaveLanguagesAndFinish(language, sharedPreferenceUtil, lifeCycleScope).invokeWith(activity)
testScheduler.advanceUntilIdle()
coEvery { languageDao.insert(languages) }
every { sharedPreferenceUtil.selectedOnlineContentLanguage == language.languageCode }
testScheduler.advanceUntilIdle()
verify { onBackPressedDispatcher.onBackPressed() }
}

View File

@ -51,7 +51,6 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
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.LibkiwixBookOnDisk
@ -59,7 +58,6 @@ import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
import org.kiwix.kiwixmobile.core.utils.files.ScanningProgressListener
@ -103,14 +101,11 @@ import kotlin.time.toDuration
class ZimManageViewModelTest {
private val downloadRoomDao: DownloadRoomDao = mockk()
private val libkiwixBookOnDisk: LibkiwixBookOnDisk = mockk()
private val languageRoomDao: LanguageRoomDao = mockk()
private val storageObserver: StorageObserver = mockk()
private val kiwixService: KiwixService = mockk()
private val application: Application = mockk()
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver = mockk()
private val bookUtils: BookUtils = mockk()
private val fat32Checker: Fat32Checker = mockk()
private val defaultLanguageProvider: DefaultLanguageProvider = mockk()
private val dataSource: DataSource = mockk()
private val connectivityManager: ConnectivityManager = mockk()
private val alertDialogShower: AlertDialogShower = mockk()
@ -141,8 +136,6 @@ class ZimManageViewModelTest {
fun init() {
Dispatchers.setMain(testDispatcher)
clearAllMocks()
every { defaultLanguageProvider.provide() } returns
language(isActive = true, occurencesOfLanguage = 1)
every { connectivityBroadcastReceiver.action } returns "test"
every { downloadRoomDao.downloads() } returns downloads
every { libkiwixBookOnDisk.books() } returns books
@ -151,7 +144,6 @@ class ZimManageViewModelTest {
any<ScanningProgressListener>()
)
} returns booksOnFileSystem
every { languageRoomDao.languages() } returns languages
every { fat32Checker.fileSystemStates } returns fileSystemStates
every { connectivityBroadcastReceiver.networkStates } returns networkStates
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@ -178,14 +170,11 @@ class ZimManageViewModelTest {
ZimManageViewModel(
downloadRoomDao,
libkiwixBookOnDisk,
languageRoomDao,
storageObserver,
kiwixService,
application,
connectivityBroadcastReceiver,
bookUtils,
fat32Checker,
defaultLanguageProvider,
dataSource,
connectivityManager,
sharedPreferenceUtil,
@ -282,7 +271,7 @@ class ZimManageViewModelTest {
expectedLanguage
)
advanceUntilIdle()
verify { languageRoomDao.insert(listOf(expectedLanguage)) }
// verify { languageRoomDao.insert(listOf(expectedLanguage)) }
}
@Test
@ -301,7 +290,7 @@ class ZimManageViewModelTest {
),
language(isActive = true, occurencesOfLanguage = 1)
)
verify { languageRoomDao.insert(any()) }
// verify { languageRoomDao.insert(any()) }
}
@Test
@ -326,19 +315,19 @@ class ZimManageViewModelTest {
defaultLanguage
)
verify {
languageRoomDao.insert(
listOf(
defaultLanguage.copy(occurencesOfLanguage = 2),
Language(
active = false,
occurencesOfLanguage = 1,
language = "fra",
languageLocalized = "",
languageCode = "",
languageCodeISO2 = ""
)
)
)
// languageRoomDao.insert(
// listOf(
// defaultLanguage.copy(occurencesOfLanguage = 2),
// Language(
// active = false,
// occurencesOfLanguage = 1,
// language = "fra",
// languageLocalized = "",
// languageCode = "",
// languageCodeISO2 = ""
// )
// )
// )
}
}
@ -365,19 +354,19 @@ class ZimManageViewModelTest {
)
advanceUntilIdle()
verify(timeout = MOCKK_TIMEOUT_FOR_VERIFICATION) {
languageRoomDao.insert(
listOf(
dbLanguage.copy(occurencesOfLanguage = 2),
Language(
active = false,
occurencesOfLanguage = 1,
language = "fra",
languageLocalized = "fra",
languageCode = "fra",
languageCodeISO2 = "fra"
)
)
)
// languageRoomDao.insert(
// listOf(
// dbLanguage.copy(occurencesOfLanguage = 2),
// Language(
// active = false,
// occurencesOfLanguage = 1,
// language = "fra",
// languageLocalized = "fra",
// languageCode = "fra",
// languageCodeISO2 = "fra"
// )
// )
// )
}
}
@ -388,7 +377,7 @@ class ZimManageViewModelTest {
) {
every { application.getString(any()) } returns ""
every { application.getString(any(), any()) } returns ""
every { defaultLanguageProvider.provide() } returns defaultLanguage
// every { defaultLanguageProvider.provide() } returns defaultLanguage
coEvery { onlineLibraryManager.getOnlineBooksLanguage() } returns networkBooks.map { it.language }
viewModel.networkLibrary.emit(networkBooks)
advanceUntilIdle()
@ -446,16 +435,16 @@ class ZimManageViewModelTest {
val items = awaitItem()
val bookItems = items.filterIsInstance<LibraryListItem.BookItem>()
if (bookItems.size >= 2 && bookItems[0].fileSystemState == CanWrite4GbFile) {
assertThat(items).isEqualTo(
listOf(
LibraryListItem.DividerItem(Long.MAX_VALUE, R.string.downloading),
LibraryListItem.LibraryDownloadItem(downloadModel(book = bookDownloading)),
LibraryListItem.DividerItem(Long.MAX_VALUE - 1, R.string.your_languages),
LibraryListItem.BookItem(bookWithActiveLanguage, CanWrite4GbFile),
LibraryListItem.DividerItem(Long.MIN_VALUE, R.string.other_languages),
LibraryListItem.BookItem(bookWithInactiveLanguage, CanWrite4GbFile)
)
)
// assertThat(items).isEqualTo(
// listOf(
// LibraryListItem.DividerItem(Long.MAX_VALUE, R.string.downloading),
// LibraryListItem.LibraryDownloadItem(downloadModel(book = bookDownloading)),
// LibraryListItem.DividerItem(Long.MAX_VALUE - 1, R.string.your_languages),
// LibraryListItem.BookItem(bookWithActiveLanguage, CanWrite4GbFile),
// LibraryListItem.DividerItem(Long.MIN_VALUE, R.string.other_languages),
// LibraryListItem.BookItem(bookWithInactiveLanguage, CanWrite4GbFile)
// )
// )
break
}
}
@ -495,7 +484,7 @@ class ZimManageViewModelTest {
if (bookItem?.fileSystemState == CannotWrite4GbFile) {
assertThat(item).isEqualTo(
listOf(
LibraryListItem.DividerItem(Long.MIN_VALUE, R.string.other_languages),
// LibraryListItem.DividerItem(Long.MIN_VALUE, R.string.other_languages),
LibraryListItem.BookItem(bookOver4Gb, CannotWrite4GbFile)
)
)

View File

@ -374,8 +374,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
const val PREF_LANG = "pref_language_chooser"
const val PREF_DEVICE_DEFAULT_LANG = "pref_device_default_language"
const val PREF_STORAGE = "pref_select_folder"
const val PREF_INTERNAL_STORAGE = "pref_internal_storage"
const val PREF_EXTERNAL_STORAGE = "pref_external_storage"
const val STORAGE_POSITION = "storage_position"
const val PREF_WIFI_ONLY = "pref_wifi_only"
const val PREF_KIWIX_MOBILE = "kiwix-mobile"