Improve library list
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package org.koitharu.kotatsu.history.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
|
||||
|
||||
class HistoryActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityContainerBinding.inflate(layoutInflater))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
val fm = supportFragmentManager
|
||||
if (fm.findFragmentById(R.id.container) == null) {
|
||||
fm.commit {
|
||||
val fragment = HistoryListFragment.newInstance()
|
||||
replace(R.id.container, fragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
with(binding.toolbar) {
|
||||
updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right
|
||||
)
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top
|
||||
}
|
||||
}
|
||||
binding.container.updatePadding(
|
||||
bottom = insets.bottom
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newIntent(context: Context) = Intent(context, HistoryActivity::class.java)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import org.koitharu.kotatsu.databinding.FragmentLibraryBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
|
||||
import org.koitharu.kotatsu.history.ui.HistoryListFragment
|
||||
import org.koitharu.kotatsu.history.ui.HistoryActivity
|
||||
import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter
|
||||
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
|
||||
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
|
||||
@@ -46,7 +46,7 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListene
|
||||
val sizeResolver = ItemSizeResolver(resources, get())
|
||||
val itemCLickListener = object : OnListItemClickListener<LibraryGroupModel> {
|
||||
override fun onItemClick(item: LibraryGroupModel, view: View) {
|
||||
|
||||
onGroupClick(item, view)
|
||||
}
|
||||
}
|
||||
selectionDecoration = MangaSelectionDecoration(view.context)
|
||||
@@ -153,6 +153,14 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListene
|
||||
actionMode = null
|
||||
}
|
||||
|
||||
private fun onGroupClick(item: LibraryGroupModel, view: View) {
|
||||
val intent = when (item) {
|
||||
is LibraryGroupModel.History -> HistoryActivity.newIntent(view.context)
|
||||
is LibraryGroupModel.Favourites -> TODO()
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun collectSelectedItems(): Set<Manga> {
|
||||
val ids = selectionDecoration?.checkedItemsIds
|
||||
if (ids.isNullOrEmpty()) {
|
||||
|
||||
@@ -5,11 +5,13 @@ import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.domain.MangaWithHistory
|
||||
@@ -21,6 +23,10 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||
import java.util.*
|
||||
|
||||
private const val HISTORY_MAX_SEGMENTS = 2
|
||||
|
||||
class LibraryViewModel(
|
||||
private val historyRepository: HistoryRepository,
|
||||
@@ -57,22 +63,54 @@ class LibraryViewModel(
|
||||
): List<ListModel> {
|
||||
val result = ArrayList<ListModel>(favourites.keys.size + 1)
|
||||
if (history.isNotEmpty()) {
|
||||
result += LibraryGroupModel.History(mapHistory(history), null)
|
||||
result += mapHistory(history)
|
||||
}
|
||||
for ((category, list) in favourites) {
|
||||
result += LibraryGroupModel.Favourites(list.toUi(ListMode.GRID, this), category)
|
||||
result += LibraryGroupModel.Favourites(list.toUi(ListMode.GRID, this), category, R.string.show_all)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun mapHistory(list: List<MangaWithHistory>): List<MangaItemModel> {
|
||||
private suspend fun mapHistory(list: List<MangaWithHistory>): List<LibraryGroupModel.History> {
|
||||
val showPercent = settings.isReadingIndicatorsEnabled
|
||||
val result = ArrayList<MangaItemModel>(list.size)
|
||||
val groups = ArrayList<DateTimeAgo>()
|
||||
val map = HashMap<DateTimeAgo, ArrayList<MangaItemModel>>()
|
||||
for ((manga, history) in list) {
|
||||
val date = timeAgo(history.updatedAt)
|
||||
val counter = trackingRepository.getNewChaptersCount(manga.id)
|
||||
val percent = if (showPercent) history.percent else PROGRESS_NONE
|
||||
result += manga.toGridModel(counter, percent)
|
||||
if (groups.lastOrNull() != date) {
|
||||
groups.add(date)
|
||||
}
|
||||
map.getOrPut(date) { ArrayList() }.add(manga.toGridModel(counter, percent))
|
||||
}
|
||||
val result = ArrayList<LibraryGroupModel.History>(HISTORY_MAX_SEGMENTS)
|
||||
repeat(minOf(HISTORY_MAX_SEGMENTS - 1, groups.size - 1)) { i ->
|
||||
val key = groups[i]
|
||||
val values = map.remove(key)
|
||||
if (!values.isNullOrEmpty()) {
|
||||
result.add(LibraryGroupModel.History(values, key, 0))
|
||||
}
|
||||
}
|
||||
val values = map.values.flatten()
|
||||
if (values.isNotEmpty()) {
|
||||
val key = if (result.isEmpty()) {
|
||||
map.keys.singleOrNull()?.takeUnless { it == DateTimeAgo.LongAgo }
|
||||
} else {
|
||||
map.keys.singleOrNull() ?: DateTimeAgo.LongAgo
|
||||
}
|
||||
result.add(LibraryGroupModel.History(values, key, R.string.show_all))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun timeAgo(date: Date): DateTimeAgo {
|
||||
val diffDays = -date.daysDiff(System.currentTimeMillis())
|
||||
return when {
|
||||
diffDays < 1 -> DateTimeAgo.Today
|
||||
diffDays == 1 -> DateTimeAgo.Yesterday
|
||||
diffDays <= 3 -> DateTimeAgo.DaysAgo(diffDays)
|
||||
else -> DateTimeAgo.LongAgo
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,5 +56,12 @@ class LibraryAdapter(
|
||||
override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
|
||||
return Intrinsics.areEqual(oldItem, newItem)
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? {
|
||||
return when {
|
||||
oldItem is LibraryGroupModel && newItem is LibraryGroupModel -> Unit
|
||||
else -> super.getChangePayload(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.library.ui.adapter
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
|
||||
@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
|
||||
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
|
||||
|
||||
fun libraryGroupAD(
|
||||
sharedPool: RecyclerView.RecycledViewPool,
|
||||
@@ -30,7 +31,8 @@ fun libraryGroupAD(
|
||||
) {
|
||||
|
||||
binding.recyclerView.setRecycledViewPool(sharedPool)
|
||||
val adapter = ListDelegationAdapter(
|
||||
val adapter = AsyncListDifferDelegationAdapter<ListModel>(
|
||||
MangaItemDiffCallback(),
|
||||
mangaGridItemAD(coil, lifecycleOwner, listener, sizeResolver)
|
||||
)
|
||||
binding.recyclerView.addItemDecoration(selectionDecoration)
|
||||
@@ -38,11 +40,11 @@ fun libraryGroupAD(
|
||||
val spacing = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing)
|
||||
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
|
||||
val eventListener = AdapterDelegateClickListenerAdapter(this, itemClickListener)
|
||||
itemView.setOnClickListener(eventListener)
|
||||
binding.buttonMore.setOnClickListener(eventListener)
|
||||
|
||||
bind {
|
||||
binding.textViewTitle.text = item.getTitle(context.resources)
|
||||
binding.buttonMore.setTextAndVisible(item.showAllButtonText)
|
||||
adapter.items = item.items
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.koitharu.kotatsu.library.ui.adapter
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class MangaItemDiffCallback : DiffUtil.ItemCallback<ListModel>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
|
||||
oldItem as MangaItemModel
|
||||
newItem as MangaItemModel
|
||||
return oldItem.javaClass == newItem.javaClass && oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
|
||||
return Intrinsics.areEqual(oldItem, newItem)
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? {
|
||||
oldItem as MangaItemModel
|
||||
newItem as MangaItemModel
|
||||
return when {
|
||||
oldItem.progress != newItem.progress -> MangaListAdapter.PAYLOAD_PROGRESS
|
||||
oldItem.counter != newItem.counter -> Unit
|
||||
else -> super.getChangePayload(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.library.ui.model
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.StringRes
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
@@ -8,7 +9,8 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
|
||||
|
||||
sealed class LibraryGroupModel(
|
||||
val items: List<MangaItemModel>
|
||||
val items: List<MangaItemModel>,
|
||||
@StringRes val showAllButtonText: Int,
|
||||
) : ListModel {
|
||||
|
||||
abstract val key: Any
|
||||
@@ -17,7 +19,8 @@ sealed class LibraryGroupModel(
|
||||
class History(
|
||||
items: List<MangaItemModel>,
|
||||
val timeAgo: DateTimeAgo?,
|
||||
) : LibraryGroupModel(items) {
|
||||
showAllButtonText: Int,
|
||||
) : LibraryGroupModel(items, showAllButtonText) {
|
||||
|
||||
override val key: Any
|
||||
get() = timeAgo?.javaClass ?: this::class.java
|
||||
@@ -32,8 +35,9 @@ sealed class LibraryGroupModel(
|
||||
|
||||
other as History
|
||||
|
||||
if (items != other.items) return false
|
||||
if (timeAgo != other.timeAgo) return false
|
||||
if (showAllButtonText != other.showAllButtonText) return false
|
||||
if (items != other.items) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -41,6 +45,7 @@ sealed class LibraryGroupModel(
|
||||
override fun hashCode(): Int {
|
||||
var result = items.hashCode()
|
||||
result = 31 * result + (timeAgo?.hashCode() ?: 0)
|
||||
result = 31 * result + showAllButtonText.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -48,7 +53,8 @@ sealed class LibraryGroupModel(
|
||||
class Favourites(
|
||||
items: List<MangaItemModel>,
|
||||
val category: FavouriteCategory,
|
||||
) : LibraryGroupModel(items) {
|
||||
showAllButtonText: Int,
|
||||
) : LibraryGroupModel(items, showAllButtonText) {
|
||||
|
||||
override val key: Any
|
||||
get() = category.id
|
||||
@@ -63,8 +69,9 @@ sealed class LibraryGroupModel(
|
||||
|
||||
other as Favourites
|
||||
|
||||
if (items != other.items) return false
|
||||
if (category != other.category) return false
|
||||
if (showAllButtonText != other.showAllButtonText) return false
|
||||
if (items != other.items) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -72,6 +79,7 @@ sealed class LibraryGroupModel(
|
||||
override fun hashCode(): Int {
|
||||
var result = items.hashCode()
|
||||
result = 31 * result + category.hashCode()
|
||||
result = 31 * result + showAllButtonText.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,13 +109,15 @@ class MainActivity :
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
if (isSearchOpened()) {
|
||||
val isSearchOpened = isSearchOpened()
|
||||
if (isSearchOpened) {
|
||||
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||
scrollFlags = SCROLL_FLAG_NO_SCROLL
|
||||
}
|
||||
binding.appbar.setBackgroundColor(getThemeColor(materialR.attr.colorSurfaceVariant))
|
||||
binding.appbar.updatePadding(left = 0, right = 0)
|
||||
}
|
||||
adjustFabVisibility(isSearchOpened = isSearchOpened)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
@@ -127,7 +129,7 @@ class MainActivity :
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
runOnCommit { onSearchClosed() }
|
||||
}
|
||||
else -> binding.searchView.requestFocus()
|
||||
else -> super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,11 +220,13 @@ class MainActivity :
|
||||
|
||||
override fun onSupportActionModeStarted(mode: ActionMode) {
|
||||
super.onSupportActionModeStarted(mode)
|
||||
adjustFabVisibility()
|
||||
showBottomNav(false)
|
||||
}
|
||||
|
||||
override fun onSupportActionModeFinished(mode: ActionMode) {
|
||||
super.onSupportActionModeFinished(mode)
|
||||
adjustFabVisibility()
|
||||
showBottomNav(true)
|
||||
}
|
||||
|
||||
@@ -330,7 +334,12 @@ class MainActivity :
|
||||
isSearchOpened: Boolean = isSearchOpened(),
|
||||
) {
|
||||
val fab = binding.fab
|
||||
if (isResumeEnabled && !isSearchOpened && topFragment is LibraryFragment) {
|
||||
if (
|
||||
isResumeEnabled &&
|
||||
!actionModeDelegate.isActionModeStarted &&
|
||||
!isSearchOpened &&
|
||||
topFragment is LibraryFragment
|
||||
) {
|
||||
if (!fab.isVisible) {
|
||||
fab.show()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user