Migrate feed to adapter delegates
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ class FavouritesListFragment : MangaListFragment() {
|
||||
private val categoryId: Long
|
||||
get() = arguments?.getLong(ARG_CATEGORY_ID) ?: 0L
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) = Unit
|
||||
override val isSwipeRefreshEnabled = false
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun setUpEmptyListHolder() {
|
||||
textView_holder.setText(
|
||||
|
||||
@@ -17,17 +17,14 @@ import org.koitharu.kotatsu.utils.ext.ellipsize
|
||||
class HistoryListFragment : MangaListFragment() {
|
||||
|
||||
override val viewModel by viewModel<HistoryListViewModel>()
|
||||
|
||||
init {
|
||||
isSwipeRefreshEnabled = false
|
||||
}
|
||||
override val isSwipeRefreshEnabled = false
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel.onItemRemoved.observe(viewLifecycleOwner, ::onItemRemoved)
|
||||
}
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) = Unit
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.opt_history, menu)
|
||||
|
||||
@@ -45,7 +45,7 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
|
||||
private var paginationListener: PaginationScrollListener? = null
|
||||
private val spanResolver = MangaListSpanResolver()
|
||||
private val spanSizeLookup = SpanSizeLookup()
|
||||
protected var isSwipeRefreshEnabled = true
|
||||
open val isSwipeRefreshEnabled = true
|
||||
|
||||
protected abstract val viewModel: MangaListViewModel
|
||||
|
||||
@@ -63,6 +63,7 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.addOnScrollListener(paginationListener!!)
|
||||
swipeRefreshLayout.setOnRefreshListener(this)
|
||||
swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled
|
||||
recyclerView_filter.setHasFixedSize(true)
|
||||
recyclerView_filter.addItemDecoration(ItemTypeDividerDecoration(view.context))
|
||||
recyclerView_filter.addItemDecoration(SectionItemDecoration(false, this))
|
||||
@@ -125,9 +126,9 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
|
||||
}
|
||||
}
|
||||
|
||||
final override fun onRefresh() {
|
||||
@CallSuper
|
||||
override fun onRefresh() {
|
||||
swipeRefreshLayout.isRefreshing = true
|
||||
onRequestMoreItems(0)
|
||||
}
|
||||
|
||||
private fun onListChanged(list: List<Any>) {
|
||||
|
||||
@@ -57,7 +57,7 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
|
||||
appbar.elevation = resources.getDimension(R.dimen.elevation_large)
|
||||
}
|
||||
if (savedInstanceState == null) {
|
||||
onRequestMoreItems(0)
|
||||
onScrolledToEnd()
|
||||
}
|
||||
viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
|
||||
viewModel.onError.observe(viewLifecycleOwner, ::onError)
|
||||
@@ -128,8 +128,6 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
|
||||
recyclerView.callOnScrollListeners()
|
||||
}
|
||||
|
||||
override fun getItemsCount() = adapter?.itemCount ?: 0
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
@@ -151,7 +149,7 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
|
||||
ListMode.GRID -> {
|
||||
GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx)).apply {
|
||||
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||
override fun getSpanSize(position: Int) = if (position < getItemsCount())
|
||||
override fun getSpanSize(position: Int) = if (position < TODO() as Int)
|
||||
1 else this@apply.spanCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,13 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<Uri> {
|
||||
viewModel.onMangaRemoved.observe(viewLifecycleOwner, ::onItemRemoved)
|
||||
}
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) {
|
||||
if (offset == 0) {
|
||||
viewModel.onRefresh()
|
||||
}
|
||||
override fun onRefresh() {
|
||||
super.onRefresh()
|
||||
viewModel.onRefresh()
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.opt_local, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
|
||||
@@ -45,11 +45,17 @@ class LocalListViewModel(
|
||||
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
init {
|
||||
loadList()
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
fun onRefresh() {
|
||||
loadList()
|
||||
launchLoadingJob {
|
||||
withContext(Dispatchers.Default) {
|
||||
val list = repository.getList(0)
|
||||
mangaList.value = list
|
||||
isEmptyState.postValue(list.isEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun importFile(uri: Uri) {
|
||||
@@ -69,7 +75,7 @@ class LocalListViewModel(
|
||||
}
|
||||
} ?: throw IOException("Cannot open input stream: $uri")
|
||||
}
|
||||
loadList()
|
||||
onRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,14 +94,4 @@ class LocalListViewModel(
|
||||
onMangaRemoved.call(manga)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadList() {
|
||||
launchLoadingJob {
|
||||
withContext(Dispatchers.Default) {
|
||||
val list = repository.getList(0)
|
||||
mangaList.value = list
|
||||
isEmptyState.postValue(list.isEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,13 @@ class RemoteListFragment : MangaListFragment() {
|
||||
|
||||
private val source by parcelableArgument<MangaSource>(ARG_SOURCE)
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) {
|
||||
viewModel.loadList(offset)
|
||||
override fun onRefresh() {
|
||||
super.onRefresh()
|
||||
viewModel.loadList(append = false)
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.loadList(append = true)
|
||||
}
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
|
||||
@@ -45,22 +45,22 @@ class RemoteListViewModel(
|
||||
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
init {
|
||||
loadList(0)
|
||||
loadList(false)
|
||||
loadFilter()
|
||||
}
|
||||
|
||||
fun loadList(offset: Int) {
|
||||
fun loadList(append: Boolean) {
|
||||
if (loadingJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
loadingJob = launchLoadingJob {
|
||||
withContext(Dispatchers.Default) {
|
||||
val list = repository.getList(
|
||||
offset = offset,
|
||||
offset = if (append) mangaList.value.size else 0,
|
||||
sortOrder = appliedFilter?.sortOrder,
|
||||
tag = appliedFilter?.tag
|
||||
)
|
||||
if (offset == 0) {
|
||||
if (!append) {
|
||||
mangaList.value = list
|
||||
} else if (list.isNotEmpty()) {
|
||||
mangaList.value += list
|
||||
@@ -74,7 +74,7 @@ class RemoteListViewModel(
|
||||
appliedFilter = newFilter
|
||||
mangaList.value = emptyList()
|
||||
hasNextPage.value = false
|
||||
loadList(0)
|
||||
loadList(false)
|
||||
}
|
||||
|
||||
private fun loadFilter() {
|
||||
|
||||
@@ -28,8 +28,8 @@ class MangaSearchSheet : MangaListSheet() {
|
||||
setSubtitle(getString(R.string.search_results_on_s, source.title))
|
||||
}
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) {
|
||||
viewModel.loadList(query.orEmpty(), offset)
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.loadList(query.orEmpty(), append = true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -17,8 +17,13 @@ class SearchFragment : MangaListFragment() {
|
||||
private val query by stringArgument(ARG_QUERY)
|
||||
private val source by parcelableArgument<MangaSource>(ARG_SOURCE)
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) {
|
||||
viewModel.loadList(query.orEmpty(), offset)
|
||||
override fun onRefresh() {
|
||||
super.onRefresh()
|
||||
viewModel.loadList(query.orEmpty(), append = false)
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.loadList(query.orEmpty(), append = true)
|
||||
}
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
|
||||
@@ -14,12 +14,12 @@ class SearchViewModel(
|
||||
|
||||
override val content = MutableLiveData<List<Any>>()
|
||||
|
||||
fun loadList(query: String, offset: Int) {
|
||||
fun loadList(query: String, append: Boolean) {
|
||||
launchLoadingJob {
|
||||
val list = withContext(Dispatchers.Default) {
|
||||
repository.getList(offset, query = query)
|
||||
repository.getList(TODO(), query = query)
|
||||
}
|
||||
if (offset == 0) {
|
||||
if (!append) {
|
||||
content.value = list
|
||||
} else {
|
||||
content.value = content.value.orEmpty() + list
|
||||
|
||||
@@ -12,12 +12,13 @@ class GlobalSearchFragment : MangaListFragment() {
|
||||
|
||||
private val query by stringArgument(ARG_QUERY)
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) {
|
||||
if (offset == 0) {
|
||||
viewModel.startSearch(query.orEmpty())
|
||||
}
|
||||
override fun onRefresh() {
|
||||
super.onRefresh()
|
||||
viewModel.startSearch(query.orEmpty())
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.tracker
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
@@ -10,5 +11,5 @@ val trackerModule
|
||||
|
||||
single { TrackingRepository(get(), get()) }
|
||||
|
||||
viewModel { FeedViewModel(get()) }
|
||||
viewModel { FeedViewModel(androidContext(), get()) }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,21 @@ import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.fragment_tracklogs.*
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.android.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||
import org.koitharu.kotatsu.utils.ext.callOnScrollListeners
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.hasItems
|
||||
|
||||
class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScrollListener.Callback,
|
||||
OnRecyclerItemClickListener<TrackingLogItem> {
|
||||
OnListItemClickListener<Manga> {
|
||||
|
||||
private val viewModel by viewModel<FeedViewModel>()
|
||||
|
||||
@@ -37,7 +37,7 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScroll
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
adapter = FeedAdapter(this)
|
||||
adapter = FeedAdapter(get(), this)
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.addItemDecoration(
|
||||
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing))
|
||||
@@ -45,12 +45,13 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScroll
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.addOnScrollListener(PaginationScrollListener(4, this))
|
||||
if (savedInstanceState == null) {
|
||||
onRequestMoreItems(0)
|
||||
onScrolledToEnd()
|
||||
}
|
||||
|
||||
viewModel.content.observe(viewLifecycleOwner, this::onListChanged)
|
||||
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
|
||||
viewModel.onError.observe(viewLifecycleOwner, this::onError)
|
||||
viewModel.isEmptyState.observe(viewLifecycleOwner, this::onEmptyStateChanged)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
@@ -72,15 +73,8 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScroll
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun onListChanged(list: List<TrackingLogItem>) {
|
||||
adapter?.replaceData(list)
|
||||
if (list.isEmpty()) {
|
||||
setUpEmptyListHolder()
|
||||
layout_holder.isVisible = true
|
||||
} else {
|
||||
layout_holder.isVisible = false
|
||||
}
|
||||
recyclerView.callOnScrollListeners()
|
||||
private fun onListChanged(list: List<Any>) {
|
||||
adapter?.items = list
|
||||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
@@ -102,21 +96,21 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScroll
|
||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
val hasItems = recyclerView.hasItems
|
||||
progressBar.isVisible = isLoading && !hasItems
|
||||
if (isLoading) {
|
||||
layout_holder.isVisible = false
|
||||
}
|
||||
|
||||
private fun onEmptyStateChanged(isEmpty: Boolean) {
|
||||
if (isEmpty) {
|
||||
setUpEmptyListHolder()
|
||||
}
|
||||
layout_holder.isVisible = isEmpty
|
||||
}
|
||||
|
||||
override fun getItemsCount(): Int {
|
||||
return adapter?.itemCount ?: 0
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.loadList(append = true)
|
||||
}
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) {
|
||||
viewModel.loadList(offset)
|
||||
}
|
||||
|
||||
override fun onItemClick(item: TrackingLogItem, position: Int, view: View) {
|
||||
startActivity(DetailsActivity.newIntent(context ?: return, item.manga))
|
||||
override fun onItemClick(item: Manga, view: View) {
|
||||
startActivity(DetailsActivity.newIntent(context ?: return, item))
|
||||
}
|
||||
|
||||
private fun setUpEmptyListHolder() {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
Reference in New Issue
Block a user