Improve library list

This commit is contained in:
Koitharu
2022-07-04 16:39:26 +03:00
parent e4a2897731
commit e0d93b0630
9 changed files with 175 additions and 19 deletions

View File

@@ -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)
}
}

View File

@@ -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()) {

View File

@@ -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
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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()
}