Migrate feed to adapter delegates
parent
12c8cdfd70
commit
b9f35f34ad
@ -1,34 +1,18 @@
|
||||
package org.koitharu.kotatsu.base.ui.list
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class PaginationScrollListener(offset: Int, private val callback: Callback) :
|
||||
BoundsScrollListener(0, offset) {
|
||||
|
||||
private var lastTotalCount = 0
|
||||
|
||||
override fun onScrolledToStart(recyclerView: RecyclerView) = Unit
|
||||
|
||||
override fun onScrolledToEnd(recyclerView: RecyclerView) {
|
||||
val total = (recyclerView.layoutManager as? LinearLayoutManager)?.itemCount ?: return
|
||||
if (total > lastTotalCount) {
|
||||
lastTotalCount = total
|
||||
callback.onRequestMoreItems(total)
|
||||
} else if (total < lastTotalCount) {
|
||||
lastTotalCount = total
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
lastTotalCount = 0
|
||||
callback.onScrolledToEnd()
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
||||
fun onRequestMoreItems(offset: Int)
|
||||
|
||||
@Deprecated("Not in use")
|
||||
fun getItemsCount(): Int = 0
|
||||
fun onScrolledToEnd()
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,46 @@
|
||||
package org.koitharu.kotatsu.tracker.ui
|
||||
|
||||
import android.view.ViewGroup
|
||||
import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
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) :
|
||||
BaseRecyclerAdapter<TrackingLogItem, Unit>(onItemClickListener) {
|
||||
class FeedAdapter(
|
||||
coil: ImageLoader,
|
||||
clickListener: OnListItemClickListener<Manga>
|
||||
) : AsyncListDifferDelegationAdapter<Any>(DiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<TrackingLogItem, Unit> {
|
||||
return FeedHolder(parent)
|
||||
init {
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
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.core.model.TrackingLogItem
|
||||
import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.tracker.ui.model.toFeedItem
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
|
||||
class FeedViewModel(
|
||||
context: Context,
|
||||
private val repository: TrackingRepository
|
||||
) : 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) {
|
||||
launchLoadingJob {
|
||||
val isEmptyState = MutableLiveData(false)
|
||||
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)
|
||||
if (offset == 0) {
|
||||
content.value = list
|
||||
} else {
|
||||
content.value = content.value.orEmpty() + list
|
||||
if (!append) {
|
||||
logList.value = list
|
||||
} else if (list.isNotEmpty()) {
|
||||
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