Global search

This commit is contained in:
Koitharu
2020-05-30 11:18:14 +03:00
parent ff56f5a343
commit 3539e6a892
11 changed files with 118 additions and 47 deletions

View File

@@ -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
}
}
}
}

View File

@@ -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"

View File

@@ -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)

View File

@@ -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

View File

@@ -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) {

View File

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

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>