Global search
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package org.koitharu.kotatsu.domain
|
||||
|
||||
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.SortOrder
|
||||
|
||||
class MangaSearchRepository : KoinComponent {
|
||||
|
||||
fun globalSearch(query: String): 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.koitharu.kotatsu.ui.common.list
|
||||
|
||||
import android.view.ViewGroup
|
||||
|
||||
class ProgressBarAdapter : BaseRecyclerAdapter<Boolean, Unit>() {
|
||||
|
||||
var isVisible: Boolean
|
||||
get() = dataSet.isNotEmpty()
|
||||
set(value) {
|
||||
if (value == dataSet.isEmpty()) {
|
||||
if (value) {
|
||||
appendItem(true)
|
||||
} else {
|
||||
removeItemAt(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getExtra(item: Boolean, position: Int) = Unit
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup) = ProgressBarHolder(parent)
|
||||
|
||||
override fun onGetItemId(item: Boolean) = 1L
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.koitharu.kotatsu.ui.common.list
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.item_progress.*
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class ProgressBarHolder(parent: ViewGroup) :
|
||||
BaseViewHolder<Boolean, Unit>(parent, R.layout.item_progress) {
|
||||
|
||||
override fun onBind(data: Boolean, extra: Unit) {
|
||||
progressBar.visibility = if (data) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.koitharu.kotatsu.ui.list.remote.RemoteListFragment
|
||||
import org.koitharu.kotatsu.ui.list.tracklogs.FeedFragment
|
||||
import org.koitharu.kotatsu.ui.reader.ReaderActivity
|
||||
import org.koitharu.kotatsu.ui.reader.ReaderState
|
||||
import org.koitharu.kotatsu.ui.search.SearchHelper
|
||||
import org.koitharu.kotatsu.ui.settings.AppUpdateService
|
||||
import org.koitharu.kotatsu.ui.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.ui.tracker.TrackWorker
|
||||
@@ -91,8 +92,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
drawerToggle.onConfigurationChanged(newConfig)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.opt_main, menu)
|
||||
menu.findItem(R.id.action_search)?.let { menuItem ->
|
||||
SearchHelper.setupSearchView(menuItem)
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 {
|
||||
@@ -21,12 +22,26 @@ object SearchHelper {
|
||||
view.setOnSuggestionListener(SuggestionListener(view))
|
||||
}
|
||||
|
||||
private class QueryListener(private val context: Context, private val source: MangaSource) :
|
||||
@JvmStatic
|
||||
fun setupSearchView(menuItem: MenuItem) {
|
||||
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))
|
||||
view.setOnSuggestionListener(SuggestionListener(view))
|
||||
}
|
||||
|
||||
private class QueryListener(private val context: Context, private val source: MangaSource? = null) :
|
||||
SearchView.OnQueryTextListener {
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return if (!query.isNullOrBlank()) {
|
||||
context.startActivity(SearchActivity.newIntent(context, source, query.trim()))
|
||||
if (source == null) {
|
||||
context.startActivity(GlobalSearchActivity.newIntent(context, query.trim()))
|
||||
} else {
|
||||
context.startActivity(SearchActivity.newIntent(context, source, query.trim()))
|
||||
}
|
||||
MangaSuggestionsProvider.saveQuery(context, query)
|
||||
true
|
||||
} else false
|
||||
|
||||
@@ -15,13 +15,6 @@ import org.koitharu.kotatsu.ui.list.MangaListView
|
||||
@InjectViewState
|
||||
class SearchPresenter : BasePresenter<MangaListView<Unit>>() {
|
||||
|
||||
private lateinit var sources: Array<MangaSource>
|
||||
|
||||
override fun onFirstViewAttach() {
|
||||
sources = MangaSource.values()
|
||||
super.onFirstViewAttach()
|
||||
}
|
||||
|
||||
fun loadList(source: MangaSource, query: String, offset: Int) {
|
||||
presenterScope.launch {
|
||||
viewState.onLoadingStateChanged(true)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.koitharu.kotatsu.ui.search.global
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.ui.common.BaseActivity
|
||||
|
||||
class GlobalSearchActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_search)
|
||||
val query = intent.getStringExtra(EXTRA_QUERY)
|
||||
|
||||
if (query == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
title = query
|
||||
supportActionBar?.subtitle = getString(R.string.search_results)
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, GlobalSearchFragment.newInstance(query))
|
||||
.commit()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_QUERY = "query"
|
||||
|
||||
fun newIntent(context: Context, query: String) =
|
||||
Intent(context, GlobalSearchActivity::class.java)
|
||||
.putExtra(EXTRA_QUERY, query)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.koitharu.kotatsu.ui.search.global
|
||||
|
||||
import moxy.ktx.moxyPresenter
|
||||
import org.koitharu.kotatsu.ui.list.MangaListFragment
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
|
||||
class GlobalSearchFragment: MangaListFragment<Unit>() {
|
||||
|
||||
private val presenter by moxyPresenter(factory = ::GlobalSearchPresenter)
|
||||
|
||||
private val query by stringArg(ARG_QUERY)
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) {
|
||||
if (offset == 0) {
|
||||
presenter.startSearch(query.orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
return query
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARG_QUERY = "query"
|
||||
|
||||
fun newInstance(query: String) = GlobalSearchFragment().withArgs(1) {
|
||||
putString(ARG_QUERY, query)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.koitharu.kotatsu.ui.search.global
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.onEmpty
|
||||
import kotlinx.coroutines.launch
|
||||
import moxy.presenterScope
|
||||
import org.koitharu.kotatsu.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.ui.common.BasePresenter
|
||||
import org.koitharu.kotatsu.ui.list.MangaListView
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
import java.io.IOException
|
||||
|
||||
class GlobalSearchPresenter : BasePresenter<MangaListView<Unit>>() {
|
||||
|
||||
private lateinit var repository: MangaSearchRepository
|
||||
|
||||
override fun onFirstViewAttach() {
|
||||
repository = MangaSearchRepository()
|
||||
super.onFirstViewAttach()
|
||||
}
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun startSearch(query: String) {
|
||||
presenterScope.launch {
|
||||
viewState.onLoadingStateChanged(isLoading = true)
|
||||
repository.globalSearch(query)
|
||||
.flowOn(Dispatchers.IO)
|
||||
.catch { e ->
|
||||
if (e is IOException) {
|
||||
viewState.onError(e)
|
||||
}
|
||||
}
|
||||
.onFirst {
|
||||
viewState.onLoadingStateChanged(isLoading = false)
|
||||
}
|
||||
.onEmpty {
|
||||
viewState.onListChanged(emptyList())
|
||||
viewState.onLoadingStateChanged(isLoading = false)
|
||||
}
|
||||
.collect {
|
||||
viewState.onListAppended(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
@@ -25,4 +27,14 @@ suspend fun Call.await() = suspendCancellableCoroutine<Response> { cont ->
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> {
|
||||
var isFirstCall = true
|
||||
return onEach {
|
||||
if (isFirstCall) {
|
||||
action(it)
|
||||
isFirstCall = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user