Search through settings
parent
100073f45e
commit
3d285104a4
@ -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
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="breadcrumbs_separator" translatable="false"><![CDATA[" < "]]></string>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue