Fixes for browse + latest page

This commit is contained in:
Jobobby04 2021-02-24 16:18:13 -05:00
parent 079ca1d0b3
commit 0185d5f7d6
4 changed files with 130 additions and 196 deletions

View File

@ -1,140 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.index
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.IndexAdapterBinding
/**
* Adapter that holds the search cards.
*
* @param controller instance of [IndexController].
*/
class IndexAdapter(val controller: IndexController) :
RecyclerView.Adapter<IndexAdapter.ViewHolder>() {
val clickListener: ClickListener = controller
private lateinit var binding: IndexAdapterBinding
var holder: IndexAdapter.ViewHolder? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IndexAdapter.ViewHolder {
binding = IndexAdapterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding.root)
}
override fun onBindViewHolder(holder: IndexAdapter.ViewHolder, position: Int) {
this.holder = holder
holder.bindBrowse(null)
holder.bindLatest(null)
}
// stores and recycles views as they are scrolled off screen
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val latestAdapter = IndexCardAdapter(controller)
private var latestLastBoundResults: List<IndexCardItem>? = null
private val browseAdapter = IndexCardAdapter(controller)
private var browseLastBoundResults: List<IndexCardItem>? = null
init {
binding.browseBarWrapper.setOnClickListener {
clickListener.onBrowseClick()
}
binding.latestBarWrapper.setOnClickListener {
clickListener.onLatestClick()
}
binding.latestRecycler.layoutManager = LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
binding.latestRecycler.adapter = latestAdapter
binding.browseRecycler.layoutManager = LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
binding.browseRecycler.adapter = browseAdapter
}
fun bindLatest(latestResults: List<IndexCardItem>?) {
when {
latestResults == null -> {
binding.latestProgress.isVisible = true
showLatestResultsHolder()
}
latestResults.isEmpty() -> {
binding.latestProgress.isVisible = false
showLatestNoResults()
}
else -> {
binding.latestProgress.isVisible = false
showLatestResultsHolder()
}
}
if (latestResults !== latestLastBoundResults) {
latestAdapter.updateDataSet(latestResults)
latestLastBoundResults = latestResults
}
}
fun bindBrowse(browseResults: List<IndexCardItem>?) {
when {
browseResults == null -> {
binding.browseProgress.isVisible = true
showBrowseResultsHolder()
}
browseResults.isEmpty() -> {
binding.browseProgress.isVisible = false
showBrowseNoResults()
}
else -> {
binding.browseProgress.isVisible = false
showBrowseResultsHolder()
}
}
if (browseResults !== browseLastBoundResults) {
browseAdapter.updateDataSet(browseResults)
browseLastBoundResults = browseResults
}
}
private fun showLatestResultsHolder() {
binding.latestNoResultsFound.isVisible = false
}
private fun showLatestNoResults() {
binding.latestNoResultsFound.isVisible = true
}
private fun showBrowseResultsHolder() {
binding.browseNoResultsFound.isVisible = false
}
private fun showBrowseNoResults() {
binding.browseNoResultsFound.isVisible = true
}
fun setLatestImage(manga: Manga) {
latestAdapter.allBoundViewHolders.forEach {
if (it !is IndexCardHolder) return@forEach
if (latestAdapter.getItem(it.bindingAdapterPosition)?.manga?.id != manga.id) return@forEach
it.setImage(manga)
}
}
fun setBrowseImage(manga: Manga) {
browseAdapter.allBoundViewHolders.forEach {
if (it !is IndexCardHolder) return@forEach
if (browseAdapter.getItem(it.bindingAdapterPosition)?.manga?.id != manga.id) return@forEach
it.setImage(manga)
}
}
}
interface ClickListener {
fun onBrowseClick(search: String? = null, filters: String? = null)
fun onLatestClick()
}
override fun getItemCount(): Int = 1
}

View File

