Open source from global search

Along with other changes from upstream

The difference as you'd expect:
Not allowing the opening of a source on migration and from extension intent
Also when moving up sources in global search after returning results. The incoming results for subsequent sources will go under the already returned results, instead of being alphabetical and shifting the top results (pinned sources still show on top though)

Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com>
This commit is contained in:
Jays2Kings 2021-04-24 16:31:31 -04:00
parent 48a4b3091f
commit c9388f6633
12 changed files with 155 additions and 60 deletions

View File

@ -284,7 +284,7 @@ class PreferencesHelper(val context: Context) {
fun hiddenSources() = flowPrefs.getStringSet("hidden_catalogues", mutableSetOf()) fun hiddenSources() = flowPrefs.getStringSet("hidden_catalogues", mutableSetOf())
fun pinnedCatalogues() = rxPrefs.getStringSet("pinned_catalogues", emptySet()) fun pinnedCatalogues() = flowPrefs.getStringSet("pinned_catalogues", mutableSetOf())
fun downloadNew() = flowPrefs.getBoolean(Keys.downloadNew, false) fun downloadNew() = flowPrefs.getBoolean(Keys.downloadNew, false)

View File

@ -177,7 +177,7 @@ class PreMigrationController(bundle: Bundle? = null) :
val enabledSources = if (item.itemId == R.id.action_match_enabled) { val enabledSources = if (item.itemId == R.id.action_match_enabled) {
prefs.hiddenSources().get().mapNotNull { it.toLongOrNull() } prefs.hiddenSources().get().mapNotNull { it.toLongOrNull() }
} else { } else {
prefs.pinnedCatalogues().get()?.mapNotNull { it.toLongOrNull() } ?: emptyList() prefs.pinnedCatalogues().get().mapNotNull { it.toLongOrNull() } ?: emptyList()
} }
val items = adapter?.currentItems?.toList() ?: return true val items = adapter?.currentItems?.toList() ?: return true
items.forEach { items.forEach {

View File

@ -24,7 +24,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.databinding.BrowseControllerBinding import eu.kanade.tachiyomi.databinding.BrowseControllerBinding
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
@ -390,7 +389,7 @@ class BrowseController :
} }
private fun pinCatalogue(source: Source, isPinned: Boolean) { private fun pinCatalogue(source: Source, isPinned: Boolean) {
val current = preferences.pinnedCatalogues().getOrDefault() val current = preferences.pinnedCatalogues().get()
if (isPinned) { if (isPinned) {
preferences.pinnedCatalogues().set(current - source.id.toString()) preferences.pinnedCatalogues().set(current - source.id.toString())
} else { } else {

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.source package eu.kanade.tachiyomi.ui.source
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -57,7 +56,7 @@ class SourcePresenter(
private fun loadSources() { private fun loadSources() {
scope.launch { scope.launch {
val pinnedSources = mutableListOf<SourceItem>() val pinnedSources = mutableListOf<SourceItem>()
val pinnedCatalogues = preferences.pinnedCatalogues().getOrDefault() val pinnedCatalogues = preferences.pinnedCatalogues().get()
val map = TreeMap<String, MutableList<CatalogueSource>> { d1, d2 -> val map = TreeMap<String, MutableList<CatalogueSource>> { d1, d2 ->
// Catalogues without a lang defined will be placed at the end // Catalogues without a lang defined will be placed at the end
@ -105,7 +104,7 @@ class SourcePresenter(
private fun getLastUsedSource(value: Long): SourceItem? { private fun getLastUsedSource(value: Long): SourceItem? {
return (sourceManager.get(value) as? CatalogueSource)?.let { source -> return (sourceManager.get(value) as? CatalogueSource)?.let { source ->
val pinnedCatalogues = preferences.pinnedCatalogues().getOrDefault() val pinnedCatalogues = preferences.pinnedCatalogues().get()
val isPinned = source.id.toString() in pinnedCatalogues val isPinned = source.id.toString() in pinnedCatalogues
if (isPinned) null if (isPinned) null
else SourceItem(source, null, isPinned) else SourceItem(source, null, isPinned)

View File

@ -12,7 +12,6 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -29,6 +28,7 @@ import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.source.BrowseController import eu.kanade.tachiyomi.ui.source.BrowseController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.addOrRemoveToFavorites import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
import eu.kanade.tachiyomi.util.system.connectivityManager import eu.kanade.tachiyomi.util.system.connectivityManager
@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
@ -44,12 +45,8 @@ import eu.kanade.tachiyomi.util.view.visibleIf
import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.EmptyView import eu.kanade.tachiyomi.widget.EmptyView
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit
/** /**
* Controller to manage the catalogues available in the app. * Controller to manage the catalogues available in the app.
@ -105,11 +102,6 @@ open class BrowseSourceController(bundle: Bundle) :
*/ */
private var recycler: RecyclerView? = null private var recycler: RecyclerView? = null
/**
* Subscription for the search view.
*/
private var searchViewSubscription: Subscription? = null
/** /**
* Endless loading item. * Endless loading item.
*/ */
@ -124,7 +116,7 @@ open class BrowseSourceController(bundle: Bundle) :
} }
override fun createPresenter(): BrowseSourcePresenter { override fun createPresenter(): BrowseSourcePresenter {
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY)) return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
} }
override fun createBinding(inflater: LayoutInflater) = BrowseSourceControllerBinding.inflate(inflater) override fun createBinding(inflater: LayoutInflater) = BrowseSourceControllerBinding.inflate(inflater)
@ -142,8 +134,6 @@ open class BrowseSourceController(bundle: Bundle) :
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
searchViewSubscription?.unsubscribe()
searchViewSubscription = null
adapter = null adapter = null
snack = null snack = null
recycler = null recycler = null
@ -222,31 +212,40 @@ open class BrowseSourceController(bundle: Bundle) :
val searchView = searchItem.actionView as SearchView val searchView = searchItem.actionView as SearchView
val query = presenter.query val query = presenter.query
if (!query.isBlank()) { if (query.isNotBlank()) {
searchItem.expandActionView() searchItem.expandActionView()
searchView.setQuery(query, true) searchView.setQuery(query, true)
searchView.clearFocus() searchView.clearFocus()
} }
val searchEventsObservable = searchView.queryTextChangeEvents() // val searchEventsObservable = searchView.queryTextChangeEvents()
.skip(1) // .skip(1)
.filter { router.backstack.lastOrNull()?.controller() == this@BrowseSourceController } // .filter { router.backstack.lastOrNull()?.controller() == this@BrowseSourceController }
.share() // .share()
val writingObservable = searchEventsObservable // val writingObservable = searchEventsObservable
.filter { !it.isSubmitted } // .filter { !it.isSubmitted }
.debounce(1250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) // .debounce(1250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
val submitObservable = searchEventsObservable // val submitObservable = searchEventsObservable
.filter { it.isSubmitted } // .filter { it.isSubmitted }
//
// searchViewSubscription?.unsubscribe()
// searchViewSubscription = Observable.merge(writingObservable, submitObservable)
// .map { it.queryText().toString() }
// .subscribeUntilDestroy { searchWithQuery(it) }
searchViewSubscription?.unsubscribe() setOnQueryTextChangeListener(searchView, onlyOnSubmit = true, hideKbOnSubmit = false) {
searchViewSubscription = Observable.merge(writingObservable, submitObservable) searchWithQuery(it ?: "")
.map { it.queryText().toString() } true
.subscribeUntilDestroy { searchWithQuery(it) } }
searchItem.fixExpand( searchItem.fixExpand(
onExpand = { invalidateMenuOnExpand() }, onExpand = { invalidateMenuOnExpand() },
onCollapse = { onCollapse = {
if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller() is GlobalSearchController) {
router.popController(this)
} else {
searchWithQuery("") searchWithQuery("")
}
true true
} }
) )

View File

@ -48,6 +48,7 @@ import uy.kohesive.injekt.api.get
*/ */
open class BrowseSourcePresenter( open class BrowseSourcePresenter(
sourceId: Long, sourceId: Long,
searchQuery: String? = null,
sourceManager: SourceManager = Injekt.get(), sourceManager: SourceManager = Injekt.get(),
val db: DatabaseHelper = Injekt.get(), val db: DatabaseHelper = Injekt.get(),
private val prefs: PreferencesHelper = Injekt.get(), private val prefs: PreferencesHelper = Injekt.get(),
@ -117,6 +118,10 @@ open class BrowseSourcePresenter(
private var scope = CoroutineScope(Job() + Dispatchers.IO) private var scope = CoroutineScope(Job() + Dispatchers.IO)
init {
query = searchQuery ?: ""
}
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)

View File

@ -4,6 +4,7 @@ import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.util.SparseArray import android.util.SparseArray
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.source.CatalogueSource
/** /**
* Adapter that holds the search cards. * Adapter that holds the search cards.
@ -13,6 +14,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
class GlobalSearchAdapter(val controller: GlobalSearchController) : class GlobalSearchAdapter(val controller: GlobalSearchController) :
FlexibleAdapter<GlobalSearchItem>(null, controller, true) { FlexibleAdapter<GlobalSearchItem>(null, controller, true) {
val titleClickListener: OnTitleClickListener = controller
/** /**
* Bundle where the view state of the holders is saved. * Bundle where the view state of the holders is saved.
*/ */
@ -67,6 +70,10 @@ class GlobalSearchAdapter(val controller: GlobalSearchController) :
} }
} }
interface OnTitleClickListener {
fun onTitleClick(source: CatalogueSource)
}
private companion object { private companion object {
const val HOLDER_BUNDLE_KEY = "holder_bundle" const val HOLDER_BUNDLE_KEY = "holder_bundle"
} }

View File

@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.util.addOrRemoveToFavorites import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
import eu.kanade.tachiyomi.util.view.activityBinding import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.scrollViewWith
@ -33,9 +34,10 @@ import uy.kohesive.injekt.injectLazy
*/ */
open class GlobalSearchController( open class GlobalSearchController(
protected val initialQuery: String? = null, protected val initialQuery: String? = null,
protected val extensionFilter: String? = null val extensionFilter: String? = null
) : NucleusController<SourceGlobalSearchControllerBinding, GlobalSearchPresenter>(), ) : NucleusController<SourceGlobalSearchControllerBinding, GlobalSearchPresenter>(),
FloatingSearchInterface, FloatingSearchInterface,
GlobalSearchAdapter.OnTitleClickListener,
GlobalSearchCardAdapter.OnMangaClickListener { GlobalSearchCardAdapter.OnMangaClickListener {
/** /**
@ -82,6 +84,11 @@ open class GlobalSearchController(
return GlobalSearchPresenter(initialQuery, extensionFilter) return GlobalSearchPresenter(initialQuery, extensionFilter)
} }
override fun onTitleClick(source: CatalogueSource) {
preferences.lastUsedCatalogueSource().set(source.id)
router.pushController(BrowseSourceController(source, presenter.query).withFadeTransaction())
}
/** /**
* Called when manga in global search is clicked, opens manga. * Called when manga in global search is clicked, opens manga.
* *
@ -180,6 +187,9 @@ open class GlobalSearchController(
customTitle = view.context?.getString(R.string.loading) customTitle = view.context?.getString(R.string.loading)
setTitle() setTitle()
} }
binding.recycler.post {
binding.recycler.scrollToPosition(0)
}
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {

View File

@ -1,9 +1,13 @@
package eu.kanade.tachiyomi.ui.source.global_search package eu.kanade.tachiyomi.ui.source.global_search
import android.view.View import android.view.View
import androidx.core.view.isVisible
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.SourceGlobalSearchControllerCardBinding import eu.kanade.tachiyomi.databinding.SourceGlobalSearchControllerCardBinding
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.ui.migration.SearchController
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
@ -30,6 +34,15 @@ class GlobalSearchHolder(view: View, val adapter: GlobalSearchAdapter) :
binding.recycler.layoutManager = binding.recycler.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(view.context, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, false) androidx.recyclerview.widget.LinearLayoutManager(view.context, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, false)
binding.recycler.adapter = mangaAdapter binding.recycler.adapter = mangaAdapter
binding.titleMoreIcon.isVisible = adapter.controller !is SearchController && adapter.controller.extensionFilter == null
if (binding.titleMoreIcon.isVisible) {
binding.titleWrapper.setOnClickListener {
adapter.getItem(bindingAdapterPosition)?.let {
adapter.titleClickListener.onTitleClick(it.source)
}
}
}
} }
/** /**
@ -46,6 +59,8 @@ class GlobalSearchHolder(view: View, val adapter: GlobalSearchAdapter) :
// Set Title with country code if available. // Set Title with country code if available.
binding.title.text = titlePrefix + source.name + langSuffix binding.title.text = titlePrefix + source.name + langSuffix
binding.subtitle.isVisible = source !is LocalSource
binding.subtitle.text = LocaleHelper.getDisplayName(source.lang)
when { when {
results == null -> { results == null -> {

View File

@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
@ -25,6 +24,7 @@ import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date
/** /**
* Presenter of [GlobalSearchController] * Presenter of [GlobalSearchController]
@ -32,7 +32,7 @@ import uy.kohesive.injekt.injectLazy
* *
* @param sourceManager manages the different sources. * @param sourceManager manages the different sources.
* @param db manages the database calls. * @param db manages the database calls.
* @param preferencesHelper manages the preference calls. * @param preferences manages the preference calls.
*/ */
open class GlobalSearchPresenter( open class GlobalSearchPresenter(
private val initialQuery: String? = "", private val initialQuery: String? = "",
@ -40,7 +40,7 @@ open class GlobalSearchPresenter(
private val sourcesToUse: List<CatalogueSource>? = null, private val sourcesToUse: List<CatalogueSource>? = null,
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val db: DatabaseHelper = Injekt.get(), val db: DatabaseHelper = Injekt.get(),
private val preferencesHelper: PreferencesHelper = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get() private val coverCache: CoverCache = Injekt.get()
) : BasePresenter<GlobalSearchController>() { ) : BasePresenter<GlobalSearchController>() {
@ -60,6 +60,8 @@ open class GlobalSearchPresenter(
*/ */
private var fetchSourcesSubscription: Subscription? = null private var fetchSourcesSubscription: Subscription? = null
private var loadTime = hashMapOf<Long, Long>()
/** /**
* Subject which fetches image of given manga. * Subject which fetches image of given manga.
*/ */
@ -104,16 +106,16 @@ open class GlobalSearchPresenter(
* @return list containing enabled sources. * @return list containing enabled sources.
*/ */
protected open fun getEnabledSources(): List<CatalogueSource> { protected open fun getEnabledSources(): List<CatalogueSource> {
val languages = preferencesHelper.enabledLanguages().get() val languages = preferences.enabledLanguages().get()
val hiddenCatalogues = preferencesHelper.hiddenSources().get() val hiddenCatalogues = preferences.hiddenSources().get()
val pinnedCatalogues = preferencesHelper.pinnedCatalogues().getOrDefault() val pinnedCatalogues = preferences.pinnedCatalogues().get()
val list = sourceManager.getCatalogueSources() val list = sourceManager.getCatalogueSources()
.filter { it.lang in languages } .filter { it.lang in languages }
.filterNot { it.id.toString() in hiddenCatalogues } .filterNot { it.id.toString() in hiddenCatalogues }
.sortedBy { "(${it.lang}) ${it.name}" } .sortedBy { "(${it.lang}) ${it.name}" }
return if (preferencesHelper.onlySearchPinned().get()) { return if (preferences.onlySearchPinned().get()) {
list.filter { it.id.toString() in pinnedCatalogues } list.filter { it.id.toString() in pinnedCatalogues }
} else { } else {
list.sortedBy { it.id.toString() !in pinnedCatalogues } list.sortedBy { it.id.toString() !in pinnedCatalogues }
@ -176,6 +178,8 @@ open class GlobalSearchPresenter(
val initialItems = sources.map { createCatalogueSearchItem(it, null) } val initialItems = sources.map { createCatalogueSearchItem(it, null) }
var items = initialItems var items = initialItems
val pinnedSourceIds = preferences.pinnedCatalogues().get()
fetchSourcesSubscription?.unsubscribe() fetchSourcesSubscription?.unsubscribe()
fetchSourcesSubscription = Observable.from(sources).flatMap( fetchSourcesSubscription = Observable.from(sources).flatMap(
{ source -> { source ->
@ -197,6 +201,9 @@ open class GlobalSearchPresenter(
} // Convert to local manga. } // Convert to local manga.
.doOnNext { fetchImage(it, source) } // Load manga covers. .doOnNext { fetchImage(it, source) } // Load manga covers.
.map { .map {
if (it.isNotEmpty() && !loadTime.containsKey(source.id)) {
loadTime[source.id] = Date().time
}
createCatalogueSearchItem( createCatalogueSearchItem(
source, source,
it.map { GlobalSearchMangaItem(it) } it.map { GlobalSearchMangaItem(it) }
@ -204,10 +211,22 @@ open class GlobalSearchPresenter(
} }
}, },
5 5
).observeOn(AndroidSchedulers.mainThread()) )
.observeOn(AndroidSchedulers.mainThread())
// Update matching source with the obtained results // Update matching source with the obtained results
.map { result -> .map { result ->
items.map { item -> if (item.source == result.source) result else item } items
.map { item -> if (item.source == result.source) result else item }
.sortedWith(
compareBy(
// Bubble up sources that actually have results
{ it.results.isNullOrEmpty() },
// Same as initial sort, i.e. pinned first then alphabetically
{ it.source.id.toString() !in pinnedSourceIds },
{ loadTime[it.source.id] ?: 0L },
{ "${it.source.name.toLowerCase()} (${it.source.lang})" }
)
)
} }
// Update current state // Update current state
.doOnNext { items = it } .doOnNext { items = it }

View File

@ -42,6 +42,7 @@ import kotlin.random.Random
fun Controller.setOnQueryTextChangeListener( fun Controller.setOnQueryTextChangeListener(
searchView: SearchView, searchView: SearchView,
onlyOnSubmit: Boolean = false, onlyOnSubmit: Boolean = false,
hideKbOnSubmit: Boolean = true,
f: (text: String?) -> Boolean f: (text: String?) -> Boolean
) { ) {
searchView.setOnQueryTextListener( searchView.setOnQueryTextListener(
@ -57,10 +58,12 @@ fun Controller.setOnQueryTextChangeListener(
override fun onQueryTextSubmit(query: String?): Boolean { override fun onQueryTextSubmit(query: String?): Boolean {
if (router.backstack.lastOrNull()?.controller() == this@setOnQueryTextChangeListener) { if (router.backstack.lastOrNull()?.controller() == this@setOnQueryTextChangeListener) {
if (hideKbOnSubmit) {
val imm = val imm =
activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
?: return f(query) ?: return f(query)
imm.hideSoftInputFromWindow(searchView.windowToken, 0) imm.hideSoftInputFromWindow(searchView.windowToken, 0)
}
return f(query) return f(query)
} }
return true return true

View File

@ -6,19 +6,57 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/title_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="?attr/selectableItemBackground">
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
style="@style/TextAppearance.Regular.SubHeading" style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="16dp" android:layout_marginStart="16dp"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:paddingBottom="0dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/subtitle"
app:layout_constraintEnd_toStartOf="@id/title_more_icon"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Title" /> tools:text="Title" />
<TextView
android:id="@+id/subtitle"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:maxLines="1"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/title_more_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="English"
tools:visibility="visible" />
<ImageView
android:id="@+id/title_more_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/more"
android:padding="@dimen/material_component_text_fields_padding_above_and_below_label"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_arrow_forward_24dp"
app:tint="?android:attr/textColorPrimary" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView <TextView
android:id="@+id/no_results" android:id="@+id/no_results"
style="@style/TextAppearance.Regular.Body1" style="@style/TextAppearance.Regular.Body1"
@ -34,7 +72,7 @@
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" tools:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintTop_toBottomOf="@id/title_wrapper"
android:text="@string/no_results_found"/> android:text="@string/no_results_found"/>
<FrameLayout <FrameLayout
@ -44,7 +82,8 @@
android:minHeight="208dp" android:minHeight="208dp"
android:paddingTop="2dp" android:paddingTop="2dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/title" android:layout_marginTop="4dp"
app:layout_constraintTop_toBottomOf="@id/title_wrapper"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constrainedHeight="true" app:layout_constrainedHeight="true"