Search through settings

This commit is contained in:
Koitharu
2024-10-20 17:01:17 +03:00
parent 100073f45e
commit 3d285104a4
28 changed files with 434 additions and 12 deletions

View File

@@ -9,7 +9,10 @@ import androidx.annotation.CallSuper
import androidx.annotation.StringRes
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.preference.get
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
@@ -20,9 +23,11 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.getThemeDrawable
import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.settings.SettingsActivity
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
@@ -67,6 +72,10 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
override fun onResume() {
super.onResume()
setTitle(if (titleId != 0) getString(titleId) else null)
arguments?.getString(SettingsActivity.ARG_PREF_KEY)?.let {
focusPreference(it)
arguments?.remove(SettingsActivity.ARG_PREF_KEY)
}
}
@CallSuper
@@ -87,4 +96,31 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
false
}
private fun focusPreference(key: String) {
val pref = findPreference<Preference>(key)
if (pref == null) {
scrollToPreference(key)
return
}
scrollToPreference(pref)
val prefIndex = preferenceScreen.indexOf(key)
val view = if (prefIndex >= 0) {
listView.findViewHolderForAdapterPosition(prefIndex)?.itemView ?: return
} else {
return
}
view.context.getThemeDrawable(materialR.attr.colorTertiaryContainer)?.let {
view.background = it
}
}
private fun PreferenceScreen.indexOf(key: String): Int {
for (i in 0 until preferenceCount) {
if (get(i).key == key) {
return i
}
}
return -1
}
}

View File

@@ -28,6 +28,8 @@ class AdapterDelegateClickListenerAdapter<I, O>(
private fun mappedItem(): O = itemMapper.apply(adapterDelegate.item)
fun attach() = attach(adapterDelegate.itemView)
fun attach(itemView: View) {
itemView.setOnClickListener(this)
itemView.setOnLongClickListener(this)

View File

@@ -74,6 +74,12 @@ class SearchSuggestionFragment :
companion object {
@Deprecated("",
ReplaceWith(
"SearchSuggestionFragment()",
"org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment"
)
)
fun newInstance() = SearchSuggestionFragment()
}
}

View File

@@ -6,7 +6,9 @@ import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
@@ -23,11 +25,17 @@ import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaSourceInfo
import org.koitharu.kotatsu.core.parser.external.ExternalMangaSource
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ActivitySettingsBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
import org.koitharu.kotatsu.settings.search.SettingsItem
import org.koitharu.kotatsu.settings.search.SettingsSearchFragment
import org.koitharu.kotatsu.settings.search.SettingsSearchMenuProvider
import org.koitharu.kotatsu.settings.search.SettingsSearchViewModel
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
import org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment
@@ -48,6 +56,8 @@ class SettingsActivity :
private var screenPadding = 0
private val viewModel: SettingsSearchViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivitySettingsBinding.inflate(layoutInflater))
@@ -64,6 +74,9 @@ class SettingsActivity :
replace(R.id.container_master, RootSettingsFragment())
}
}
viewModel.isSearchActive.observe(this, ::toggleSearchMode)
viewModel.onNavigateToPreference.observeEvent(this, ::navigateToPreference)
addMenuProvider(SettingsSearchMenuProvider(viewModel))
addMenuProvider(SettingsMenuProvider(this))
}
@@ -97,6 +110,7 @@ class SettingsActivity :
}
fun openFragment(fragmentClass: Class<out Fragment>, args: Bundle?, isFromRoot: Boolean) {
viewModel.discardSearch()
val hasFragment = supportFragmentManager.findFragmentById(R.id.container) != null
supportFragmentManager.commit {
setReorderingAllowed(true)
@@ -108,6 +122,27 @@ class SettingsActivity :
}
}
private fun toggleSearchMode(isEnabled: Boolean) {
viewBinding.containerSearch.isVisible = isEnabled
val searchFragment = supportFragmentManager.findFragmentById(R.id.container_search)
if (searchFragment != null) {
if (!isEnabled) {
invalidateOptionsMenu()
supportFragmentManager.commit {
setReorderingAllowed(true)
remove(searchFragment)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
}
}
} else if (isEnabled) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.container_search, SettingsSearchFragment::class.java, null)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
}
}
}
private fun openDefaultFragment() {
val fragment = when (intent?.action) {
ACTION_READER -> ReaderSettingsFragment()
@@ -138,6 +173,12 @@ class SettingsActivity :
}
}
private fun navigateToPreference(item: SettingsItem) {
val args = Bundle(1)
args.putString(ARG_PREF_KEY, item.key)
openFragment(item.fragmentClass, args, true)
}
companion object {
private const val ACTION_READER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
@@ -152,6 +193,7 @@ class SettingsActivity :
private const val EXTRA_SOURCE = "source"
private const val HOST_ABOUT = "about"
private const val HOST_SYNC_SETTINGS = "sync-settings"
const val ARG_PREF_KEY = "pref_key"
fun newIntent(context: Context) = Intent(context, SettingsActivity::class.java)

View File

@@ -0,0 +1,16 @@
package org.koitharu.kotatsu.settings.search
import androidx.preference.PreferenceFragmentCompat
import org.koitharu.kotatsu.list.ui.model.ListModel
data class SettingsItem(
val key: String,
val title: CharSequence,
val breadcrumbs: List<String>,
val fragmentClass: Class<out PreferenceFragmentCompat>,
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {
return other is SettingsItem && other.key == key
}
}

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.settings.search
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemPreferenceBinding
fun settingsItemAD(
listener: OnListItemClickListener<SettingsItem>,
) = adapterDelegateViewBinding<SettingsItem, SettingsItem, ItemPreferenceBinding>(
{ layoutInflater, parent -> ItemPreferenceBinding.inflate(layoutInflater, parent, false) },
) {
AdapterDelegateClickListenerAdapter(this, listener).attach()
val breadcrumbsSeparator = getString(R.string.breadcrumbs_separator)
bind {
binding.textViewTitle.text = item.title
binding.textViewSummary.textAndVisible = item.breadcrumbs.joinToString(breadcrumbsSeparator)
}
}

View File

@@ -0,0 +1,48 @@
package org.koitharu.kotatsu.settings.search
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.FragmentSearchSuggestionBinding
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
@AndroidEntryPoint
class SettingsSearchFragment : BaseFragment<FragmentSearchSuggestionBinding>(), OnListItemClickListener<SettingsItem> {
private val viewModel: SettingsSearchViewModel by activityViewModels()
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSearchSuggestionBinding {
return FragmentSearchSuggestionBinding.inflate(inflater, container, false)
}
override fun onViewBindingCreated(binding: FragmentSearchSuggestionBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
val adapter = BaseListAdapter<SettingsItem>()
.addDelegate(ListItemType.NAV_ITEM, settingsItemAD(this))
binding.root.adapter = adapter
binding.root.setHasFixedSize(true)
viewModel.content.observe(viewLifecycleOwner, adapter)
}
override fun onWindowInsetsChanged(insets: Insets) {
val extraPadding = resources.getDimensionPixelOffset(R.dimen.list_spacing)
requireViewBinding().root.updatePadding(
top = extraPadding,
right = insets.right,
left = insets.left,
bottom = insets.bottom,
)
}
override fun onItemClick(item: SettingsItem, view: View) = viewModel.navigateToPreference(item)
}

View File

@@ -0,0 +1,84 @@
package org.koitharu.kotatsu.settings.search
import android.annotation.SuppressLint
import android.content.Context
import androidx.annotation.XmlRes
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen
import androidx.preference.get
import dagger.Reusable
import dagger.hilt.android.qualifiers.ApplicationContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.settings.AppearanceSettingsFragment
import org.koitharu.kotatsu.settings.DownloadsSettingsFragment
import org.koitharu.kotatsu.settings.NetworkSettingsFragment
import org.koitharu.kotatsu.settings.ReaderSettingsFragment
import org.koitharu.kotatsu.settings.ServicesSettingsFragment
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment
import org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment
import javax.inject.Inject
@Reusable
@SuppressLint("RestrictedApi")
class SettingsSearchHelper @Inject constructor(
@ApplicationContext private val context: Context,
) {
fun inflatePreferences(): List<SettingsItem> {
val preferenceManager = PreferenceManager(context)
val result = ArrayList<SettingsItem>()
preferenceManager.inflateTo(result, R.xml.pref_appearance, emptyList(), AppearanceSettingsFragment::class.java)
preferenceManager.inflateTo(result, R.xml.pref_sources, emptyList(), SourcesSettingsFragment::class.java)
preferenceManager.inflateTo(result, R.xml.pref_reader, emptyList(), ReaderSettingsFragment::class.java)
preferenceManager.inflateTo(result, R.xml.pref_network, emptyList(), NetworkSettingsFragment::class.java)
preferenceManager.inflateTo(result, R.xml.pref_user_data, emptyList(), UserDataSettingsFragment::class.java)
preferenceManager.inflateTo(result, R.xml.pref_downloads, emptyList(), DownloadsSettingsFragment::class.java)
preferenceManager.inflateTo(result, R.xml.pref_tracker, emptyList(), TrackerSettingsFragment::class.java)
preferenceManager.inflateTo(result, R.xml.pref_services, emptyList(), ServicesSettingsFragment::class.java)
preferenceManager.inflateTo(result, R.xml.pref_about, emptyList(), AboutSettingsFragment::class.java)
return result
}
private fun PreferenceManager.inflateTo(
result: MutableList<SettingsItem>,
@XmlRes resId: Int,
breadcrumbs: List<String>,
fragmentClass: Class<out PreferenceFragmentCompat>
) {
val screen = inflateFromResource(context, resId, null)
val screenTitle = screen.title?.toString()
screen.inflateTo(
result = result,
breadcrumbs = if (screenTitle.isNullOrEmpty()) breadcrumbs else breadcrumbs + screenTitle,
fragmentClass = fragmentClass,
)
}
private fun PreferenceScreen.inflateTo(
result: MutableList<SettingsItem>,
breadcrumbs: List<String>,
fragmentClass: Class<out PreferenceFragmentCompat>
): Unit = repeat(preferenceCount) { i ->
val pref = this[i]
if (pref is PreferenceScreen) {
val screenTitle = pref.title?.toString()
pref.inflateTo(
result = result,
breadcrumbs = if (screenTitle.isNullOrEmpty()) breadcrumbs else breadcrumbs + screenTitle,
fragmentClass = fragmentClass,
)
} else {
result.add(
SettingsItem(
key = pref.key ?: return@repeat,
title = pref.title ?: return@repeat,
breadcrumbs = breadcrumbs,
fragmentClass = fragmentClass,
),
)
}
}
}

View File

@@ -0,0 +1,51 @@
package org.koitharu.kotatsu.settings.search
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R
class SettingsSearchMenuProvider(
private val viewModel: SettingsSearchViewModel,
) : MenuProvider, MenuItem.OnActionExpandListener, SearchView.OnQueryTextListener {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_search, menu)
val menuItem = menu.findItem(R.id.action_search)
menuItem.setOnActionExpandListener(this)
val searchView = menuItem.actionView as SearchView
searchView.setOnQueryTextListener(this)
searchView.queryHint = menuItem.title
}
override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu)
val currentQuery = viewModel.currentQuery
if (currentQuery.isNotEmpty()) {
val menuItem = menu.findItem(R.id.action_search)
menuItem.expandActionView()
val searchView = menuItem.actionView as SearchView
searchView.setQuery(currentQuery, false)
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = false
override fun onMenuItemActionExpand(item: MenuItem): Boolean = true
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
viewModel.discardSearch()
return true
}
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
viewModel.onQueryChanged(newText.orEmpty())
return true
}
}

View File

@@ -0,0 +1,47 @@
package org.koitharu.kotatsu.settings.search
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import javax.inject.Inject
@HiltViewModel
class SettingsSearchViewModel @Inject constructor(
private val searchHelper: SettingsSearchHelper,
) : BaseViewModel() {
private val query = MutableStateFlow("")
private val allSettings by lazy {
searchHelper.inflatePreferences()
}
val content = query.map { q ->
allSettings.filter { it.title.contains(q, ignoreCase = true) }
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())
val isSearchActive = query.map {
it.isNotEmpty()
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)
val onNavigateToPreference = MutableEventFlow<SettingsItem>()
val currentQuery: String
get() = query.value
fun onQueryChanged(value: String) {
query.value = value
}
fun discardSearch() = onQueryChanged("")
fun navigateToPreference(item: SettingsItem) {
onNavigateToPreference.call(item)
}
}

View File

@@ -72,4 +72,15 @@
app:layout_constraintStart_toEndOf="@id/container_master"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_search"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -26,4 +26,11 @@
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:clipToPadding="false"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="vertical"
android:padding="4dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<TextView
android:id="@+id/textView_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
tools:text="@string/too_many_requests_message" />
<TextView
android:id="@+id/textView_summary"
style="@style/PreferenceSummaryTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
tools:text="@string/tap_to_try_again" />
</LinearLayout>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="breadcrumbs_separator" translatable="false"><![CDATA[" < "]]></string>
</resources>

View File

@@ -757,4 +757,5 @@
<string name="screen_orientation">Screen orientation</string>
<string name="portrait">Portrait</string>
<string name="landscape">Landscape</string>
<string name="breadcrumbs_separator" translatable="false"><![CDATA[" > "]]></string>
</resources>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/about">
<Preference
android:key="app_version"

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/appearance">
<org.koitharu.kotatsu.settings.utils.ThemeChooserPreference
android:key="color_theme"

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/periodic_backups">
<SwitchPreferenceCompat
android:defaultValue="false"

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/downloads"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference

View File

@@ -2,6 +2,7 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/network"
xmlns:tools="http://schemas.android.com/tools">
<ListPreference

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/notifications">
<SwitchPreferenceCompat
android:defaultValue="true"
@@ -35,4 +36,4 @@
app:allowDividerAbove="true"
app:isPreferenceVisible="false" />
</PreferenceScreen>
</PreferenceScreen>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/proxy">
<ListPreference
android:defaultValue="DIRECT"

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/reader_settings">
<ListPreference
android:entries="@array/reader_modes"

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/services">
<Preference
android:enabled="@bool/is_sync_enabled"

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/remote_sources">
<ListPreference
android:key="sources_sort_order"

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/suggestions">
<SwitchPreferenceCompat
android:defaultValue="false"

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/check_for_new_chapters">
<SwitchPreferenceCompat
android:defaultValue="true"

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/data_and_privacy">
<SwitchPreferenceCompat
android:key="protect_app"