mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 18:11:53 +01:00
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:
parent
48a4b3091f
commit
c9388f6633
@ -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)
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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 = {
|
||||||
searchWithQuery("")
|
if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller() is GlobalSearchController) {
|
||||||
|
router.popController(this)
|
||||||
|
} else {
|
||||||
|
searchWithQuery("")
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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 -> {
|
||||||
|
@ -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 }
|
||||||
|
@ -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) {
|
||||||
val imm =
|
if (hideKbOnSubmit) {
|
||||||
activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
val imm =
|
||||||
?: return f(query)
|
activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||||
imm.hideSoftInputFromWindow(searchView.windowToken, 0)
|
?: return f(query)
|
||||||
|
imm.hideSoftInputFromWindow(searchView.windowToken, 0)
|
||||||
|
}
|
||||||
return f(query)
|
return f(query)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -6,18 +6,56 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TextView
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/title"
|
android:id="@+id/title_wrapper"
|
||||||
style="@style/TextAppearance.Regular.SubHeading"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="0dp"
|
|
||||||
android:paddingBottom="0dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Title" />
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
style="@style/TextAppearance.Regular.SubHeading"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintStart_toStartOf="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" />
|
||||||
|
|
||||||
|
<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"
|
||||||
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user