Fixed: The Text Zoom slider did not correctly display the previously set position after restarting the application.

* Removed the BaseDelegateAdapter and its subclasses, as RecyclerView is no longer used in the codebase.
* Fixed: CustomApps was crashing when opening the settings screen.
* Fixed: StorageList was not appearing on the settings screen.
* Fixed: Dark Mode and Language dialogs to match the theme design.
* Created KiwixBasicDialogFrame, a reusable Compose UI component to standardize the design of alert dialogs.
* Removed unused code from the project.
This commit is contained in:
MohitMaliFtechiz 2025-05-30 17:55:02 +05:30
parent 164e38b113
commit ba476058fe
23 changed files with 149 additions and 960 deletions

View File

@ -24,7 +24,6 @@ import dagger.Module
import dagger.multibindings.IntoMap
import org.kiwix.kiwixmobile.core.di.ViewModelKey
import org.kiwix.kiwixmobile.core.di.modules.CoreViewModelModule
import org.kiwix.kiwixmobile.core.settings.viewmodel.SettingsViewModel
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel
@ -39,9 +38,4 @@ abstract class KiwixViewModelModule {
@IntoMap
@ViewModelKey(LanguageViewModel::class)
abstract fun bindLanguageViewModel(languageViewModel: LanguageViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(SettingsViewModel::class)
abstract fun bindSettingsViewModel(settingsViewModel: SettingsViewModel): ViewModel
}

View File

@ -22,9 +22,7 @@ import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.view.View
import androidx.lifecycle.lifecycleScope
import eu.mhutti1.utils.storage.StorageDevice
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.extensions.update
@ -55,16 +53,17 @@ class KiwixSettingsFragment : CoreSettingsFragment() {
return@setStorage
}
showHideProgressBarWhileFetchingStorageInfo(true)
lifecycleScope.launch {
storageDeviceList = (requireActivity() as KiwixMainActivity).getStorageDeviceList()
showHideProgressBarWhileFetchingStorageInfo(false)
setUpStoragePreference()
}
storageDeviceList = (requireActivity() as KiwixMainActivity).getStorageDeviceList()
showHideProgressBarWhileFetchingStorageInfo(false)
setUpStoragePreference()
}
}
private fun setUpStoragePreference() {
settingsScreenState.value.update { copy(storageDeviceList = storageDeviceList) }
settingsScreenState.value.value =
settingsScreenState.value.value.copy(storageDeviceList = emptyList())
settingsScreenState.value.value =
settingsScreenState.value.value.copy(storageDeviceList = storageDeviceList)
}
/**

View File

@ -1,41 +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 androidx.viewpager.widget.ViewPager.OnPageChangeListener
class SimplePageChangeListener constructor(
private val onPageSelectedAction: (Int) -> Unit,
private val onPageScrollStateChanged: ((Int) -> Unit)? = null
) : OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageSelected(position: Int) {
onPageSelectedAction.invoke(position)
}
override fun onPageScrollStateChanged(state: Int) {
onPageScrollStateChanged?.invoke(state)
}
}

View File

@ -1,42 +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.core.base.adapter
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
interface AbsDelegateAdapter<
INSTANCE : SUPERTYPE,
SUPERTYPE : Any,
out VIEWHOLDER : BaseViewHolder<INSTANCE>
> : AdapterDelegate<SUPERTYPE> {
val itemClass: Class<INSTANCE>
@Suppress("UNCHECKED_CAST")
override fun bind(
viewHolder: RecyclerView.ViewHolder,
itemToBind: SUPERTYPE
) {
(viewHolder as VIEWHOLDER).bind(itemToBind as INSTANCE)
}
override fun isFor(item: SUPERTYPE) = itemClass.isInstance(item)
override fun createViewHolder(parent: ViewGroup): VIEWHOLDER
}

View File

@ -1,30 +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.core.base.adapter
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.ViewHolder
interface AdapterDelegate<in T> {
fun createViewHolder(parent: ViewGroup): ViewHolder
fun bind(viewHolder: ViewHolder, itemToBind: T)
fun isFor(item: T): Boolean
}

View File

@ -1,56 +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.core.base.adapter
import android.view.ViewGroup
import androidx.collection.SparseArrayCompat
import androidx.recyclerview.widget.RecyclerView
class AdapterDelegateManager<T> {
fun addDelegate(delegate: AdapterDelegate<T>) {
delegates.put(delegates.size(), delegate)
}
@Suppress("UnsafeCallOnNullableType")
fun createViewHolder(
parent: ViewGroup,
viewType: Int
) = delegates[viewType]!!.createViewHolder(parent)
fun onBindViewHolder(
libraryListItem: T,
holder: RecyclerView.ViewHolder
) {
delegates[holder.itemViewType]?.bind(holder, libraryListItem)
}
fun getViewTypeFor(item: T) = delegates.keyAt(getDelegateIndexFor(item))
private fun getDelegateIndexFor(item: T): Int {
for (index in 0..delegates.size()) {
val valueAt = delegates.valueAt(index)
if (valueAt.isFor(item) == true) {
return index
}
}
throw RuntimeException("No delegate registered for $item")
}
private var delegates: SparseArrayCompat<AdapterDelegate<T>> = SparseArrayCompat()
}

View File

@ -1,66 +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.core.base.adapter
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
abstract class BaseDelegateAdapter<ITEM>(
vararg delegates: AdapterDelegate<ITEM>,
private val delegateManager: AdapterDelegateManager<ITEM> = AdapterDelegateManager()
) : Adapter<ViewHolder>() {
init {
delegates.forEach(delegateManager::addDelegate)
@Suppress("LeakingThis")
setHasStableIds(true)
}
var items: List<ITEM> = mutableListOf()
set(value) {
field = value
notifyDataSetChanged()
}
// Function to add new data to the adapter
fun addData(newData: List<ITEM>) {
val startPosition = items.size
items = items.toMutableList().apply { addAll(newData) }
notifyItemRangeInserted(startPosition, newData.size)
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
) = delegateManager.createViewHolder(parent, viewType)
override fun getItemCount() = items.size
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
delegateManager.onBindViewHolder(items[position], holder)
}
override fun getItemViewType(position: Int) =
delegateManager.getViewTypeFor(items[position])
override fun getItemId(position: Int): Long = getIdFor(items[position])
abstract fun getIdFor(item: ITEM): Long
}

View File

@ -29,6 +29,7 @@ import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.BookmarkViewModel
import org.kiwix.kiwixmobile.core.page.history.viewmodel.HistoryViewModel
import org.kiwix.kiwixmobile.core.page.notes.viewmodel.NotesViewModel
import org.kiwix.kiwixmobile.core.search.viewmodel.SearchViewModel
import org.kiwix.kiwixmobile.core.settings.viewmodel.SettingsViewModel
@Module
abstract class CoreViewModelModule {
@ -54,4 +55,9 @@ abstract class CoreViewModelModule {
@IntoMap
@ViewModelKey(BookmarkViewModel::class)
abstract fun bindBookmarksViewModel(bookmarksViewModel: BookmarkViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(SettingsViewModel::class)
abstract fun bindSettingsViewModel(settingsViewModel: SettingsViewModel): ViewModel
}

View File

@ -1,25 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2022 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.di.qualifiers
import javax.inject.Qualifier
@Qualifier
@kotlin.annotation.Retention
annotation class Computation

View File

@ -1,24 +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.core.di.qualifiers
import javax.inject.Qualifier
@Qualifier
@kotlin.annotation.Retention
annotation class IO

View File

@ -1,24 +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.core.di.qualifiers
import javax.inject.Qualifier
@Qualifier
@kotlin.annotation.Retention
annotation class MainThread

View File

@ -31,6 +31,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.BuildConfig
@ -145,11 +147,9 @@ open class KiwixWebView @SuppressLint("SetJavaScriptEnabled") constructor(
super.onAttachedToWindow()
// cancel any previous running job.
textZoomJob?.cancel()
textZoomJob = CoroutineScope(SupervisorJob() + Dispatchers.Main).launch {
sharedPreferenceUtil.textZooms.collect {
settings.textZoom = it
}
}
textZoomJob = sharedPreferenceUtil.textZooms
.onEach { settings.textZoom = it }
.launchIn(CoroutineScope(SupervisorJob() + Dispatchers.Main))
}
override fun onDetachedFromWindow() {

View File

@ -1,21 +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.core.search
data class SearchSuggestion(val title: String, val url: String)

View File

@ -144,9 +144,6 @@ abstract class CoreSettingsFragment : SettingsContract.View, BaseFragment() {
DialogHost(alertDialogShower as AlertDialogShower)
}
}
lifecycleScope.launch {
setStorage()
}
settingsScreenState.value.update {
copy(versionInformation = "$versionName Build: $versionCode")
}
@ -173,6 +170,9 @@ abstract class CoreSettingsFragment : SettingsContract.View, BaseFragment() {
Action.OpenCredits -> openCredits()
}
}.launchIn(lifecycleScope)
lifecycleScope.launch {
setStorage()
}
}
override fun onCreateView(

View File

@ -34,8 +34,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.AlertDialog
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
@ -44,7 +45,6 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -69,11 +69,14 @@ import org.kiwix.kiwixmobile.core.settings.viewmodel.Action.ImportBookmarks
import org.kiwix.kiwixmobile.core.settings.viewmodel.Action.OnStorageItemClick
import org.kiwix.kiwixmobile.core.settings.viewmodel.Action.OpenCredits
import org.kiwix.kiwixmobile.core.settings.viewmodel.SettingsViewModel
import org.kiwix.kiwixmobile.core.settings.viewmodel.ZOOM_OFFSET
import org.kiwix.kiwixmobile.core.settings.viewmodel.ZOOM_SCALE
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.components.StorageDeviceItem
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.CATEGORY_TITLE_TEXT_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.DIALOG_DEFAULT_PADDING_FOR_CONTENT
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP
@ -83,6 +86,9 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.STORAGE_LOADING_PROGRESS_B
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWELVE_DP
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange
import org.kiwix.kiwixmobile.core.utils.dialog.DialogConfirmButton
import org.kiwix.kiwixmobile.core.utils.dialog.DialogTitle
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixBasicDialogFrame
import java.util.Locale
import kotlin.math.roundToInt
@ -201,7 +207,7 @@ private fun LanguageCategory(settingScreenState: SettingScreenState) {
val selectedIndex = languageCodes.indexOf(selectedCode)
SettingsCategory(stringResource(R.string.pref_language_title)) {
ListPreference(
title = stringResource(R.string.pref_language_title),
titleId = R.string.pref_language_title,
summary = languageDisplayNames.getOrNull(selectedIndex) ?: selectedCode,
options = languageDisplayNames,
selectedOption = languageDisplayNames[selectedIndex]
@ -319,7 +325,7 @@ private fun DisplayCategory(settingsViewModel: SettingsViewModel) {
val darkModeLabel by settingsViewModel.darkModeLabel.collectAsState()
val backToTopEnabled by settingsViewModel.backToTopEnabled
val textZoom by settingsViewModel.textZoom.collectAsState()
val textZoomPosition by settingsViewModel.textZoomPosition.collectAsState()
val textZoomPosition = (textZoom / ZOOM_SCALE) - ZOOM_OFFSET
SettingsCategory(stringResource(R.string.pref_display_title)) {
DarkModePreference(darkModeLabel = darkModeLabel, settingsViewModel = settingsViewModel)
SwitchPreference(
@ -352,7 +358,7 @@ fun DarkModePreference(
}
ListPreference(
title = stringResource(id = R.string.pref_dark_mode),
titleId = R.string.pref_dark_mode,
summary = stringResource(id = R.string.pref_dark_mode_summary),
options = entries,
selectedOption = darkModeLabel,
@ -443,9 +449,10 @@ private fun SeekBarPreference(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ListPreference(
title: String,
titleId: Int,
summary: String,
options: List<String>,
selectedOption: String,
@ -459,53 +466,68 @@ fun ListPreference(
.fillMaxWidth()
.clickable { showDialog = true }
) {
PreferenceItem(title, summary) { showDialog = true }
PreferenceItem(stringResource(titleId), summary) { showDialog = true }
}
if (showDialog) {
AlertDialog(
onDismissRequest = { showDialog = false },
title = {
Text(text = title)
},
text = {
Column {
options.forEach { option ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.selectable(
selected = option == selected,
onClick = {
selected = option
onOptionSelected(option)
showDialog = false
}
)
.padding(vertical = EIGHT_DP)
) {
RadioButton(
selected = option == selected,
onClick = {
selected = option
onOptionSelected(option)
showDialog = false
}
)
Text(text = option, style = MaterialTheme.typography.bodyLarge)
}
}
}
},
confirmButton = {
TextButton(onClick = {
KiwixBasicDialogFrame(
onDismissRequest = { showDialog = false }
) {
DialogTitle(titleId)
ListOptions(
modifier = Modifier
.fillMaxWidth()
.weight(1f, fill = false)
.verticalScroll(rememberScrollState()),
options = options,
selected = selected,
onOptionSelected = {
selected = it
onOptionSelected(it)
showDialog = false
}) {
Text(stringResource(R.string.cancel))
}
)
Spacer(modifier = Modifier.height(DIALOG_DEFAULT_PADDING_FOR_CONTENT))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
DialogConfirmButton(
confirmButtonText = stringResource(R.string.cancel),
dialogConfirmButtonClick = { showDialog = false },
null
)
}
)
}
}
}
@Composable
private fun ListOptions(
modifier: Modifier,
options: List<String>,
selected: String,
onOptionSelected: (String) -> Unit
) {
Column(modifier = modifier) {
options.forEach { option ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.selectable(
selected = option == selected,
onClick = { onOptionSelected(option) }
)
.padding(vertical = EIGHT_DP)
) {
RadioButton(
selected = option == selected,
onClick = { onOptionSelected(option) }
)
Text(text = option, style = MaterialTheme.typography.bodyLarge)
}
}
}
}

View File

@ -1,121 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2O24 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.settings
import android.content.Context
import android.util.AttributeSet
import android.view.View.VISIBLE
import android.widget.ProgressBar
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
const val MARGIN_TOP = 8
class StorageLoadingPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = ZERO
) : Preference(context, attrs, defStyleAttr) {
private var customProgressTitle: TextView? = null
private var progressBarTitleText: String? = null
init {
widgetLayoutResource = R.layout.item_custom_spinner
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val progressBar = holder.findViewById(R.id.custom_progressbar) as? ProgressBar
customProgressTitle = holder.findViewById(R.id.custom_progress_title) as TextView
progressBarTitleText?.let(::setProgressBarTitle)
val constraintLayout = holder.itemView as ConstraintLayout
val constraintSet = ConstraintSet()
constraintSet.clone(constraintLayout)
constraintSet.connect(
progressBar?.id ?: ZERO,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
ZERO
)
constraintSet.connect(
progressBar?.id ?: ZERO,
ConstraintSet.END,
ConstraintSet.PARENT_ID,
ConstraintSet.END,
ZERO
)
constraintSet.connect(
progressBar?.id ?: ZERO,
ConstraintSet.TOP,
ConstraintSet.PARENT_ID,
ConstraintSet.TOP,
ZERO
)
constraintSet.connect(
customProgressTitle?.id ?: ZERO,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
ZERO
)
constraintSet.connect(
customProgressTitle?.id ?: ZERO,
ConstraintSet.END,
ConstraintSet.PARENT_ID,
ConstraintSet.END,
ZERO
)
constraintSet.connect(
customProgressTitle?.id ?: ZERO,
ConstraintSet.TOP,
progressBar?.id ?: ZERO,
ConstraintSet.BOTTOM,
MARGIN_TOP
)
constraintSet.connect(
customProgressTitle?.id ?: ZERO,
ConstraintSet.BOTTOM,
ConstraintSet.PARENT_ID,
ConstraintSet.BOTTOM,
ZERO
)
constraintSet.applyTo(constraintLayout)
}
fun setTitle(title: String) {
progressBarTitleText = title
setProgressBarTitle(title)
}
private fun setProgressBarTitle(title: String) {
customProgressTitle?.apply {
text = title
visibility = VISIBLE
}
}
}

View File

@ -1,92 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2024 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.settings
import android.content.Context
import android.util.AttributeSet
import android.widget.ProgressBar
import android.widget.RadioButton
import android.widget.TextView
import androidx.preference.CheckBoxPreference
import androidx.preference.PreferenceViewHolder
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil.Companion.PREF_EXTERNAL_STORAGE
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil.Companion.PREF_INTERNAL_STORAGE
class StorageRadioButtonPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : CheckBoxPreference(context, attrs, defStyleAttr) {
init {
widgetLayoutResource = R.layout.item_storage_preference
}
private var radioButton: RadioButton? = null
private var progressBar: ProgressBar? = null
private var usedSpaceTextView: TextView? = null
private var freeSpaceTextView: TextView? = null
private var pathAndTitleTextView: TextView? = null
private var usedSpace: String? = null
private var freeSpace: String? = null
private var pathAndTitle: String? = null
private var progress: Int = 0
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
radioButton = holder.findViewById(R.id.radioButton) as RadioButton
progressBar = holder.findViewById(R.id.storageProgressBar) as ProgressBar
usedSpaceTextView = holder.findViewById(R.id.usedSpace) as TextView
freeSpaceTextView = holder.findViewById(R.id.freeSpace) as TextView
pathAndTitleTextView = holder.findViewById(R.id.storagePathAndTitle) as TextView
radioButton?.isChecked = isChecked
usedSpaceTextView?.let { it.text = usedSpace }
freeSpaceTextView?.let { it.text = freeSpace }
pathAndTitleTextView?.let { it.text = pathAndTitle }
progressBar?.let { it.progress = progress }
}
override fun onClick() {
if (isChecked) return
preferenceManager.findPreference<CheckBoxPreference>(PREF_INTERNAL_STORAGE)?.isChecked = false
preferenceManager.findPreference<CheckBoxPreference>(PREF_EXTERNAL_STORAGE)?.isChecked = false
super.onClick()
}
fun setProgress(usedPercentage: Int) {
progress = usedPercentage
progressBar?.progress = usedPercentage
}
fun setUsedSpace(usedSpace: String) {
this.usedSpace = usedSpace
usedSpaceTextView?.text = usedSpace
}
fun setFreeSpace(freeSpace: String) {
this.freeSpace = freeSpace
freeSpaceTextView?.text = freeSpace
}
fun setPathAndTitleForStorage(storageTitleAndPath: String) {
pathAndTitle = storageTitleAndPath
pathAndTitleTextView?.text = storageTitleAndPath
}
}

View File

@ -23,7 +23,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -32,7 +31,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.DarkModeConfig
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.ui.components.TWO
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
@ -73,9 +71,6 @@ class SettingsViewModel @Inject constructor(
initialValue = sharedPreferenceUtil.textZoom
)
private val _textZoomPosition = MutableStateFlow(TWO)
val textZoomPosition: StateFlow<Int> = _textZoomPosition
var newTabInBackground = mutableStateOf(sharedPreferenceUtil.prefNewTabBackground)
val wifiOnly: StateFlow<Boolean> = sharedPreferenceUtil.prefWifiOnlys
@ -108,7 +103,6 @@ class SettingsViewModel @Inject constructor(
}
fun setTextZoom(position: Int) {
_textZoomPosition.value = position
sharedPreferenceUtil.textZoom = (position + ZOOM_OFFSET) * ZOOM_SCALE
}

View File

@ -50,9 +50,8 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
private val _prefStorages = MutableStateFlow("")
val prefStorages
get() = _prefStorages.asStateFlow().onStart { emit(prefStorage) }
private val _textZooms = MutableStateFlow(DEFAULT_ZOOM)
val textZooms
get() = _textZooms.asStateFlow().onStart { emit(textZoom) }
private val _textZooms = MutableStateFlow(textZoom)
val textZooms get() = _textZooms.asStateFlow()
private val darkModes = MutableStateFlow(DarkModeConfig.Mode.SYSTEM)
private val _prefWifiOnlys = MutableStateFlow(true)
val prefWifiOnlys

View File

@ -26,6 +26,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -104,41 +105,53 @@ fun DialogHost(alertDialogShower: AlertDialogShower) {
val dialogData = alertDialogShower.dialogState.value
dialogData?.let { (dialog, clickListeners, uri) ->
KiwixDialogTheme {
BasicAlertDialog(
onDismissRequest = {
if (dialog.cancelable) {
alertDialogShower.dismiss()
}
},
properties = DialogProperties(usePlatformDefaultWidth = false),
modifier = Modifier.padding(DIALOG_PADDING)
KiwixBasicDialogFrame(
onDismissRequest = { alertDialogShower.dismiss() },
cancelable = dialog.cancelable,
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Surface(
DialogIcon(dialog)
DialogTitle(dialog.title)
}
DialogMessage(dialog)
ShowUri(uri)
ShowCustomComposeView(dialog)
ShowDialogButtons(dialog, clickListeners, alertDialogShower)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun KiwixBasicDialogFrame(
onDismissRequest: () -> Unit,
cancelable: Boolean = true,
content: @Composable ColumnScope.() -> Unit
) {
KiwixDialogTheme {
BasicAlertDialog(
onDismissRequest = {
if (cancelable) onDismissRequest()
},
properties = DialogProperties(usePlatformDefaultWidth = false),
modifier = Modifier.padding(DIALOG_PADDING)
) {
Surface(
modifier = Modifier
.wrapContentSize()
.wrapContentHeight(),
shape = MaterialTheme.shapes.extraSmall,
tonalElevation = AlertDialogDefaults.TonalElevation,
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier
.wrapContentSize()
.wrapContentHeight(),
shape = MaterialTheme.shapes.extraSmall,
tonalElevation = AlertDialogDefaults.TonalElevation,
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier
.padding(horizontal = DIALOG_DEFAULT_PADDING_FOR_CONTENT)
.padding(top = DIALOG_DEFAULT_PADDING_FOR_CONTENT)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
DialogIcon(dialog)
DialogTitle(dialog)
}
DialogMessage(dialog)
ShowUri(uri)
ShowCustomComposeView(dialog)
ShowDialogButtons(dialog, clickListeners, alertDialogShower)
}
}
.padding(horizontal = DIALOG_DEFAULT_PADDING_FOR_CONTENT)
.padding(top = DIALOG_DEFAULT_PADDING_FOR_CONTENT),
content = content
)
}
}
}
@ -175,16 +188,15 @@ fun DialogIcon(dialog: KiwixDialog) {
}
@Composable
private fun DialogConfirmButton(
dialog: KiwixDialog,
fun DialogConfirmButton(
confirmButtonText: String,
dialogConfirmButtonClick: (() -> Unit)?,
alertDialogShower: AlertDialogShower
alertDialogShower: AlertDialogShower?
) {
val confirmButtonText = stringResource(id = dialog.confirmButtonText)
if (confirmButtonText.isNotEmpty()) {
TextButton(
onClick = {
alertDialogShower.dismiss()
alertDialogShower?.dismiss()
dialogConfirmButtonClick?.invoke()
},
modifier = Modifier.semantics { testTag = ALERT_DIALOG_CONFIRM_BUTTON_TESTING_TAG },
@ -285,13 +297,17 @@ private fun ShowDialogButtons(
)
Spacer(modifier = Modifier.weight(1f))
DialogDismissButton(dialog, clickListeners.getOrNull(1), alertDialogShower)
DialogConfirmButton(dialog, clickListeners.getOrNull(0), alertDialogShower)
DialogConfirmButton(
stringResource(dialog.confirmButtonText),
clickListeners.getOrNull(0),
alertDialogShower
)
}
}
@Composable
private fun DialogTitle(dialog: KiwixDialog) {
dialog.title?.let {
fun DialogTitle(title: Int?) {
title?.let {
Text(
text = stringResource(id = it),
style = MaterialTheme.typography.titleSmall.copy(

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2023 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/>.
~
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/activity_horizontal_margin">
<ProgressBar
android:id="@+id/custom_progressbar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerHorizontal="true"
android:indeterminate="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/custom_progress_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/card_margin"
android:layout_marginBottom="@dimen/activity_horizontal_margin"
android:gravity="center"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/custom_progressbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_horizontal_margin">
<RadioButton
android:id="@+id/radioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0" />
<TextView
android:id="@+id/storagePathAndTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorPrimary"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/radioButton"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/usedSpace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="@+id/storagePathAndTitle"
app:layout_constraintTop_toBottomOf="@+id/storageProgressBar" />
<TextView
android:id="@+id/freeSpace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBaseline_toBaselineOf="@id/usedSpace"
app:layout_constraintEnd_toEndOf="parent" />
<ProgressBar
android:id="@+id/storageProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:indeterminate="false"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/progress_bar_state"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/radioButton"
app:layout_constraintTop_toBottomOf="@id/storagePathAndTitle" />
<View
android:id="@+id/clickOverlay"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
android:background="?selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,178 +0,0 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:iconSpaceReserved="false">
<PreferenceCategory
android:key="pref_display"
android:title="@string/pref_display_title"
app:iconSpaceReserved="false">
<ListPreference
android:defaultValue="-1"
android:entries="@array/pref_dark_modes_entries"
android:entryValues="@array/pref_dark_modes_values"
android:key="pref_dark_mode"
android:summary="@string/pref_dark_mode_summary"
android:title="@string/pref_dark_mode"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_backtotop"
android:summary="@string/pref_back_to_top_summary"
android:title="@string/pref_back_to_top"
app:iconSpaceReserved="false" />
<SeekBarPreference
android:defaultValue="2"
android:max="6"
android:summary="@string/pref_text_zoom_summary"
app:iconSpaceReserved="false"
app:key="text_zoom"
app:title="@string/pref_text_zoom_title"
app:updatesContinuously="true" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_extras"
android:title="@string/pref_extras"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_newtab_background"
android:summary="@string/pref_newtab_background_summary"
android:title="@string/pref_newtab_background_title"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_external_link_popup"
android:summary="@string/pref_external_link_popup_summary"
android:title="@string/pref_external_link_popup_title"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_wifi_only"
android:summary="@string/pref_wifi_only"
android:title="@string/pref_wifi_only"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_storage"
app:iconSpaceReserved="false"
app:title="@string/pref_storage">
<org.kiwix.kiwixmobile.core.settings.StorageLoadingPreference
android:key="storage_progressbar"
android:layout="@layout/item_custom_spinner"
app:iconSpaceReserved="false"
app:isPreferenceVisible="false" />
<org.kiwix.kiwixmobile.core.settings.StorageRadioButtonPreference
android:key="pref_internal_storage"
android:layout="@layout/item_storage_preference"
app:iconSpaceReserved="false"
app:isPreferenceVisible="false" />
<org.kiwix.kiwixmobile.core.settings.StorageRadioButtonPreference
android:key="pref_external_storage"
android:layout="@layout/item_storage_preference"
app:iconSpaceReserved="false"
app:isPreferenceVisible="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_history"
android:title="@string/history"
app:iconSpaceReserved="false">
<Preference
android:key="pref_clear_all_history"
android:summary="@string/pref_clear_all_history_summary"
android:title="@string/pref_clear_all_history_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_notes"
android:title="@string/pref_notes"
app:iconSpaceReserved="false">
<Preference
android:key="pref_clear_all_notes"
android:summary="@string/pref_clear_all_notes_summary"
android:title="@string/pref_clear_all_notes_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_bookmark"
android:title="@string/bookmarks"
app:iconSpaceReserved="false">
<Preference
android:key="pref_import_bookmark"
android:summary="@string/pref_import_bookmark_summary"
android:title="@string/pref_import_bookmark_title"
app:iconSpaceReserved="false" />
<Preference
android:key="pref_export_bookmark"
android:summary="@string/pref_export_bookmark_summary"
android:title="@string/pref_export_bookmark_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_permissions"
android:title="@string/pref_permission"
app:iconSpaceReserved="false"
app:isPreferenceVisible="false">
<Preference
android:key="pref_manage_external_storage"
android:title="@string/pref_allow_to_read_or_write_zim_files_on_sd_card"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_language"
android:title="@string/pref_language_title"
app:iconSpaceReserved="false">
<ListPreference
android:key="pref_language_chooser"
android:summary="@string/pref_language_chooser"
android:title="@string/pref_language_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_info"
android:title="@string/pref_info_title"
app:iconSpaceReserved="false">
<EditTextPreference
android:enabled="false"
android:key="pref_version"
android:persistent="false"
android:selectable="true"
android:shouldDisableView="false"
android:title="@string/pref_info_version"
app:iconSpaceReserved="false" />
<Preference
android:key="pref_credits"
android:summary="@string/pref_credits"
android:title="@string/pref_credits_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
</PreferenceScreen>