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.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga 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.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList 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.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.appcompat.QueryTextEvent import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents import reactivecircus.flowbinding.appcompat.queryTextEvents
import uy.kohesive.injekt.Injekt 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 * [IndexCardAdapter.OnMangaClickListener] called when manga is clicked in global search
*/ */
open class IndexController : open class IndexController :
NucleusController<LatestControllerBinding, IndexPresenter>, NucleusController<IndexControllerBinding, IndexPresenter>,
FabController, FabController,
IndexCardAdapter.OnMangaClickListener, IndexCardAdapter.OnMangaClickListener {
IndexAdapter.ClickListener {
constructor(source: CatalogueSource?) : super( constructor(source: CatalogueSource?) : super(
bundleOf( bundleOf(
@ -65,13 +64,10 @@ open class IndexController :
var source: CatalogueSource? = null var source: CatalogueSource? = null
/** private var latestAdapter: IndexCardAdapter? = null
* Adapter containing search results grouped by lang. private var browseAdapter: IndexCardAdapter? = null
*/
protected var adapter: IndexAdapter? = null
private var actionFab: ExtendedFloatingActionButton? = null private var actionFab: ExtendedFloatingActionButton? = null
private var actionFabScrollListener: RecyclerView.OnScrollListener? = null
/** /**
* Sheet containing filter items. * Sheet containing filter items.
@ -90,7 +86,7 @@ open class IndexController :
* @return inflated view * @return inflated view
*/ */
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = LatestControllerBinding.inflate(inflater) binding = IndexControllerBinding.inflate(inflater)
return binding.root return binding.root
} }
@ -176,11 +172,39 @@ open class IndexController :
// Prepare filter sheet // Prepare filter sheet
initFilterSheet() initFilterSheet()
adapter = IndexAdapter(this) latestAdapter = IndexCardAdapter(this)
// Create recycler and set adapter. binding.latestRecycler.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.HORIZONTAL, false)
binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.latestRecycler.adapter = latestAdapter
binding.recycler.adapter = adapter
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() presenter.getLatest()
} }
@ -261,28 +285,91 @@ open class IndexController :
override fun cleanupFab(fab: ExtendedFloatingActionButton) { override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null) fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null actionFab = null
} }
fun setLatestManga(results: List<IndexCardItem>?) { private fun bindLatest(latestResults: List<IndexCardItem>?) {
adapter?.holder?.bindLatest(results) when {
latestResults == null -> {
binding.latestProgress.isVisible = true
showLatestResultsHolder()
}
latestResults.isEmpty() -> {
binding.latestProgress.isVisible = false
showLatestNoResults()
}
else -> {
binding.latestProgress.isVisible = false
showLatestResultsHolder()
}
}
latestAdapter?.updateDataSet(latestResults)
} }
fun setBrowseManga(results: List<IndexCardItem>?) { private fun bindBrowse(browseResults: List<IndexCardItem>?) {
adapter?.holder?.bindBrowse(results) 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) { override fun onDestroyView(view: View) {
adapter = null latestAdapter = null
browseAdapter = null
super.onDestroyView(view) 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()) router.replaceTopController(BrowseSourceController(presenter.source, search, filterList = filters).withFadeTransaction())
} }
override fun onLatestClick() { private fun onLatestClick() {
router.replaceTopController(LatestUpdatesController(presenter.source).withFadeTransaction()) router.replaceTopController(LatestUpdatesController(presenter.source).withFadeTransaction())
} }
@ -292,8 +379,8 @@ open class IndexController :
* @param manga the initialized manga. * @param manga the initialized manga.
*/ */
fun onMangaInitialized(manga: Manga, isLatest: Boolean) { fun onMangaInitialized(manga: Manga, isLatest: Boolean) {
if (isLatest) adapter?.holder?.setLatestImage(manga) if (isLatest) setLatestImage(manga)
else adapter?.holder?.setBrowseImage(manga) else setBrowseImage(manga)
} }
companion object { 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.ui.browse.source.browse.BrowseSourcePresenter.Companion.toItems
import eu.kanade.tachiyomi.util.lang.awaitSingle import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withUIContext
import exh.savedsearches.EXHSavedSearch import exh.savedsearches.EXHSavedSearch
import exh.savedsearches.JsonSavedSearch import exh.savedsearches.JsonSavedSearch
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -78,6 +78,10 @@ open class IndexPresenter(
*/ */
private var fetchImageSubscription: Subscription? = null private var fetchImageSubscription: Subscription? = null
val latestItems = MutableStateFlow<List<IndexCardItem>?>(null)
val browseItems = MutableStateFlow<List<IndexCardItem>?>(null)
override fun onDestroy() { override fun onDestroy() {
fetchSourcesSubscription?.unsubscribe() fetchSourcesSubscription?.unsubscribe()
fetchImageSubscription?.unsubscribe() fetchImageSubscription?.unsubscribe()
@ -98,54 +102,37 @@ open class IndexPresenter(
initializeFetchImageSubscription() initializeFetchImageSubscription()
presenterScope.launch(Dispatchers.IO) { presenterScope.launch(Dispatchers.IO) {
withUIContext { if (latestItems.value != null) return@launch
Observable.just(null).subscribeLatestCache({ view, results -> val results = if (source.supportsLatest) {
view.setLatestManga(results) try {
})
}
if (source.supportsLatest) {
val results = try {
source.fetchLatestUpdates(1) source.fetchLatestUpdates(1)
.awaitSingle() .awaitSingle()
.mangas .mangas
.take(10)
.map { networkToLocalManga(it, source.id) } .map { networkToLocalManga(it, source.id) }
} catch (e: Exception) { } catch (e: Exception) {
emptyList() emptyList()
} }
fetchImage(results, true) } else emptyList()
withUIContext { fetchImage(results, true)
Observable.just(results.map { IndexCardItem(it) }).subscribeLatestCache({ view, results ->
view.setLatestManga(results) latestItems.value = results.map { IndexCardItem(it) }
})
}
}
} }
presenterScope.launch(Dispatchers.IO) { presenterScope.launch(Dispatchers.IO) {
withUIContext { if (browseItems.value != null) return@launch
Observable.just(null).subscribeLatestCache({ view, results ->
view.setBrowseManga(results)
})
}
val results = try { val results = try {
source.fetchPopularManga(1) source.fetchPopularManga(1)
.awaitSingle() .awaitSingle()
.mangas .mangas
.take(10)
.map { networkToLocalManga(it, source.id) } .map { networkToLocalManga(it, source.id) }
} catch (e: Exception) { } catch (e: Exception) {
emptyList() emptyList()
} }
fetchImage(results, false) fetchImage(results, false)
withUIContext { browseItems.value = results.map { IndexCardItem(it) }
Observable.just(results.map { IndexCardItem(it) }).subscribeLatestCache({ view, results ->
view.setBrowseManga(results)
})
}
} }
} }
@ -221,10 +208,10 @@ open class IndexPresenter(
fun loadSearches(): List<EXHSavedSearch> { fun loadSearches(): List<EXHSavedSearch> {
val loaded = preferences.savedSearches().get() val loaded = preferences.savedSearches().get()
return loaded.map { return loaded.mapNotNull {
try { try {
val id = it.substringBefore(':').toLong() 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 content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
val originalFilters = source.getFilterList() val originalFilters = source.getFilterList()
filterSerializer.deserialize(originalFilters, content.filters) filterSerializer.deserialize(originalFilters, content.filters)
@ -239,6 +226,6 @@ open class IndexPresenter(
t.printStackTrace() t.printStackTrace()
null null
} }
}.filterNotNull() }
} }
} }