From 9b0d85bf6c9cf1a6b476be477255f52ac07195c4 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 23 May 2022 18:33:46 -0400 Subject: [PATCH] Migrate settings search view to Compose --- .../more/settings/SettingsSearchScreen.kt | 85 ++++++++++++ .../setting/search/SettingsSearchAdapter.kt | 82 ----------- .../search/SettingsSearchController.kt | 129 ++++-------------- .../ui/setting/search/SettingsSearchHelper.kt | 2 +- .../ui/setting/search/SettingsSearchHolder.kt | 41 ------ .../ui/setting/search/SettingsSearchItem.kt | 57 -------- .../setting/search/SettingsSearchPresenter.kt | 31 ++++- .../res/layout/settings_search_controller.xml | 36 ----- .../settings_search_controller_card.xml | 37 ----- 9 files changed, 134 insertions(+), 366 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/more/settings/SettingsSearchScreen.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchAdapter.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHolder.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchItem.kt delete mode 100644 app/src/main/res/layout/settings_search_controller.xml delete mode 100644 app/src/main/res/layout/settings_search_controller_card.xml diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/SettingsSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/SettingsSearchScreen.kt new file mode 100644 index 0000000000..dd8756e293 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/SettingsSearchScreen.kt @@ -0,0 +1,85 @@ +package eu.kanade.presentation.more.settings + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.unit.dp +import eu.kanade.presentation.components.ScrollbarLazyColumn +import eu.kanade.presentation.util.horizontalPadding +import eu.kanade.tachiyomi.ui.setting.SettingsController +import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchHelper +import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchPresenter +import kotlin.reflect.full.createInstance + +@Composable +fun SettingsSearchScreen( + nestedScroll: NestedScrollConnection, + presenter: SettingsSearchPresenter, + onClickResult: (SettingsController) -> Unit, +) { + val results by presenter.state.collectAsState() + + val scrollState = rememberLazyListState() + ScrollbarLazyColumn( + modifier = Modifier + .nestedScroll(nestedScroll), + contentPadding = WindowInsets.navigationBars.asPaddingValues(), + state = scrollState, + ) { + items( + items = results, + key = { it.key.toString() }, + ) { result -> + SearchResult(result, onClickResult) + } + } +} + +@Composable +private fun SearchResult( + result: SettingsSearchHelper.SettingsSearchResult, + onClickResult: (SettingsController) -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = horizontalPadding, vertical = 8.dp) + .clickable { + // Must pass a new Controller instance to avoid this error + // https://github.com/bluelinelabs/Conductor/issues/446 + val controller = result.searchController::class.createInstance() + controller.preferenceKey = result.key + onClickResult(controller) + }, + ) { + Text( + text = result.title, + ) + + Text( + text = result.summary, + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.outline, + ), + ) + + Text( + text = result.breadcrumb, + style = MaterialTheme.typography.bodySmall, + ) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchAdapter.kt deleted file mode 100644 index 2d749363c9..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchAdapter.kt +++ /dev/null @@ -1,82 +0,0 @@ -package eu.kanade.tachiyomi.ui.setting.search - -import android.os.Bundle -import android.os.Parcelable -import android.util.SparseArray -import androidx.recyclerview.widget.RecyclerView -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.kanade.tachiyomi.ui.setting.SettingsController - -/** - * Adapter that holds the search cards. - * - * @param controller instance of [SettingsSearchController]. - */ -class SettingsSearchAdapter(val controller: SettingsSearchController) : - FlexibleAdapter(null, controller, true) { - - val titleClickListener: OnTitleClickListener = controller - - /** - * Bundle where the view state of the holders is saved. - */ - private var bundle = Bundle() - - override fun onBindViewHolder( - holder: RecyclerView.ViewHolder, - position: Int, - payloads: List, - ) { - super.onBindViewHolder(holder, position, payloads) - restoreHolderState(holder) - } - - override fun onViewRecycled(holder: RecyclerView.ViewHolder) { - super.onViewRecycled(holder) - saveHolderState(holder, bundle) - } - - override fun onSaveInstanceState(outState: Bundle) { - val holdersBundle = Bundle() - allBoundViewHolders.forEach { saveHolderState(it, holdersBundle) } - outState.putBundle(HOLDER_BUNDLE_KEY, holdersBundle) - super.onSaveInstanceState(outState) - } - - override fun onRestoreInstanceState(savedInstanceState: Bundle) { - super.onRestoreInstanceState(savedInstanceState) - bundle = savedInstanceState.getBundle(HOLDER_BUNDLE_KEY)!! - } - - /** - * Saves the view state of the given holder. - * - * @param holder The holder to save. - * @param outState The bundle where the state is saved. - */ - private fun saveHolderState(holder: RecyclerView.ViewHolder, outState: Bundle) { - val key = "holder_${holder.bindingAdapterPosition}" - val holderState = SparseArray() - holder.itemView.saveHierarchyState(holderState) - outState.putSparseParcelableArray(key, holderState) - } - - /** - * Restores the view state of the given holder. - * - * @param holder The holder to restore. - */ - private fun restoreHolderState(holder: RecyclerView.ViewHolder) { - val key = "holder_${holder.bindingAdapterPosition}" - bundle.getSparseParcelableArray(key)?.let { - holder.itemView.restoreHierarchyState(it) - bundle.remove(key) - } - } - - interface OnTitleClickListener { - fun onTitleClick(ctrl: SettingsController) - } -} - -private const val HOLDER_BUNDLE_KEY = "holder_bundle" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt index 94826ef5ad..e87346390d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt @@ -1,68 +1,45 @@ package eu.kanade.tachiyomi.ui.setting.search -import android.os.Bundle -import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem -import android.view.View import androidx.appcompat.widget.SearchView -import androidx.recyclerview.widget.LinearLayoutManager -import dev.chrisbanes.insetter.applyInsetter +import androidx.compose.runtime.Composable +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import eu.kanade.presentation.more.settings.SettingsSearchScreen import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.databinding.SettingsSearchControllerBinding -import eu.kanade.tachiyomi.ui.base.controller.NucleusController +import eu.kanade.tachiyomi.ui.base.controller.ComposeController import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.setting.SettingsController -/** - * This controller shows and manages the different search result in settings search. - * [SettingsSearchAdapter.OnTitleClickListener] called when preference is clicked in settings search - */ -class SettingsSearchController : - NucleusController(), - SettingsSearchAdapter.OnTitleClickListener { +class SettingsSearchController : ComposeController() { - /** - * Adapter containing search results grouped by lang. - */ - private var adapter: SettingsSearchAdapter? = null private lateinit var searchView: SearchView init { setHasOptionsMenu(true) } - override fun createBinding(inflater: LayoutInflater) = SettingsSearchControllerBinding.inflate(inflater) + override fun getTitle() = presenter.query - override fun getTitle(): String? { - return presenter.query + override fun createPresenter() = SettingsSearchPresenter() + + @Composable + override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) { + SettingsSearchScreen( + nestedScroll = nestedScrollInterop, + presenter = presenter, + onClickResult = { controller -> + searchView.query.let { + presenter.setLastSearchQuerySearchSettings(it.toString()) + } + router.pushController(controller) + }, + ) } - /** - * Create the [SettingsSearchPresenter] used in controller. - * - * @return instance of [SettingsSearchPresenter] - */ - override fun createPresenter(): SettingsSearchPresenter { - return SettingsSearchPresenter() - } - - /** - * Adds items to the options menu. - * - * @param menu menu containing options. - * @param inflater used to load the menu xml. - */ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.settings_main, menu) - binding.recycler.applyInsetter { - type(navigationBars = true) { - padding() - } - } - // Initialize search menu val searchItem = menu.findItem(R.id.action_search) searchView = searchItem.actionView as SearchView @@ -70,7 +47,6 @@ class SettingsSearchController : searchView.queryHint = applicationContext?.getString(R.string.action_search_settings) searchItem.expandActionView() - setItems(getResultSet()) searchItem.setOnActionExpandListener( object : MenuItem.OnActionExpandListener { @@ -88,76 +64,17 @@ class SettingsSearchController : searchView.setOnQueryTextListener( object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { - setItems(getResultSet(query)) + presenter.searchSettings(query) return false } override fun onQueryTextChange(newText: String?): Boolean { - setItems(getResultSet(newText)) + presenter.searchSettings(newText) return false } }, ) - searchView.setQuery(presenter.preferences.lastSearchQuerySearchSettings().get(), true) - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) - - adapter = SettingsSearchAdapter(this) - binding.recycler.layoutManager = LinearLayoutManager(view.context) - binding.recycler.adapter = adapter - - // load all search results - SettingsSearchHelper.initPreferenceSearchResultCollection(presenter.preferences.context) - } - - override fun onDestroyView(view: View) { - adapter = null - super.onDestroyView(view) - } - - override fun onSaveViewState(view: View, outState: Bundle) { - super.onSaveViewState(view, outState) - adapter?.onSaveInstanceState(outState) - } - - override fun onRestoreViewState(view: View, savedViewState: Bundle) { - super.onRestoreViewState(view, savedViewState) - adapter?.onRestoreInstanceState(savedViewState) - } - - /** - * returns a list of `SettingsSearchItem` to be shown as search results - * Future update: should we add a minimum length to the query before displaying results? Consider other languages. - */ - fun getResultSet(query: String? = null): List { - if (!query.isNullOrBlank()) { - return SettingsSearchHelper.getFilteredResults(query) - .map { SettingsSearchItem(it, null) } - } - - return mutableListOf() - } - - /** - * Add search result to adapter. - * - * @param searchResult result of search. - */ - fun setItems(searchResult: List) { - adapter?.updateDataSet(searchResult) - } - - /** - * Opens a catalogue with the given search. - */ - override fun onTitleClick(ctrl: SettingsController) { - searchView.query.let { - presenter.preferences.lastSearchQuerySearchSettings().set(it.toString()) - } - - router.pushController(ctrl) + searchView.setQuery(presenter.getLastSearchQuerySearchSettings(), true) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHelper.kt index cb9366bf00..107c23fee0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHelper.kt @@ -46,7 +46,7 @@ object SettingsSearchHelper { * Must be called to populate `prefSearchResultList` */ @SuppressLint("RestrictedApi") - fun initPreferenceSearchResultCollection(context: Context) { + fun initPreferenceSearchResults(context: Context) { val preferenceManager = PreferenceManager(context) prefSearchResultList.clear() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHolder.kt deleted file mode 100644 index 1dd79a22ca..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHolder.kt +++ /dev/null @@ -1,41 +0,0 @@ -package eu.kanade.tachiyomi.ui.setting.search - -import android.view.View -import eu.davidea.viewholders.FlexibleViewHolder -import eu.kanade.tachiyomi.databinding.SettingsSearchControllerCardBinding -import kotlin.reflect.full.createInstance - -/** - * Holder that binds the [SettingsSearchItem] containing catalogue cards. - * - * @param view view of [SettingsSearchItem] - * @param adapter instance of [SettingsSearchAdapter] - */ -class SettingsSearchHolder(view: View, val adapter: SettingsSearchAdapter) : - FlexibleViewHolder(view, adapter) { - - private val binding = SettingsSearchControllerCardBinding.bind(view) - - init { - binding.titleWrapper.setOnClickListener { - adapter.getItem(bindingAdapterPosition)?.let { - val ctrl = it.settingsSearchResult.searchController::class.createInstance() - ctrl.preferenceKey = it.settingsSearchResult.key - - // must pass a new Controller instance to avoid this error https://github.com/bluelinelabs/Conductor/issues/446 - adapter.titleClickListener.onTitleClick(ctrl) - } - } - } - - /** - * Show the loading of source search result. - * - * @param item item of card. - */ - fun bind(item: SettingsSearchItem) { - binding.searchResultPrefTitle.text = item.settingsSearchResult.title - binding.searchResultPrefSummary.text = item.settingsSearchResult.summary - binding.searchResultPrefBreadcrumb.text = item.settingsSearchResult.breadcrumb - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchItem.kt deleted file mode 100644 index 092e0178d5..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchItem.kt +++ /dev/null @@ -1,57 +0,0 @@ -package eu.kanade.tachiyomi.ui.setting.search - -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.tachiyomi.R - -/** - * Item that contains search result information. - * - * @param pref the source for the search results. - * @param results the search results. - */ -class SettingsSearchItem( - val settingsSearchResult: SettingsSearchHelper.SettingsSearchResult, - val results: List?, -) : - AbstractFlexibleItem() { - - override fun getLayoutRes(): Int { - return R.layout.settings_search_controller_card - } - - /** - * Create view holder (see [SettingsSearchAdapter]. - * - * @return holder of view. - */ - override fun createViewHolder( - view: View, - adapter: FlexibleAdapter>, - ): SettingsSearchHolder { - return SettingsSearchHolder(view, adapter as SettingsSearchAdapter) - } - - override fun bindViewHolder( - adapter: FlexibleAdapter>, - holder: SettingsSearchHolder, - position: Int, - payloads: List?, - ) { - holder.bind(this) - } - - override fun equals(other: Any?): Boolean { - if (other is SettingsSearchItem) { - return settingsSearchResult == settingsSearchResult - } - return false - } - - override fun hashCode(): Int { - return settingsSearchResult.hashCode() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchPresenter.kt index 6587d100a8..c73b675fa7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchPresenter.kt @@ -3,20 +3,39 @@ package eu.kanade.tachiyomi.ui.setting.search import android.os.Bundle import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class SettingsSearchPresenter : BasePresenter() { +class SettingsSearchPresenter( + private val preferences: PreferencesHelper = Injekt.get(), +) : BasePresenter() { - val preferences: PreferencesHelper = Injekt.get() + private val _state: MutableStateFlow> = + MutableStateFlow(emptyList()) + val state: StateFlow> = _state.asStateFlow() override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - query = savedState?.getString(SettingsSearchPresenter::query.name) ?: "" // TODO - Some way to restore previous query? + + SettingsSearchHelper.initPreferenceSearchResults(preferences.context) } - override fun onSave(state: Bundle) { - state.putString(SettingsSearchPresenter::query.name, query) - super.onSave(state) + fun getLastSearchQuerySearchSettings(): String { + return preferences.lastSearchQuerySearchSettings().get() + } + + fun setLastSearchQuerySearchSettings(query: String) { + preferences.lastSearchQuerySearchSettings().set(query) + } + + fun searchSettings(query: String?) { + _state.value = if (!query.isNullOrBlank()) { + SettingsSearchHelper.getFilteredResults(query) + } else { + emptyList() + } } } diff --git a/app/src/main/res/layout/settings_search_controller.xml b/app/src/main/res/layout/settings_search_controller.xml deleted file mode 100644 index 618fdf8754..0000000000 --- a/app/src/main/res/layout/settings_search_controller.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/settings_search_controller_card.xml b/app/src/main/res/layout/settings_search_controller_card.xml deleted file mode 100644 index b07d41d75c..0000000000 --- a/app/src/main/res/layout/settings_search_controller_card.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - -