diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/ItemTouchHelperAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/ItemTouchHelperAdapter.kt deleted file mode 100644 index 8fbb68fc3..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/ItemTouchHelperAdapter.kt +++ /dev/null @@ -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) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/OnStartDragListener.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/OnStartDragListener.kt deleted file mode 100644 index 3589c1201..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/OnStartDragListener.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/SimpleItemTouchHelperCallback.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/SimpleItemTouchHelperCallback.kt deleted file mode 100644 index bfbbecc08..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/SimpleItemTouchHelperCallback.kt +++ /dev/null @@ -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) - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt index 67023fdd4..28c4f7bbc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt @@ -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(), - 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) { - destroyActionModeIfNeeded() - adapter.setItems(categories) - } - - /** - * Returns the selected categories - * - * @return list of selected categories - */ - private fun getSelectedCategories(): List { - // Create a list of the selected categories - return adapter.selectedItems.map { adapter.getItem(it) } + fun setCategories(categories: List) { + 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 }) } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt index a5f796a74..899ca65a3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt @@ -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(), ItemTouchHelperAdapter { - - init { - // Set unique id's - setHasStableIds(true) - } + FlexibleAdapter(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) { - 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) } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt index b9cec3445..35f58a7b5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt @@ -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() + } + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt new file mode 100644 index 000000000..3dd3ad5a7 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt @@ -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() { + + 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?) { + 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!! + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt deleted file mode 100644 index 1e1ad8df3..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt +++ /dev/null @@ -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 - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt index 9f129a150..b8691aa5d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt @@ -31,6 +31,7 @@ class CategoryPresenter : BasePresenter() { db.getCategories().asRxObservable() .doOnNext { categories = it } + .map { it.map(::CategoryItem) } .observeOn(AndroidSchedulers.mainThread()) .subscribeLatestCache(CategoryActivity::setCategories) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1f77b50e4..4d14b8079 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,6 +71,7 @@ Share Save Reset + Undo Deleting… @@ -286,6 +287,7 @@ A category with this name already exists! + Categories deleted This will remove the read date of this chapter. Are you sure?