@ -10,12 +10,11 @@ import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.LatestControllerBinding
import eu.kanade.tachiyomi.databinding.IndexControllerBinding
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList
@ -31,6 +30,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.json.buildJsonObject
import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents
import uy.kohesive.injekt.Injekt
@ -43,10 +43,9 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer
* [IndexCardAdapter.OnMangaClickListener] called when manga is clicked in global search
*/
open class IndexController :
NucleusController<LatestControllerBinding, IndexPresenter>,
NucleusController<IndexControllerBinding, IndexPresenter>,
FabController,
IndexCardAdapter.OnMangaClickListener,
IndexAdapter.ClickListener {
IndexCardAdapter.OnMangaClickListener {
constructor(source: CatalogueSource?) : super(
bundleOf(
@ -65,13 +64,10 @@ open class IndexController :
var source: CatalogueSource? = null
/**
* Adapter containing search results grouped by lang.
*/
protected var adapter: IndexAdapter? = null
private var latestAdapter: IndexCardAdapter? = null
private var browseAdapter: IndexCardAdapter? = null
private var actionFab: ExtendedFloatingActionButton? = null
private var actionFabScrollListener: RecyclerView.OnScrollListener? = null
/**
* Sheet containing filter items.
@ -90,7 +86,7 @@ open class IndexController :
* @return inflated view
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = LatestControllerBinding.inflate(inflater)
binding = IndexControllerBinding.inflate(inflater)
return binding.root
}
@ -176,11 +172,39 @@ open class IndexController :
// Prepare filter sheet
initFilterSheet()
adapter = IndexAdapter(this)
latestAdapter = IndexCardAdapter(this)
// Create recycler and set adapter.
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
binding.latestRecycler.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.HORIZONTAL, false)
binding.latestRecycler.adapter = latestAdapter
browseAdapter = IndexCardAdapter(this)
binding.browseRecycler.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.HORIZONTAL, false)
binding.browseRecycler.adapter = browseAdapter
binding.latestBarWrapper.clicks()
.onEach {
onLatestClick()
}
.launchIn(viewScope)
binding.browseBarWrapper.clicks()
.onEach {
onBrowseClick()
}
.launchIn(viewScope)
presenter.latestItems
.onEach {
bindLatest(it)
}
.launchIn(viewScope)
presenter.browseItems
.onEach {
bindBrowse(it)
}
.launchIn(viewScope)
presenter.getLatest()
}
@ -261,28 +285,91 @@ open class IndexController :
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null
}
fun setLatestManga(results: List<IndexCardItem>?) {
adapter?.holder?.bindLatest(results)
private fun bindLatest(latestResults: List<IndexCardItem>?) {
when {
latestResults == null -> {
binding.latestProgress.isVisible = true
showLatestResultsHolder()
}
latestResults.isEmpty() -> {
binding.latestProgress.isVisible = false
showLatestNoResults()
}
else -> {
binding.latestProgress.isVisible = false
showLatestResultsHolder()
}
}
fun setBrowseManga(results: List<IndexCardItem>?) {
adapter?.holder?.bindBrowse(results)
latestAdapter?.updateDataSet(latestResults)
}
private fun bindBrowse(browseResults: List<IndexCardItem>?) {
when {
browseResults == null -> {
binding.browseProgress.isVisible = true
showBrowseResultsHolder()
}
browseResults.isEmpty() -> {
binding.browseProgress.isVisible = false
showBrowseNoResults()
}
else -> {
binding.browseProgress.isVisible = false
showBrowseResultsHolder()
}
}
browseAdapter?.updateDataSet(browseResults)
}
private fun showLatestResultsHolder() {
binding.latestNoResultsFound.isVisible = false
}
private fun showLatestNoResults() {
binding.latestNoResultsFound.isVisible = true
}
private fun showBrowseResultsHolder() {
binding.browseNoResultsFound.isVisible = false
}
private fun showBrowseNoResults() {
binding.browseNoResultsFound.isVisible = true
}
private fun setLatestImage(manga: Manga) {
val latestAdapter = latestAdapter ?: return
latestAdapter.allBoundViewHolders.forEach {
if (it !is IndexCardHolder) return@forEach
if (latestAdapter.getItem(it.bindingAdapterPosition)?.manga?.id != manga.id) return@forEach
it.setImage(manga)
}
}
private fun setBrowseImage(manga: Manga) {
val browseAdapter = browseAdapter ?: return
browseAdapter.allBoundViewHolders.forEach {
if (it !is IndexCardHolder) return@forEach
if (browseAdapter.getItem(it.bindingAdapterPosition)?.manga?.id != manga.id) return@forEach
it.setImage(manga)
}
}
override fun onDestroyView(view: View) {
adapter = null
latestAdapter = null
browseAdapter = null
super.onDestroyView(view)
}
override fun onBrowseClick(search: String?, filters: String?) {
private fun onBrowseClick(search: String? = null, filters: String? = null) {
router.replaceTopController(BrowseSourceController(presenter.source, search, filterList = filters).withFadeTransaction())
}
override fun onLatestClick() {
private fun onLatestClick() {
router.replaceTopController(LatestUpdatesController(presenter.source).withFadeTransaction())
}
@ -292,8 +379,8 @@ open class IndexController :
* @param manga the initialized manga.
*/
fun onMangaInitialized(manga: Manga, isLatest: Boolean) {
if (isLatest) adapter?.holder?.setLatestImage(manga)
else adapter?.holder?.setBrowseImage(manga)
if (isLatest) setLatestImage(manga)
else setBrowseImage(manga)
}
companion object {

View File

@ -15,10 +15,10 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Companion.toItems
import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withUIContext
import exh.savedsearches.EXHSavedSearch
import exh.savedsearches.JsonSavedSearch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@ -78,6 +78,10 @@ open class IndexPresenter(
*/
private var fetchImageSubscription: Subscription? = null
val latestItems = MutableStateFlow<List<IndexCardItem>?>(null)
val browseItems = MutableStateFlow<List<IndexCardItem>?>(null)
override fun onDestroy() {
fetchSourcesSubscription?.unsubscribe()
fetchImageSubscription?.unsubscribe()
@ -98,54 +102,37 @@ open class IndexPresenter(
initializeFetchImageSubscription()
presenterScope.launch(Dispatchers.IO) {
withUIContext {
Observable.just(null).subscribeLatestCache({ view, results ->
view.setLatestManga(results)
})
}
if (source.supportsLatest) {
val results = try {
if (latestItems.value != null) return@launch
val results = if (source.supportsLatest) {
try {
source.fetchLatestUpdates(1)
.awaitSingle()
.mangas
.take(10)
.map { networkToLocalManga(it, source.id) }
} catch (e: Exception) {
emptyList()
}
} else emptyList()
fetchImage(results, true)
withUIContext {
Observable.just(results.map { IndexCardItem(it) }).subscribeLatestCache({ view, results ->
view.setLatestManga(results)
})
}
}
latestItems.value = results.map { IndexCardItem(it) }
}
presenterScope.launch(Dispatchers.IO) {
withUIContext {
Observable.just(null).subscribeLatestCache({ view, results ->
view.setBrowseManga(results)
})
}
if (browseItems.value != null) return@launch
val results = try {
source.fetchPopularManga(1)
.awaitSingle()
.mangas
.take(10)
.map { networkToLocalManga(it, source.id) }
} catch (e: Exception) {
emptyList()
}
fetchImage(results, false)
withUIContext {
Observable.just(results.map { IndexCardItem(it) }).subscribeLatestCache({ view, results ->
view.setBrowseManga(results)
})
}
browseItems.value = results.map { IndexCardItem(it) }
}
}
@ -221,10 +208,10 @@ open class IndexPresenter(
fun loadSearches(): List<EXHSavedSearch> {
val loaded = preferences.savedSearches().get()
return loaded.map {
return loaded.mapNotNull {
try {
val id = it.substringBefore(':').toLong()
if (id != source.id) return@map null
if (id != source.id) return@mapNotNull null
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
val originalFilters = source.getFilterList()
filterSerializer.deserialize(originalFilters, content.filters)
@ -239,6 +226,6 @@ open class IndexPresenter(
t.printStackTrace()
null
}
}.filterNotNull()
}
}
}