Global search

pull/26/head
Koitharu 6 years ago
parent ff56f5a343
commit 3539e6a892

@ -4,16 +4,31 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import java.util.*
class MangaSearchRepository : KoinComponent { 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) val sources = MangaProviderFactory.getSources(false)
for (source in sources) { val lists = EnumMap<MangaSource, List<Manga>>(MangaSource::class.java)
val provider = MangaProviderFactory.create(source) var i = 0
val list = provider.getList(0, query, SortOrder.POPULARITY) while (true) {
emit(list.take(4)) 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.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem
import moxy.ktx.moxyPresenter import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaFilter import org.koitharu.kotatsu.core.model.MangaFilter
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.list.MangaListFragment 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 import org.koitharu.kotatsu.utils.ext.withArgs
class RemoteListFragment : MangaListFragment<Unit>() { class RemoteListFragment : MangaListFragment<Unit>() {
@ -31,12 +32,17 @@ class RemoteListFragment : MangaListFragment<Unit>() {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.opt_remote, menu) inflater.inflate(R.menu.opt_remote, menu)
menu.findItem(R.id.action_search)?.let { menuItem ->
SearchHelper.setupSearchView(menuItem, source)
}
super.onCreateOptionsMenu(menu, inflater) 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 { companion object {
private const val ARG_SOURCE = "provider" private const val ARG_SOURCE = "provider"

@ -4,38 +4,60 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable 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.R
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.common.BaseActivity 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search) setContentView(R.layout.activity_search)
val source = intent.getParcelableExtra<MangaSource>(EXTRA_SOURCE) source = intent.getParcelableExtra(EXTRA_SOURCE) ?: run {
val query = intent.getStringExtra(EXTRA_QUERY)
if (source == null || query == null) {
finish() finish()
return return
} }
val query = intent.getStringExtra(EXTRA_QUERY)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
title = query searchView.queryHint = getString(R.string.search_on_s, source.title)
supportActionBar?.subtitle = getString(R.string.search_results_on_s, source.title) searchView.suggestionsAdapter = MangaSuggestionsProvider.getSuggestionAdapter(this)
supportFragmentManager searchView.setOnSuggestionListener(SearchHelper.SuggestionListener(searchView))
.beginTransaction() searchView.setOnQueryTextListener(this)
.replace(R.id.container, SearchFragment.newInstance(source, query))
.commit() 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 { companion object {
private const val EXTRA_SOURCE = "source" private const val EXTRA_SOURCE = "source"
private const val EXTRA_QUERY = "query" 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) Intent(context, SearchActivity::class.java)
.putExtra(EXTRA_SOURCE, source as Parcelable) .putExtra(EXTRA_SOURCE, source as Parcelable)
.putExtra(EXTRA_QUERY, query) .putExtra(EXTRA_QUERY, query)

@ -6,22 +6,11 @@ import android.database.Cursor
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.search.global.GlobalSearchActivity import org.koitharu.kotatsu.ui.search.global.GlobalSearchActivity
import org.koitharu.kotatsu.utils.ext.safe import org.koitharu.kotatsu.utils.ext.safe
object SearchHelper { 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 @JvmStatic
fun setupSearchView(menuItem: MenuItem) { fun setupSearchView(menuItem: MenuItem) {
val view = menuItem.actionView as? SearchView ?: return val view = menuItem.actionView as? SearchView ?: return
@ -32,16 +21,12 @@ object SearchHelper {
view.setOnSuggestionListener(SuggestionListener(view)) 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 { SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean { override fun onQueryTextSubmit(query: String?): Boolean {
return if (!query.isNullOrBlank()) { return if (!query.isNullOrBlank()) {
if (source == null) { context.startActivity(GlobalSearchActivity.newIntent(context, query.trim()))
context.startActivity(GlobalSearchActivity.newIntent(context, query.trim()))
} else {
context.startActivity(SearchActivity.newIntent(context, source, query.trim()))
}
MangaSuggestionsProvider.saveQuery(context, query) MangaSuggestionsProvider.saveQuery(context, query)
true true
} else false } else false
@ -50,7 +35,7 @@ object SearchHelper {
override fun onQueryTextChange(newText: String?) = false override fun onQueryTextChange(newText: String?) = false
} }
private class SuggestionListener(private val view: SearchView) : class SuggestionListener(private val view: SearchView) :
SearchView.OnSuggestionListener { SearchView.OnSuggestionListener {
override fun onSuggestionSelect(position: Int) = false override fun onSuggestionSelect(position: Int) = false

@ -10,7 +10,7 @@ class GlobalSearchActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search) setContentView(R.layout.activity_search_global)
val query = intent.getStringExtra(EXTRA_QUERY) val query = intent.getStringExtra(EXTRA_QUERY)
if (query == null) { if (query == null) {

@ -34,6 +34,7 @@ class GlobalSearchPresenter : BasePresenter<MangaListView<Unit>>() {
} }
} }
.onFirst { .onFirst {
viewState.onListChanged(emptyList())
viewState.onLoadingStateChanged(isLoading = false) viewState.onLoadingStateChanged(isLoading = false)
} }
.onEmpty { .onEmpty {

@ -9,8 +9,8 @@
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:background="?colorPrimary" android:background="?colorPrimary"
android:fitsSystemWindows="true"
android:theme="@style/AppToolbarTheme"> android:theme="@style/AppToolbarTheme">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
@ -18,7 +18,18 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?android:actionBarSize" android:layout_height="?android:actionBarSize"
app:layout_scrollFlags="scroll|enterAlways" 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> </com.google.android.material.appbar.AppBarLayout>

@ -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 <item
android:id="@+id/action_search_internal" android:id="@+id/action_search_internal"
android:icon="@drawable/ic_search" android:orderInCategory="26"
android:orderInCategory="1"
android:title="@string/search" android:title="@string/search"
app:actionViewClass="androidx.appcompat.widget.SearchView" app:showAsAction="never" />
app:showAsAction="always|collapseActionView" />
</menu> </menu>

@ -78,6 +78,7 @@
<string name="read_mode">Режим чтения</string> <string name="read_mode">Режим чтения</string>
<string name="grid_size">Размер таблицы</string> <string name="grid_size">Размер таблицы</string>
<string name="search_results_on_s">Результаты поиска по %s</string> <string name="search_results_on_s">Результаты поиска по %s</string>
<string name="search_on_s">Поиск по %s</string>
<string name="delete_manga">Удалить мангу</string> <string name="delete_manga">Удалить мангу</string>
<string name="text_delete_local_manga">Вы уверены, что хотите удалить \"%s\" с устройства? \nЭто действие нельзя будет отменить.</string> <string name="text_delete_local_manga">Вы уверены, что хотите удалить \"%s\" с устройства? \nЭто действие нельзя будет отменить.</string>
<string name="reader_settings">Настройки чтения</string> <string name="reader_settings">Настройки чтения</string>

@ -79,6 +79,7 @@
<string name="read_mode">Read mode</string> <string name="read_mode">Read mode</string>
<string name="grid_size">Grid size</string> <string name="grid_size">Grid size</string>
<string name="search_results_on_s">Search results on %s</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="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="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> <string name="reader_settings">Reader settings</string>

Loading…
Cancel
Save