Update category adapter

This commit is contained in:
len 2017-01-13 19:35:20 +01:00
parent e4d4dbbeb6
commit 7b9f5d0e9f
10 changed files with 159 additions and 296 deletions

View file

@ -1,36 +0,0 @@
package eu.kanade.tachiyomi.ui.base.adapter
/**
* Interface to listen for a move or dismissal event from a [ItemTouchHelper.Callback].
*
* @author Paul Burke (ipaulpro)
*/
interface ItemTouchHelperAdapter {
/**
* Called when an item has been dragged far enough to trigger a move. This is called every time
* an item is shifted, and **not** at the end of a "drop" event.
*
* Implementations should call [RecyclerView.Adapter.notifyItemMoved] after
* adjusting the underlying data to reflect this move.
*
* @param fromPosition The start position of the moved item.
* @param toPosition Then resolved position of the moved item.
* @see [RecyclerView.getAdapterPositionFor]
* @see [RecyclerView.ViewHolder.getAdapterPosition]
*/
fun onItemMove(fromPosition: Int, toPosition: Int)
/**
* Called when an item has been dismissed by a swipe.
*
* Implementations should call [RecyclerView.Adapter.notifyItemRemoved] after
* adjusting the underlying data to reflect this removal.
*
* @param position The position of the item dismissed.
* @see RecyclerView.getAdapterPositionFor
* @see RecyclerView.ViewHolder.getAdapterPosition
*/
fun onItemDismiss(position: Int)
}

View file

@ -1,13 +0,0 @@
package eu.kanade.tachiyomi.ui.base.adapter
import android.support.v7.widget.RecyclerView
interface OnStartDragListener {
/**
* Called when a view is requesting a start of a drag.
*
* @param viewHolder The holder of the view to drag.
*/
fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
}

View file

@ -1,28 +0,0 @@
package eu.kanade.tachiyomi.ui.base.adapter
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
open class SimpleItemTouchHelperCallback(private val adapter: ItemTouchHelperAdapter) : ItemTouchHelper.Callback() {
override fun isLongPressDragEnabled() = true
override fun isItemViewSwipeEnabled() = true
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
return ItemTouchHelper.Callback.makeMovementFlags(dragFlags, swipeFlags)
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean {
adapter.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
adapter.onItemDismiss(viewHolder.adapterPosition)
}
}

View file

