Bläddra i källkod

Update category adapter

len 8 år sedan
förälder
incheckning
7b9f5d0e9f

+ 0 - 36
app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/ItemTouchHelperAdapter.kt

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

+ 0 - 13
app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/OnStartDragListener.kt

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

+ 0 - 28
app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/SimpleItemTouchHelperCallback.kt

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

+ 90 - 94
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<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.
+     */
+    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 onStartDrag(viewHolder: RecyclerView.ViewHolder) {
-        // Notify touchHelper
-        touchHelper.startDrag(viewHolder)
+    override fun onDeleteConfirmed(action: Int) {
+        presenter.deleteCategories(adapter.deletedItems.map { it.category })
     }
 
 }

+ 12 - 79
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<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)
     }
+
 }

+ 10 - 20
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()
+    }
+
 }

+ 44 - 0
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<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!!
+    }
+
+}

+ 0 - 26
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt

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

+ 1 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt

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

+ 2 - 0
app/src/main/res/values/strings.xml

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