diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 22bad7dd9..4b0cf6180 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,14 +57,19 @@ + + + diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryActivity.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryActivity.kt new file mode 100644 index 000000000..64b629a65 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryActivity.kt @@ -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() { + + 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 { + topMargin = insets.top + } + } + binding.container.updatePadding( + bottom = insets.bottom + ) + } + + companion object { + + fun newIntent(context: Context) = Intent(context, HistoryActivity::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt index ecffa7f33..2d131e85f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt @@ -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(), MangaListListene val sizeResolver = ItemSizeResolver(resources, get()) val itemCLickListener = object : OnListItemClickListener { override fun onItemClick(item: LibraryGroupModel, view: View) { - + onGroupClick(item, view) } } selectionDecoration = MangaSelectionDecoration(view.context) @@ -153,6 +153,14 @@ class LibraryFragment : BaseFragment(), 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 { val ids = selectionDecoration?.checkedItemsIds if (ids.isNullOrEmpty()) { diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt index 17b9d9ced..47b38d652 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt @@ -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 { val result = ArrayList(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): List { + private suspend fun mapHistory(list: List): List { val showPercent = settings.isReadingIndicatorsEnabled - val result = ArrayList(list.size) + val groups = ArrayList() + val map = HashMap>() 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(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 + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt index 48d50fb0e..99e892c25 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt @@ -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) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryGroupAD.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryGroupAD.kt index 309b056d1..dca6757c4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryGroupAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryGroupAD.kt @@ -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( + 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() } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/MangaItemDiffCallback.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/MangaItemDiffCallback.kt new file mode 100644 index 000000000..e39d60454 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/MangaItemDiffCallback.kt @@ -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() { + + 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibraryGroupModel.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibraryGroupModel.kt index c1cccbf02..20dc18e2f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibraryGroupModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibraryGroupModel.kt @@ -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 + val items: List, + @StringRes val showAllButtonText: Int, ) : ListModel { abstract val key: Any @@ -17,7 +19,8 @@ sealed class LibraryGroupModel( class History( items: List, 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, 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 } } diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt index 445316cc0..b6a46f2dd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -109,13 +109,15 @@ class MainActivity : override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) - if (isSearchOpened()) { + val isSearchOpened = isSearchOpened() + if (isSearchOpened) { binding.toolbarCard.updateLayoutParams { 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() }