@ -6,16 +6,15 @@ import android.os.Bundle
import android.support.v7.view.ActionMode
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.view.Menu
import android.view.MenuItem
import android.view.View
import com.afollestad.materialdialogs.MaterialDialog
import eu.davidea.flexibleadapter4.FlexibleAdapter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.helpers.UndoHelper
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener
import kotlinx.android.synthetic.main.activity_edit_categories.*
import kotlinx.android.synthetic.main.toolbar.*
import nucleus.factory.RequiresPresenter
@ -29,7 +28,10 @@ import nucleus.factory.RequiresPresenter
@RequiresPresenter(CategoryPresenter::class)
class CategoryActivity :
BaseRxActivity<CategoryPresenter>(),
ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener, OnStartDragListener {
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
UndoHelper.OnUndoListener {
/**
* Object used to show actionMode toolbar.
@ -41,11 +43,6 @@ class CategoryActivity :
*/
private lateinit var adapter: CategoryAdapter
/**
* TouchHelper used for reorder animation and movement.
*/
private lateinit var touchHelper: ItemTouchHelper
companion object {
/**
* Create new CategoryActivity intent.
@ -75,27 +72,17 @@ class CategoryActivity :
recycler.setHasFixedSize(true)
recycler.adapter = adapter
// Touch helper to drag and reorder categories
touchHelper = ItemTouchHelper(CategoryItemTouchHelper(adapter))
touchHelper.attachToRecyclerView(recycler)
adapter.isHandleDragEnabled = true
// Create OnClickListener for creating new category
fab.setOnClickListener({ v ->
fab.setOnClickListener {
MaterialDialog.Builder(this)
.title(R.string.action_add_category)
.negativeText(android.R.string.cancel)
.input(R.string.name, 0, false)
{ dialog, input -> presenter.createCategory(input.toString()) }
.show()
})
}
/**
* Finishes action mode.
* Call this when action mode action is finished.
*/
fun destroyActionModeIfNeeded() {
actionMode?.finish()
}
}
/**
@ -103,19 +90,13 @@ class CategoryActivity :
*
* @param categories list containing categories
*/
fun setCategories(categories: List<Category>) {
destroyActionModeIfNeeded()
adapter.setItems(categories)
}
/**
* Returns the selected categories
*
* @return list of selected categories
*/
private fun getSelectedCategories(): List<Category> {
// Create a list of the selected categories
return adapter.selectedItems.map { adapter.getItem(it) }
fun setCategories(categories: List<CategoryItem>) {
actionMode?.finish()
adapter.updateDataSet(categories)
val selected = categories.filter { it.isSelected }
if (selected.isNotEmpty()) {
selected.forEach { onItemLongClick(categories.indexOf(it)) }
}
}
/**
@ -127,51 +108,11 @@ class CategoryActivity :
MaterialDialog.Builder(this)
.title(R.string.action_rename_category)
.negativeText(android.R.string.cancel)
.onNegative { materialDialog, dialogAction -> destroyActionModeIfNeeded() }
.input(getString(R.string.name), category.name, false)
{ dialog, input -> presenter.renameCategory(category, input.toString()) }
.show()
}
/**
* Toggle actionMode selection
*
* @param position position of selected item
*/
private fun toggleSelection(position: Int) {
adapter.toggleSelection(position, false)
// Get selected item count
val count = adapter.selectedItemCount
// If no item is selected finish action mode
if (count == 0) {
actionMode?.finish()
} else {
// This block will only run if actionMode is not null
actionMode?.let {
// Set title equal to selected item
it.title = getString(R.string.label_selected, count)
it.invalidate()
// Show edit button only when one item is selected
val editItem = it.menu.findItem(R.id.action_edit)
editItem.isVisible = count == 1
}
}
}
/**
* Called each time the action mode is shown.
* Always called after onCreateActionMode
*
* @return false
*/
override fun onPrepareActionMode(actionMode: ActionMode, menu: Menu): Boolean {
return false
}
/**
* Called when action mode item clicked.
*
@ -183,12 +124,26 @@ class CategoryActivity :
override fun onActionItemClicked(actionMode: ActionMode, menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.action_delete -> {
// Delete select categories.
presenter.deleteCategories(getSelectedCategories())
UndoHelper(adapter, this)
.withAction(UndoHelper.ACTION_REMOVE, object : UndoHelper.OnActionListener {
override fun onPreAction(): Boolean {
adapter.selectedPositions.forEach { adapter.getItem(it).isSelected = false }
return false
}
override fun onPostAction() {
actionMode.finish()
}
})
.remove(adapter.selectedPositions, recycler.parent as View,
R.string.snack_categories_deleted, R.string.action_undo, 3000)
}
R.id.action_edit -> {
// Edit selected category
editCategory(getSelectedCategories()[0])
if (adapter.selectedItemCount == 1) {
val position = adapter.selectedPositions.first()
editCategory(adapter.getItem(position).category)
}
}
else -> return false
}
@ -211,6 +166,22 @@ class CategoryActivity :
return true
}
/**
* Called each time the action mode is shown.
* Always called after onCreateActionMode
*
* @return false
*/
override fun onPrepareActionMode(actionMode: ActionMode, menu: Menu): Boolean {
val count = adapter.selectedItemCount
actionMode.title = getString(R.string.label_selected, count)
// Show edit button only when one item is selected
val editItem = actionMode.menu.findItem(R.id.action_edit)
editItem.isVisible = count == 1
return true
}
/**
* Called when action mode destroyed.
*
@ -218,8 +189,7 @@ class CategoryActivity :
*/
override fun onDestroyActionMode(mode: ActionMode?) {
// Reset adapter to single selection
adapter.mode = FlexibleAdapter.MODE_SINGLE
// Clear selected items
adapter.mode = FlexibleAdapter.MODE_IDLE
adapter.clearSelection()
actionMode = null
}
@ -229,11 +199,9 @@ class CategoryActivity :
*
* @param position position of clicked item.
*/
override fun onListItemClick(position: Int): Boolean {
override fun onItemClick(position: Int): Boolean {
// Check if action mode is initialized and selected item exist.
if (position == -1) {
return false
} else if (actionMode != null) {
if (actionMode != null && position != RecyclerView.NO_POSITION) {
toggleSelection(position)
return true
} else {
@ -246,24 +214,52 @@ class CategoryActivity :
*
* @param position position of clicked item.
*/
override fun onListItemLongClick(position: Int) {
override fun onItemLongClick(position: Int) {
// Check if action mode is initialized.
if (actionMode == null)
// Initialize action mode
if (actionMode == null) {
// Initialize action mode
actionMode = startSupportActionMode(this)
}
// Set item as selected
toggleSelection(position)
}
/**
* Called when item is dragged
*
* @param viewHolder view that contains dragged item
* Toggle the selection state of an item.
* If the item was the last one in the selection and is unselected, the ActionMode is finished.
*/
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
// Notify touchHelper
touchHelper.startDrag(viewHolder)
private fun toggleSelection(position: Int) {
//Mark the position selected
adapter.toggleSelection(position)
if (adapter.selectedItemCount == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
}
}
/**
* Called when an item is released from a drag.
*/
fun onItemReleased() {
val categories = (0..adapter.itemCount-1).map { adapter.getItem(it).category }
presenter.reorderCategories(categories)
}
/**
* Called when the undo action is clicked in the snackbar.
*/
override fun onUndoConfirmed(action: Int) {
adapter.restoreDeletedItems()
}
/**
* Called when the time to restore the items expires.
*/
override fun onDeleteConfirmed(action: Int) {
presenter.deleteCategories(adapter.deletedItems.map { it.category })
}
}

View file

@ -1,12 +1,6 @@
package eu.kanade.tachiyomi.ui.category
import android.view.ViewGroup
import eu.davidea.flexibleadapter4.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter
import eu.kanade.tachiyomi.util.inflate
import java.util.*
import eu.davidea.flexibleadapter.FlexibleAdapter
/**
* Adapter of CategoryHolder.
@ -17,85 +11,24 @@ import java.util.*
* @constructor Creates a CategoryAdapter object
*/
class CategoryAdapter(private val activity: CategoryActivity) :
FlexibleAdapter<CategoryHolder, Category>(), ItemTouchHelperAdapter {
init {
// Set unique id's
setHasStableIds(true)
}
FlexibleAdapter<CategoryItem>(null, activity, true) {
/**
* Called when ViewHolder is created
*
* @param parent parent View
* @param viewType int containing viewType
* Called when item is released.
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryHolder {
// Inflate layout with item_edit_categories.xml
val view = parent.inflate(R.layout.item_edit_categories)
return CategoryHolder(view, this, activity, activity)
}
/**
* Called when ViewHolder is bind
*
* @param holder bind holder
* @param position position of holder
*/
override fun onBindViewHolder(holder: CategoryHolder, position: Int) {
// Update holder values.
val category = getItem(position)
holder.onSetValues(category)
//When user scrolls this bind the correct selection status
holder.itemView.isActivated = isSelected(position)
}
/**
* Update items with list of categories
*
* @param items list of categories
*/
fun setItems(items: List<Category>) {
mItems = ArrayList(items)
notifyDataSetChanged()
}
/**
* Get category by position
*
* @param position position of item
*/
override fun getItemId(position: Int): Long {
return mItems[position].id!!.toLong()
}
/**
* Called when item is moved
*
* @param fromPosition previous position of item.
* @param toPosition new position of item.
*/
override fun onItemMove(fromPosition: Int, toPosition: Int) {
// Move items and notify touch helper
Collections.swap(mItems, fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
fun onItemReleased() {
// Update database
activity.presenter.reorderCategories(mItems)
activity.onItemReleased()
}
/**
* Must be implemented, not used
*/
override fun onItemDismiss(position: Int) {
// Empty method.
override fun clearSelection() {
super.clearSelection()
(0..itemCount-1).forEach { getItem(it).isSelected = false }
}
/**
* Must be implemented, not used
*/
override fun updateDataSet(p0: String?) {
// Empty method.
override fun toggleSelection(position: Int) {
super.toggleSelection(position)
getItem(position).isSelected = isSelected(position)
}
}

View file

@ -2,14 +2,11 @@ package eu.kanade.tachiyomi.ui.category
import android.graphics.Color
import android.graphics.Typeface
import android.support.v4.view.MotionEventCompat
import android.view.MotionEvent
import android.view.View
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener
import kotlinx.android.synthetic.main.item_edit_categories.view.*
/**
@ -19,17 +16,10 @@ import kotlinx.android.synthetic.main.item_edit_categories.view.*
*
* @param view view of category item.
* @param adapter adapter belonging to holder.
* @param listener called when item clicked.
* @param dragListener called when item dragged.
*
* @constructor Create CategoryHolder object
*/
class CategoryHolder(
view: View,
adapter: CategoryAdapter,
listener: FlexibleViewHolder.OnListItemClickListener,
dragListener: OnStartDragListener
) : FlexibleViewHolder(view, adapter, listener) {
class CategoryHolder(view: View, val adapter: CategoryAdapter) : FlexibleViewHolder(view, adapter) {
init {
// Create round letter image onclick to simulate long click
@ -38,13 +28,7 @@ class CategoryHolder(
onLongClick(view)
}
// Set on touch listener for reorder image
itemView.reorder.setOnTouchListener { v, event ->
if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
dragListener.onStartDrag(this)
}
false
}
setDragHandleView(itemView.reorder)
}
/**
@ -52,7 +36,7 @@ class CategoryHolder(
*
* @param category category of item.
*/
fun onSetValues(category: Category) {
fun bind(category: Category) {
// Set capitalized title.
itemView.title.text = category.name.capitalize()
@ -78,4 +62,10 @@ class CategoryHolder(
.endConfig()
.buildRound(text, ColorGenerator.MATERIAL.getColor(text))
}
override fun onItemReleased(position: Int) {
super.onItemReleased(position)
adapter.onItemReleased()
}
}

View file

@ -0,0 +1,44 @@
package eu.kanade.tachiyomi.ui.category
import android.view.LayoutInflater
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.util.inflate
class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder>() {
var isSelected = false
override fun getLayoutRes(): Int {
return R.layout.item_edit_categories
}
override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater,
parent: ViewGroup): CategoryHolder {
return CategoryHolder(parent.inflate(layoutRes), adapter as CategoryAdapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: CategoryHolder,
position: Int, payloads: List<Any?>?) {
holder.bind(category)
}
override fun isDraggable(): Boolean {
return true
}
override fun equals(other: Any?): Boolean {
if (other is CategoryItem) {
return category.id == other.category.id
}
return false
}
override fun hashCode(): Int {
return category.id!!
}
}

View file

@ -1,26 +0,0 @@
package eu.kanade.tachiyomi.ui.category
import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter
import eu.kanade.tachiyomi.ui.base.adapter.SimpleItemTouchHelperCallback
class CategoryItemTouchHelper(adapter: ItemTouchHelperAdapter) : SimpleItemTouchHelperCallback(adapter) {
/**
* Disable items swipe remove
*
* @return false
*/
override fun isItemViewSwipeEnabled(): Boolean {
return false
}
/**
* Disable long press item drag
*
* @return false
*/
override fun isLongPressDragEnabled(): Boolean {
return false
}
}

View file

@ -31,6 +31,7 @@ class CategoryPresenter : BasePresenter<CategoryActivity>() {
db.getCategories().asRxObservable()
.doOnNext { categories = it }
.map { it.map(::CategoryItem) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(CategoryActivity::setCategories)
}

View file

@ -71,6 +71,7 @@
<string name="action_share">Share</string>
<string name="action_save">Save</string>
<string name="action_reset">Reset</string>
<string name="action_undo">Undo</string>
<!-- Operations -->
<string name="deleting">Deleting…</string>
@ -286,6 +287,7 @@
<!-- Category activity -->
<string name="error_category_exists">A category with this name already exists!</string>
<string name="snack_categories_deleted">Categories deleted</string>
<!-- Dialog option with checkbox view -->
<string name="dialog_with_checkbox_remove_description">This will remove the read date of this chapter. Are you sure?</string>