Favourites manage activity

This commit is contained in:
Koitharu
2023-07-21 10:07:43 +03:00
parent 5785a2d5d1
commit 45b2f2337a
8 changed files with 89 additions and 124 deletions

View File

@@ -26,7 +26,7 @@ data class FavouriteCategory(
if (previousState !is FavouriteCategory) {
return null
}
return if (isTrackingEnabled != previousState.isTrackingEnabled || isVisibleInLibrary != isVisibleInLibrary) {
return if (isTrackingEnabled != previousState.isTrackingEnabled || isVisibleInLibrary != previousState.isVisibleInLibrary) {
ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED
} else {
null

View File

@@ -3,16 +3,10 @@ package org.koitharu.kotatsu.favourites.ui.categories
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.transition.Fade
import android.transition.TransitionManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.ItemTouchHelper
@@ -48,16 +42,14 @@ class FavouriteCategoriesActivity :
private val viewModel by viewModels<FavouritesCategoriesViewModel>()
private lateinit var exitReorderModeCallback: ExitReorderModeCallback
private lateinit var adapter: CategoriesAdapter
private lateinit var selectionController: ListSelectionController
private var reorderHelper: ItemTouchHelper? = null
private lateinit var reorderHelper: ItemTouchHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
exitReorderModeCallback = ExitReorderModeCallback(viewModel)
adapter = CategoriesAdapter(coil, this, this, this)
selectionController = ListSelectionController(
activity = this,
@@ -65,49 +57,27 @@ class FavouriteCategoriesActivity :
registryOwner = this,
callback = CategoriesSelectionCallback(viewBinding.recyclerView, viewModel),
)
viewBinding.buttonDone.setOnClickListener(this)
selectionController.attachToRecyclerView(viewBinding.recyclerView)
viewBinding.recyclerView.setHasFixedSize(true)
viewBinding.recyclerView.adapter = adapter
viewBinding.fabAdd.setOnClickListener(this)
onBackPressedDispatcher.addCallback(exitReorderModeCallback)
reorderHelper = ItemTouchHelper(ReorderHelperCallback()).apply {
attachToRecyclerView(viewBinding.recyclerView)
}
viewModel.detalizedCategories.observe(this, ::onCategoriesChanged)
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
viewModel.isInReorderMode.observe(this, ::onReorderModeChanged)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.opt_categories, menu)
return true
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
menu.findItem(R.id.action_reorder)?.isVisible = !viewModel.isInReorderMode() && !viewModel.isEmpty()
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_reorder -> {
viewModel.setReorderMode(true)
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_done -> viewModel.setReorderMode(false)
R.id.fab_add -> startActivity(FavouritesCategoryEditActivity.newIntent(this))
}
}
override fun onItemClick(item: FavouriteCategory, view: View) {
if (viewModel.isInReorderMode() || selectionController.onItemClick(item.id)) {
if (selectionController.onItemClick(item.id)) {
return
}
val intent = FavouritesActivity.newIntent(this, item)
@@ -116,11 +86,12 @@ class FavouriteCategoriesActivity :
}
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
return !viewModel.isInReorderMode() && selectionController.onItemLongClick(item.id)
return selectionController.onItemLongClick(item.id)
}
override fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean {
return reorderHelper?.startDrag(holder) != null
reorderHelper.startDrag(holder)
return true
}
override fun onRetryClick(error: Throwable) = Unit
@@ -147,28 +118,6 @@ class FavouriteCategoriesActivity :
invalidateOptionsMenu()
}
private fun onReorderModeChanged(isReorderMode: Boolean) {
val transition = Fade().apply {
duration = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
}
TransitionManager.beginDelayedTransition(viewBinding.toolbar, transition)
reorderHelper?.attachToRecyclerView(null)
reorderHelper = if (isReorderMode) {
selectionController.clear()
viewBinding.fabAdd.hide()
ItemTouchHelper(ReorderHelperCallback()).apply {
attachToRecyclerView(viewBinding.recyclerView)
}
} else {
viewBinding.fabAdd.show()
null
}
viewBinding.recyclerView.isNestedScrollingEnabled = !isReorderMode
invalidateOptionsMenu()
viewBinding.buttonDone.isVisible = isReorderMode
exitReorderModeCallback.isEnabled = isReorderMode
}
private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.DOWN or ItemTouchHelper.UP,
0,
@@ -202,14 +151,10 @@ class FavouriteCategoriesActivity :
}
override fun isLongPressDragEnabled(): Boolean = false
}
private class ExitReorderModeCallback(
private val viewModel: FavouritesCategoriesViewModel,
) : OnBackPressedCallback(viewModel.isInReorderMode()) {
override fun handleOnBackPressed() {
viewModel.setReorderMode(false)
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
viewBinding.recyclerView.isNestedScrollingEnabled = actionState == ItemTouchHelper.ACTION_STATE_IDLE
}
}

