From b9f35f34ad47f0908cf36a8f8c09cefe4f3b70aa Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 23 Nov 2020 19:16:02 +0200 Subject: [PATCH] Migrate feed to adapter delegates --- .../base/ui/list/PaginationScrollListener.kt | 20 +------- .../ui/list/FavouritesListFragment.kt | 4 +- .../kotatsu/history/ui/HistoryListFragment.kt | 7 +-- .../kotatsu/list/ui/MangaListFragment.kt | 7 +-- .../kotatsu/list/ui/MangaListSheet.kt | 6 +-- .../kotatsu/local/ui/LocalListFragment.kt | 9 ++-- .../kotatsu/local/ui/LocalListViewModel.kt | 22 ++++---- .../remotelist/ui/RemoteListFragment.kt | 9 +++- .../remotelist/ui/RemoteListViewModel.kt | 10 ++-- .../kotatsu/search/ui/MangaSearchSheet.kt | 4 +- .../kotatsu/search/ui/SearchFragment.kt | 9 +++- .../kotatsu/search/ui/SearchViewModel.kt | 6 +-- .../search/ui/global/GlobalSearchFragment.kt | 9 ++-- .../koitharu/kotatsu/tracker/TrackerModule.kt | 3 +- .../kotatsu/tracker/ui/FeedAdapter.kt | 49 ++++++++++++++---- .../kotatsu/tracker/ui/FeedFragment.kt | 44 +++++++--------- .../koitharu/kotatsu/tracker/ui/FeedHolder.kt | 48 ------------------ .../kotatsu/tracker/ui/FeedViewModel.kt | 50 ++++++++++++++++--- .../kotatsu/tracker/ui/adapter/FeedItemAD.kt | 41 +++++++++++++++ .../kotatsu/tracker/ui/model/FeedItem.kt | 12 +++++ .../ui/model/ListModelConversionExt.kt | 26 ++++++++++ 21 files changed, 237 insertions(+), 158 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedHolder.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt index d9f43632d..5681cae23 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt @@ -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() } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt index 731d42eb7..df3eb9e47 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt @@ -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( diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt index 9bf9439eb..7743dc3f2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt @@ -17,17 +17,14 @@ import org.koitharu.kotatsu.utils.ext.ellipsize class HistoryListFragment : MangaListFragment() { override val viewModel by viewModel() - - 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) diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index 74e3b3982..1ea0a3398 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -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) { diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSheet.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSheet.kt index 743f29b20..77f27887a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSheet.kt @@ -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 } } diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt index fb153856b..8266299ab 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt @@ -32,12 +32,13 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback { 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) diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt index f4dada4a9..f1eaf8dae 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt @@ -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()) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt index 5da26fa35..91f95c0a3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt @@ -21,8 +21,13 @@ class RemoteListFragment : MangaListFragment() { private val source by parcelableArgument(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? { diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt index 8d006d8e3..07469a896 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -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() { diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt index e227462e9..bb3941707 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt @@ -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 { diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt index 29104078b..ac6e22c25 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt @@ -17,8 +17,13 @@ class SearchFragment : MangaListFragment() { private val query by stringArgument(ARG_QUERY) private val source by parcelableArgument(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? { diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt index 0b9648085..6d6b8b783 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt @@ -14,12 +14,12 @@ class SearchViewModel( override val content = MutableLiveData>() - 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 diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt index ea54afbbc..63a116b4e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt @@ -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 } diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt index 3fc8eee71..2d7a2ab7e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt @@ -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()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedAdapter.kt index 31f9486b0..4334e929b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedAdapter.kt @@ -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? = null) : - BaseRecyclerAdapter(onItemClickListener) { +class FeedAdapter( + coil: ImageLoader, + clickListener: OnListItemClickListener +) : AsyncListDifferDelegationAdapter(DiffCallback()) { - override fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder { - 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() { - 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 + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt index 129a75a31..fa26dad6f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt @@ -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 { + OnListItemClickListener { private val viewModel by viewModel() @@ -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) { - adapter?.replaceData(list) - if (list.isEmpty()) { - setUpEmptyListHolder() - layout_holder.isVisible = true - } else { - layout_holder.isVisible = false - } - recyclerView.callOnScrollListeners() + private fun onListChanged(list: List) { + 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() { diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedHolder.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedHolder.kt deleted file mode 100644 index 06e3dd934..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedHolder.kt +++ /dev/null @@ -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(parent, R.layout.item_tracklog) { - - private val coil by inject() - 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) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt index 9056a4395..9bc062111 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt @@ -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>() + private val logList = MutableStateFlow>(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() } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt new file mode 100644 index 000000000..a4b01a155 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt @@ -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 +) = adapterDelegateLayoutContainer(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) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt new file mode 100644 index 000000000..887f41db3 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt new file mode 100644 index 000000000..d1d6694de --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt @@ -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 +) \ No newline at end of file