Global search
This commit is contained in:
@@ -4,16 +4,31 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.model.SortOrder
|
||||
import java.util.*
|
||||
|
||||
class MangaSearchRepository : KoinComponent {
|
||||
|
||||
fun globalSearch(query: String): Flow<List<Manga>> = flow {
|
||||
fun globalSearch(query: String, batchSize: Int = 4): Flow<List<Manga>> = flow {
|
||||
val sources = MangaProviderFactory.getSources(false)
|
||||
for (source in sources) {
|
||||
val provider = MangaProviderFactory.create(source)
|
||||
val list = provider.getList(0, query, SortOrder.POPULARITY)
|
||||
emit(list.take(4))
|
||||
val lists = EnumMap<MangaSource, List<Manga>>(MangaSource::class.java)
|
||||
var i = 0
|
||||
while (true) {
|
||||
var isEmitted = false
|
||||
for (source in sources) {
|
||||
val list = lists.getOrPut(source) {
|
||||
MangaProviderFactory.create(source).getList(0, query, SortOrder.POPULARITY)
|
||||
}
|
||||
if (i < list.size) {
|
||||
emit(list.subList(i, (i + batchSize).coerceAtMost(list.lastIndex)))
|
||||
isEmitted = true
|
||||
}
|
||||
}
|
||||
i += batchSize
|
||||
if (!isEmitted) {
|
||||
return@flow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,13 @@ package org.koitharu.kotatsu.ui.list.remote
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import moxy.ktx.moxyPresenter
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.ui.list.MangaListFragment
|
||||
import org.koitharu.kotatsu.ui.search.SearchHelper
|
||||
import org.koitharu.kotatsu.ui.search.SearchActivity
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class RemoteListFragment : MangaListFragment<Unit>() {
|
||||
@@ -31,12 +32,17 @@ class RemoteListFragment : MangaListFragment<Unit>() {
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.opt_remote, menu)
|
||||
menu.findItem(R.id.action_search)?.let { menuItem ->
|
||||
SearchHelper.setupSearchView(menuItem, source)
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.action_search_internal -> {
|
||||
context?.startActivity(SearchActivity.newIntent(requireContext(), source, null))
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARG_SOURCE = "provider"
|
||||
|
||||
@@ -4,38 +4,60 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import kotlinx.android.synthetic.main.activity_search.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.ui.common.BaseActivity
|
||||
import org.koitharu.kotatsu.utils.ext.showKeyboard
|
||||
|
||||
class SearchActivity : BaseActivity() {
|
||||
class SearchActivity : BaseActivity(), SearchView.OnQueryTextListener {
|
||||
|
||||
private lateinit var source: MangaSource
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_search)
|
||||
val source = intent.getParcelableExtra<MangaSource>(EXTRA_SOURCE)
|
||||
val query = intent.getStringExtra(EXTRA_QUERY)
|
||||
|
||||
if (source == null || query == null) {
|
||||
source = intent.getParcelableExtra(EXTRA_SOURCE) ?: run {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
val query = intent.getStringExtra(EXTRA_QUERY)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
title = query
|
||||
supportActionBar?.subtitle = getString(R.string.search_results_on_s, source.title)
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, SearchFragment.newInstance(source, query))
|
||||
.commit()
|
||||
searchView.queryHint = getString(R.string.search_on_s, source.title)
|
||||
searchView.suggestionsAdapter = MangaSuggestionsProvider.getSuggestionAdapter(this)
|
||||
searchView.setOnSuggestionListener(SearchHelper.SuggestionListener(searchView))
|
||||
searchView.setOnQueryTextListener(this)
|
||||
|
||||
if (query.isNullOrBlank()) {
|
||||
searchView.requestFocus()
|
||||
searchView.showKeyboard()
|
||||
} else {
|
||||
searchView.setQuery(query, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return if (!query.isNullOrBlank()) {
|
||||
title = query
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, SearchFragment.newInstance(source, query))
|
||||
.commit()
|
||||
searchView.clearFocus()
|
||||
MangaSuggestionsProvider.saveQuery(this, query)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?) = false
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_SOURCE = "source"
|
||||
private const val EXTRA_QUERY = "query"
|
||||
|
||||
fun newIntent(context: Context, source: MangaSource, query: String) =
|
||||
fun newIntent(context: Context, source: MangaSource, query: String?) =
|
||||
Intent(context, SearchActivity::class.java)
|
||||
.putExtra(EXTRA_SOURCE, source as Parcelable)
|
||||
.putExtra(EXTRA_QUERY, query)
|
||||
|
||||
@@ -6,22 +6,11 @@ import android.database.Cursor
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.ui.search.global.GlobalSearchActivity
|
||||
import org.koitharu.kotatsu.utils.ext.safe
|
||||
|
||||
object SearchHelper {
|
||||
|
||||
@JvmStatic
|
||||
fun setupSearchView(menuItem: MenuItem, source: MangaSource) {
|
||||
val view = menuItem.actionView as? SearchView ?: return
|
||||
val context = view.context
|
||||
view.queryHint = context.getString(R.string.search_manga)
|
||||
view.suggestionsAdapter = MangaSuggestionsProvider.getSuggestionAdapter(context)
|
||||
view.setOnQueryTextListener(QueryListener(context, source))
|
||||
view.setOnSuggestionListener(SuggestionListener(view))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setupSearchView(menuItem: MenuItem) {
|
||||
val view = menuItem.actionView as? SearchView ?: return
|
||||
@@ -32,16 +21,12 @@ object SearchHelper {
|
||||
view.setOnSuggestionListener(SuggestionListener(view))
|
||||
}
|
||||
|
||||
private class QueryListener(private val context: Context, private val source: MangaSource? = null) :
|
||||
private class QueryListener(private val context: Context) :
|
||||
SearchView.OnQueryTextListener {
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return if (!query.isNullOrBlank()) {
|
||||
if (source == null) {
|
||||
context.startActivity(GlobalSearchActivity.newIntent(context, query.trim()))
|
||||
} else {
|
||||
context.startActivity(SearchActivity.newIntent(context, source, query.trim()))
|
||||
}
|
||||
context.startActivity(GlobalSearchActivity.newIntent(context, query.trim()))
|
||||
MangaSuggestionsProvider.saveQuery(context, query)
|
||||
true
|
||||
} else false
|
||||
@@ -50,7 +35,7 @@ object SearchHelper {
|
||||
override fun onQueryTextChange(newText: String?) = false
|
||||
}
|
||||
|
||||
private class SuggestionListener(private val view: SearchView) :
|
||||
class SuggestionListener(private val view: SearchView) :
|
||||
SearchView.OnSuggestionListener {
|
||||
|
||||
override fun onSuggestionSelect(position: Int) = false
|
||||
|
||||
@@ -10,7 +10,7 @@ class GlobalSearchActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_search)
|
||||
setContentView(R.layout.activity_search_global)
|
||||
val query = intent.getStringExtra(EXTRA_QUERY)
|
||||
|
||||
if (query == null) {
|
||||
|
||||
@@ -34,6 +34,7 @@ class GlobalSearchPresenter : BasePresenter<MangaListView<Unit>>() {
|
||||
}
|
||||
}
|
||||
.onFirst {
|
||||
viewState.onListChanged(emptyList())
|
||||
viewState.onLoadingStateChanged(isLoading = false)
|
||||
}
|
||||
.onEmpty {
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:background="?colorPrimary"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@style/AppToolbarTheme">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
@@ -18,7 +18,18 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:actionBarSize"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:popupTheme="@style/AppPopupTheme" />
|
||||
app:popupTheme="@style/AppPopupTheme">
|
||||
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/searchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:iconifiedByDefault="false"
|
||||
app:searchHintIcon="@null"
|
||||
app:searchIcon="@null" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
31
app/src/main/res/layout/activity_search_global.xml
Normal file
31
app/src/main/res/layout/activity_search_global.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:background="?colorPrimary"
|
||||
android:theme="@style/AppToolbarTheme">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:actionBarSize"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:popupTheme="@style/AppPopupTheme" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
<item
|
||||
android:id="@+id/action_search_internal"
|
||||
android:icon="@drawable/ic_search"
|
||||
android:orderInCategory="1"
|
||||
android:orderInCategory="26"
|
||||
android:title="@string/search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always|collapseActionView" />
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
@@ -78,6 +78,7 @@
|
||||
<string name="read_mode">Режим чтения</string>
|
||||
<string name="grid_size">Размер таблицы</string>
|
||||
<string name="search_results_on_s">Результаты поиска по %s</string>
|
||||
<string name="search_on_s">Поиск по %s</string>
|
||||
<string name="delete_manga">Удалить мангу</string>
|
||||
<string name="text_delete_local_manga">Вы уверены, что хотите удалить \"%s\" с устройства? \nЭто действие нельзя будет отменить.</string>
|
||||
<string name="reader_settings">Настройки чтения</string>
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
<string name="read_mode">Read mode</string>
|
||||
<string name="grid_size">Grid size</string>
|
||||
<string name="search_results_on_s">Search results on %s</string>
|
||||
<string name="search_on_s">Search on %s</string>
|
||||
<string name="delete_manga">Delete manga</string>
|
||||
<string name="text_delete_local_manga">Do you really want to delete \"%s\" from your phone\'s local storage? \nThis operation cannot be undone.</string>
|
||||
<string name="reader_settings">Reader settings</string>
|
||||
|
||||
Reference in New Issue
Block a user