View File

@@ -4,9 +4,8 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
@@ -27,30 +26,26 @@ class FavouritesCategoriesViewModel @Inject constructor(
) : BaseViewModel() {
private var reorderJob: Job? = null
val isInReorderMode = MutableStateFlow(false)
val detalizedCategories = combine(
repository.observeCategoriesWithCovers(),
isInReorderMode,
) { list, reordering ->
list.map { (category, covers) ->
CategoryListModel(
mangaCount = covers.size,
covers = covers.take(3),
category = category,
isReorderMode = reordering,
)
}.ifEmpty {
listOf(
EmptyState(
icon = R.drawable.ic_empty_favourites,
textPrimary = R.string.text_empty_holder_primary,
textSecondary = R.string.empty_favourite_categories,
actionStringRes = 0,
),
)
}
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
val detalizedCategories = repository.observeCategoriesWithCovers()
.map { list ->
list.map { (category, covers) ->
CategoryListModel(
mangaCount = covers.size,
covers = covers.take(3),
category = category,
)
}.ifEmpty {
listOf(
EmptyState(
icon = R.drawable.ic_empty_favourites,
textPrimary = R.string.text_empty_holder_primary,
textSecondary = R.string.empty_favourite_categories,
actionStringRes = 0,
),
)
}
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
fun deleteCategory(id: Long) {
launchJob {
@@ -68,14 +63,8 @@ class FavouritesCategoriesViewModel @Inject constructor(
settings.isAllFavouritesVisible = isVisible
}
fun isInReorderMode(): Boolean = isInReorderMode.value
fun isEmpty(): Boolean = detalizedCategories.value.none { it is CategoryListModel }
fun setReorderMode(isReorderMode: Boolean) {
isInReorderMode.value = isReorderMode
}
fun reorderCategories(oldPos: Int, newPos: Int) {
val prevJob = reorderJob
reorderJob = launchJob(Dispatchers.Default) {

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.favourites.ui.categories.adapter
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
@@ -25,6 +26,7 @@ import org.koitharu.kotatsu.databinding.ItemCategoryBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
import org.koitharu.kotatsu.list.ui.model.ListModel
@SuppressLint("ClickableViewAccessibility")
fun categoryAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
@@ -35,8 +37,7 @@ fun categoryAD(
val eventListener = object : OnClickListener, OnLongClickListener, OnTouchListener {
override fun onClick(v: View) = clickListener.onItemClick(item.category, binding.imageViewCover1)
override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, binding.imageViewCover1)
override fun onTouch(v: View?, event: MotionEvent): Boolean = item.isReorderMode &&
event.actionMasked == MotionEvent.ACTION_DOWN &&
override fun onTouch(v: View?, event: MotionEvent): Boolean = event.actionMasked == MotionEvent.ACTION_DOWN &&
clickListener.onDragHandleTouch(this@adapterDelegateViewBinding)
}
val backgroundColor = context.getThemeColor(android.R.attr.colorBackground)
@@ -57,10 +58,9 @@ fun categoryAD(
val crossFadeDuration = context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt()
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
itemView.setOnTouchListener(eventListener)
binding.imageViewHandle.setOnTouchListener(eventListener)
bind { payloads ->
binding.imageViewHandle.isVisible = item.isReorderMode
if (payloads.isNotEmpty()) {
return@bind
}
@@ -74,6 +74,8 @@ fun categoryAD(
item.mangaCount,
)
}
binding.imageViewTracker.isVisible = item.category.isTrackingEnabled
binding.imageViewVisible.isVisible = item.category.isVisibleInLibrary
repeat(coverViews.size) { i ->
val cover = item.covers.getOrNull(i)
coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run {

View File

@@ -8,7 +8,6 @@ class CategoryListModel(
val mangaCount: Int,
val covers: List<Cover>,
val category: FavouriteCategory,
val isReorderMode: Boolean,
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {
@@ -22,20 +21,26 @@ class CategoryListModel(
other as CategoryListModel
if (mangaCount != other.mangaCount) return false
if (isReorderMode != other.isReorderMode) return false
if (covers != other.covers) return false
if (category.id != other.category.id) return false
if (category.title != other.category.title) return false
return category.order == other.category.order
// ignore the category.sortKey field
if (category.order != other.category.order) return false
if (category.createdAt != other.category.createdAt) return false
if (category.isTrackingEnabled != other.category.isTrackingEnabled) return false
return category.isVisibleInLibrary == other.category.isVisibleInLibrary
}
override fun hashCode(): Int {
var result = mangaCount
result = 31 * result + isReorderMode.hashCode()
result = 31 * result + covers.hashCode()
result = 31 * result + category.id.hashCode()
result = 31 * result + category.title.hashCode()
// ignore the category.sortKey field
result = 31 * result + category.order.hashCode()
result = 31 * result + category.createdAt.hashCode()
result = 31 * result + category.isTrackingEnabled.hashCode()
result = 31 * result + category.isVisibleInLibrary.hashCode()
return result
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?><!-- drawable/eye_outline.xml -->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M12,4.5C17,4.5 21.27,7.61 23,12C21.27,16.39 17,19.5 12,19.5C7,19.5 2.73,16.39 1,12C2.73,7.61 7,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C15.76,17.5 19.17,15.36 20.82,12C19.17,8.64 15.76,6.5 12,6.5C8.24,6.5 4.83,8.64 3.18,12Z" />
</vector>

View File

@@ -84,16 +84,42 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="4dp"
android:layout_marginEnd="?listPreferredItemPaddingEnd"
android:layout_marginEnd="8dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/imageView_handle"
app:layout_constraintEnd_toStartOf="@id/imageView_tracker"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
app:layout_constraintTop_toBottomOf="@id/textView_title"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintWidth_default="wrap"
tools:text="@tools:sample/lorem[1]" />
<ImageView
android:id="@+id/imageView_tracker"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/check_for_new_chapters"
app:layout_constraintBottom_toBottomOf="@id/textView_subtitle"
app:layout_constraintEnd_toStartOf="@id/imageView_visible"
app:layout_constraintStart_toEndOf="@id/textView_subtitle"
app:layout_constraintTop_toTopOf="@id/textView_subtitle"
app:srcCompat="@drawable/ic_notification" />
<ImageView
android:id="@+id/imageView_visible"
android:layout_width="16dp"
android:layout_height="16dp"
android:contentDescription="@string/show_on_shelf"
app:layout_constraintBottom_toBottomOf="@id/textView_subtitle"
app:layout_constraintEnd_toEndOf="@id/textView_title"
app:layout_constraintStart_toEndOf="@id/imageView_tracker"
app:layout_constraintTop_toTopOf="@id/textView_subtitle"
app:srcCompat="@drawable/ic_eye" />
<ImageView
android:id="@+id/imageView_handle"
android:layout_width="wrap_content"
@@ -102,10 +128,8 @@
android:contentDescription="@string/reorder"
android:padding="@dimen/margin_normal"
android:src="@drawable/ic_reorder_handle"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_reorder"
android:icon="@drawable/ic_reorder"
android:title="@string/reorder"
app:showAsAction="ifRoom" />
</menu>