Migrate feed to adapter delegates
parent
12c8cdfd70
commit
b9f35f34ad
@ -1,34 +1,18 @@
|
|||||||
package org.koitharu.kotatsu.base.ui.list
|
package org.koitharu.kotatsu.base.ui.list
|
||||||
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
class PaginationScrollListener(offset: Int, private val callback: Callback) :
|
class PaginationScrollListener(offset: Int, private val callback: Callback) :
|
||||||
BoundsScrollListener(0, offset) {
|
BoundsScrollListener(0, offset) {
|
||||||
|
|
||||||
private var lastTotalCount = 0
|
|
||||||
|
|
||||||
override fun onScrolledToStart(recyclerView: RecyclerView) = Unit
|
override fun onScrolledToStart(recyclerView: RecyclerView) = Unit
|
||||||
|
|
||||||
override fun onScrolledToEnd(recyclerView: RecyclerView) {
|
override fun onScrolledToEnd(recyclerView: RecyclerView) {
|
||||||
val total = (recyclerView.layoutManager as? LinearLayoutManager)?.itemCount ?: return
|
callback.onScrolledToEnd()
|
||||||
if (total > lastTotalCount) {
|
|
||||||
lastTotalCount = total
|
|
||||||
callback.onRequestMoreItems(total)
|
|
||||||
} else if (total < lastTotalCount) {
|
|
||||||
lastTotalCount = total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reset() {
|
|
||||||
lastTotalCount = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
|
|
||||||
fun onRequestMoreItems(offset: Int)
|
fun onScrolledToEnd()
|
||||||
|
|
||||||
@Deprecated("Not in use")
|
|
||||||
fun getItemsCount(): Int = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,19 +1,46 @@
|
|||||||
package org.koitharu.kotatsu.tracker.ui
|
package org.koitharu.kotatsu.tracker.ui
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter
|
import coil.ImageLoader
|
||||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||||
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
|
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
import org.koitharu.kotatsu.core.model.Manga
|
||||||
|
import org.koitharu.kotatsu.list.ui.adapter.indeterminateProgressAD
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress
|
||||||
|
import org.koitharu.kotatsu.tracker.ui.adapter.feedItemAD
|
||||||
|
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
||||||
|
import kotlin.jvm.internal.Intrinsics
|
||||||
|
|
||||||
class FeedAdapter(onItemClickListener: OnRecyclerItemClickListener<TrackingLogItem>? = null) :
|
class FeedAdapter(
|
||||||
BaseRecyclerAdapter<TrackingLogItem, Unit>(onItemClickListener) {
|
coil: ImageLoader,
|
||||||
|
clickListener: OnListItemClickListener<Manga>
|
||||||
|
) : AsyncListDifferDelegationAdapter<Any>(DiffCallback()) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<TrackingLogItem, Unit> {
|
init {
|
||||||
return FeedHolder(parent)
|
delegatesManager.addDelegate(ITEM_TYPE_FEED, feedItemAD(coil, clickListener))
|
||||||
|
.addDelegate(ITEM_TYPE_PROGRESS, indeterminateProgressAD())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetItemId(item: TrackingLogItem) = item.id
|
private class DiffCallback : DiffUtil.ItemCallback<Any>() {
|
||||||
|
|
||||||
override fun getExtra(item: TrackingLogItem, position: Int) = Unit
|
override fun areItemsTheSame(oldItem: Any, newItem: Any) = when {
|
||||||
|
oldItem is FeedItem && newItem is FeedItem -> {
|
||||||
|
oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
oldItem == IndeterminateProgress && newItem == IndeterminateProgress -> {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {
|
||||||
|
return Intrinsics.areEqual(oldItem, newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val ITEM_TYPE_FEED = 0
|
||||||
|
const val ITEM_TYPE_PROGRESS = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,48 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.tracker.ui
|
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.request.Disposable
|
|
||||||
import kotlinx.android.synthetic.main.item_tracklog.*
|
|
||||||
import org.koin.core.component.inject
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
|
||||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
|
||||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
|
||||||
import org.koitharu.kotatsu.utils.ext.formatRelative
|
|
||||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
|
||||||
|
|
||||||
class FeedHolder(parent: ViewGroup) :
|
|
||||||
BaseViewHolder<TrackingLogItem, Unit>(parent, R.layout.item_tracklog) {
|
|
||||||
|
|
||||||
private val coil by inject<ImageLoader>()
|
|
||||||
private var imageRequest: Disposable? = null
|
|
||||||
|
|
||||||
override fun onBind(data: TrackingLogItem, extra: Unit) {
|
|
||||||
imageRequest?.dispose()
|
|
||||||
imageRequest = imageView_cover.newImageRequest(data.manga.coverUrl)
|
|
||||||
.placeholder(R.drawable.ic_placeholder)
|
|
||||||
.fallback(R.drawable.ic_placeholder)
|
|
||||||
.error(R.drawable.ic_placeholder)
|
|
||||||
.enqueueWith(coil)
|
|
||||||
textView_title.text = data.manga.title
|
|
||||||
textView_subtitle.text = buildString {
|
|
||||||
append(data.createdAt.formatRelative(DateUtils.DAY_IN_MILLIS))
|
|
||||||
append(" ")
|
|
||||||
append(
|
|
||||||
context.resources.getQuantityString(
|
|
||||||
R.plurals.new_chapters,
|
|
||||||
data.chapters.size,
|
|
||||||
data.chapters.size
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
textView_chapters.text = data.chapters.joinToString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRecycled() {
|
|
||||||
imageRequest?.dispose()
|
|
||||||
imageView_cover.setImageDrawable(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +1,60 @@
|
|||||||
package org.koitharu.kotatsu.tracker.ui
|
package org.koitharu.kotatsu.tracker.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.drop
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress
|
||||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
|
import org.koitharu.kotatsu.tracker.ui.model.toFeedItem
|
||||||
|
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||||
|
|
||||||
class FeedViewModel(
|
class FeedViewModel(
|
||||||
|
context: Context,
|
||||||
private val repository: TrackingRepository
|
private val repository: TrackingRepository
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val content = MutableLiveData<List<TrackingLogItem>>()
|
private val logList = MutableStateFlow<List<TrackingLogItem>>(emptyList())
|
||||||
|
private val hasNextPage = MutableStateFlow(false)
|
||||||
|
private var loadingJob: Job? = null
|
||||||
|
|
||||||
fun loadList(offset: Int) {
|
val isEmptyState = MutableLiveData(false)
|
||||||
launchLoadingJob {
|
val content = combine(
|
||||||
|
logList.drop(1).onEach {
|
||||||
|
isEmptyState.postValue(it.isEmpty())
|
||||||
|
}.mapItems {
|
||||||
|
it.toFeedItem(context.resources)
|
||||||
|
},
|
||||||
|
hasNextPage
|
||||||
|
) { list, isHasNextPage ->
|
||||||
|
if (isHasNextPage && list.isNotEmpty()) list + IndeterminateProgress else list
|
||||||
|
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadList(append = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadList(append: Boolean) {
|
||||||
|
if (loadingJob?.isActive == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loadingJob = launchLoadingJob {
|
||||||
|
val offset = if (append) logList.value.size else 0
|
||||||
val list = repository.getTrackingLog(offset, 20)
|
val list = repository.getTrackingLog(offset, 20)
|
||||||
if (offset == 0) {
|
if (!append) {
|
||||||
content.value = list
|
logList.value = list
|
||||||
} else {
|
} else if (list.isNotEmpty()) {
|
||||||
content.value = content.value.orEmpty() + list
|
logList.value += list
|
||||||
}
|
}
|
||||||
|
hasNextPage.value = list.isNotEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package org.koitharu.kotatsu.tracker.ui.adapter
|
||||||
|
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.request.Disposable
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
|
||||||
|
import kotlinx.android.synthetic.main.item_tracklog.*
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||||
|
import org.koitharu.kotatsu.core.model.Manga
|
||||||
|
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
||||||
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
|
|
||||||
|
fun feedItemAD(
|
||||||
|
coil: ImageLoader,
|
||||||
|
clickListener: OnListItemClickListener<Manga>
|
||||||
|
) = adapterDelegateLayoutContainer<FeedItem, Any>(R.layout.item_tracklog) {
|
||||||
|
|
||||||
|
var imageRequest: Disposable? = null
|
||||||
|
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
clickListener.onItemClick(item.manga, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind {
|
||||||
|
imageRequest?.dispose()
|
||||||
|
imageRequest = imageView_cover.newImageRequest(item.imageUrl)
|
||||||
|
.placeholder(R.drawable.ic_placeholder)
|
||||||
|
.fallback(R.drawable.ic_placeholder)
|
||||||
|
.error(R.drawable.ic_placeholder)
|
||||||
|
.enqueueWith(coil)
|
||||||
|
textView_title.text = item.title
|
||||||
|
textView_subtitle.text = item.subtitle
|
||||||
|
textView_chapters.text = item.chapters
|
||||||
|
}
|
||||||
|
|
||||||
|
onViewRecycled {
|
||||||
|
imageRequest?.dispose()
|
||||||
|
imageView_cover.setImageDrawable(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.tracker.ui.model
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.core.model.Manga
|
||||||
|
|
||||||
|
data class FeedItem(
|
||||||
|
val id: Long,
|
||||||
|
val imageUrl: String,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String,
|
||||||
|
val chapters: CharSequence,
|
||||||
|
val manga: Manga
|
||||||
|
)
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package org.koitharu.kotatsu.tracker.ui.model
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||||
|
import org.koitharu.kotatsu.utils.ext.formatRelative
|
||||||
|
|
||||||
|
fun TrackingLogItem.toFeedItem(resources: Resources) = FeedItem(
|
||||||
|
id = id,
|
||||||
|
imageUrl = manga.coverUrl,
|
||||||
|
title = manga.title,
|
||||||
|
subtitle = buildString {
|
||||||
|
append(createdAt.formatRelative(DateUtils.DAY_IN_MILLIS))
|
||||||
|
append(" ")
|
||||||
|
append(
|
||||||
|
resources.getQuantityString(
|
||||||
|
R.plurals.new_chapters,
|
||||||
|
chapters.size,
|
||||||
|
chapters.size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
chapters = chapters.joinToString("\n"),
|
||||||
|
manga = manga
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue