From 39606e2676a2935a54aae6bd2c3944fb8c7abd46 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 9 Oct 2019 20:16:11 +0200 Subject: [PATCH] Custom Toolbar action --- .../keepass/activities/GroupActivity.kt | 141 ++++++++---------- .../keepass/activities/ListNodesFragment.kt | 118 +++++++++------ .../kunzisoft/keepass/adapters/NodeAdapter.kt | 4 +- .../kunzisoft/keepass/view/ToolbarAction.kt | 110 ++++++++++++++ .../com/kunzisoft/keepass/view/ViewUtil.kt | 11 +- app/src/main/res/layout/activity_group.xml | 6 +- app/src/main/res/values/style_purple.xml | 2 +- app/src/main/res/values/style_red.xml | 2 +- app/src/main/res/values/styles.xml | 2 +- 9 files changed, 260 insertions(+), 136 deletions(-) create mode 100644 app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 013737e24..90bd4874b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -61,8 +61,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.view.AddNodeButtonView -import com.kunzisoft.keepass.view.collapse -import com.kunzisoft.keepass.view.expand +import com.kunzisoft.keepass.view.ToolbarAction class GroupActivity : LockingActivity(), GroupEditDialogFragment.EditGroupListener, @@ -75,7 +74,7 @@ class GroupActivity : LockingActivity(), // Views private var toolbar: Toolbar? = null private var searchTitleView: View? = null - private var toolbarPaste: Toolbar? = null + private var toolbarAction: ToolbarAction? = null private var iconView: ImageView? = null private var numberChildrenView: TextView? = null private var modeTitleView: TextView? = null @@ -116,20 +115,19 @@ class GroupActivity : LockingActivity(), toolbar = findViewById(R.id.toolbar) searchTitleView = findViewById(R.id.search_title) groupNameView = findViewById(R.id.group_name) - toolbarPaste = findViewById(R.id.toolbar_paste) + toolbarAction = findViewById(R.id.toolbar_action) modeTitleView = findViewById(R.id.mode_title_view) toolbar?.title = "" setSupportActionBar(toolbar) - toolbarPaste?.inflateMenu(R.menu.node_paste_menu) - toolbarPaste?.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp) - toolbarPaste?.collapse(false) - toolbarPaste?.setNavigationOnClickListener { - toolbarPaste?.collapse() + /* + toolbarAction?.setNavigationOnClickListener { + toolbarAction?.collapse() mNodeToCopy = null mNodeToMove = null } + */ // Focus view to reinitialize timeout resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView) @@ -139,13 +137,13 @@ class GroupActivity : LockingActivity(), if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY)) mOldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY) if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) { - mNodeToCopy = savedInstanceState.getParcelable(NODE_TO_COPY_KEY) - toolbarPaste?.setOnMenuItemClickListener(OnCopyMenuItemClickListener()) - toolbarPaste?.expand(false) + // mNodeToCopy = savedInstanceState.getParcelable(NODE_TO_COPY_KEY) + // toolbarAction?.setOnMenuItemClickListener(OnCopyMenuItemClickListener()) + // toolbarAction?.expand(false) } else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) { - mNodeToMove = savedInstanceState.getParcelable(NODE_TO_MOVE_KEY) - toolbarPaste?.setOnMenuItemClickListener(OnMoveMenuItemClickListener()) - toolbarPaste?.expand(false) + // mNodeToMove = savedInstanceState.getParcelable(NODE_TO_MOVE_KEY) + // toolbarAction?.setOnMenuItemClickListener(OnMoveMenuItemClickListener()) + // toolbarAction?.expand(false) } } @@ -445,34 +443,29 @@ class GroupActivity : LockingActivity(), private var actionNodeMode: ActionMode? = null - private fun closeNodeActionBar() { - actionNodeMode?.finish() - actionNodeMode = null - } - override fun onNodeSelected(nodes: List): Boolean { if (nodes.isNotEmpty()) { if (actionNodeMode == null) { mListNodesFragment?.actionNodesCallback(nodes, this)?.let { - actionNodeMode = startSupportActionMode(it) + actionNodeMode = toolbarAction?.startSupportActionMode(it) } } else { actionNodeMode?.invalidate() } } else { - closeNodeActionBar() + actionNodeMode?.finish() } return true } override fun onOpenMenuClick(node: NodeVersioned): Boolean { - closeNodeActionBar() + actionNodeMode?.finish() onNodeClick(node) return true } override fun onEditMenuClick(node: NodeVersioned): Boolean { - closeNodeActionBar() + actionNodeMode?.finish() when (node.type) { Type.GROUP -> { mOldGroupToUpdate = node as GroupVersioned @@ -486,35 +479,12 @@ class GroupActivity : LockingActivity(), } override fun onCopyMenuClick(nodes: List): Boolean { + actionNodeMode?.invalidate() + val node = nodes[0] - closeNodeActionBar() // TODO Multiple copy - toolbarPaste?.expand() mNodeToCopy = node - toolbarPaste?.setOnMenuItemClickListener(OnCopyMenuItemClickListener()) - return false - } - - private inner class OnCopyMenuItemClickListener : Toolbar.OnMenuItemClickListener { - override fun onMenuItemClick(item: MenuItem): Boolean { - toolbarPaste?.collapse() - - when (item.itemId) { - R.id.menu_paste -> { - when (mNodeToCopy?.type) { - Type.GROUP -> Log.e(TAG, "Copy not allowed for group") - Type.ENTRY -> { - mCurrentGroup?.let { currentGroup -> - copyEntry(mNodeToCopy as EntryVersioned, currentGroup) - } - } - } - mNodeToCopy = null - return true - } - } - return true - } + return true } private fun copyEntry(entryToCopy: EntryVersioned, newParent: GroupVersioned) { @@ -529,39 +499,12 @@ class GroupActivity : LockingActivity(), } override fun onMoveMenuClick(nodes: List): Boolean { + actionNodeMode?.invalidate() + val node = nodes[0] - closeNodeActionBar() // TODO multiple move - toolbarPaste?.expand() mNodeToMove = node - toolbarPaste?.setOnMenuItemClickListener(OnMoveMenuItemClickListener()) - return false - } - - private inner class OnMoveMenuItemClickListener : Toolbar.OnMenuItemClickListener { - override fun onMenuItemClick(item: MenuItem): Boolean { - toolbarPaste?.collapse() - - when (item.itemId) { - R.id.menu_paste -> { - when (mNodeToMove?.type) { - Type.GROUP -> { - mCurrentGroup?.let { currentGroup -> - moveGroup(mNodeToMove as GroupVersioned, currentGroup) - } - } - Type.ENTRY -> { - mCurrentGroup?.let { currentGroup -> - moveEntry(mNodeToMove as EntryVersioned, currentGroup) - } - } - } - mNodeToMove = null - return true - } - } - return true - } + return true } private fun moveGroup(groupToMove: GroupVersioned, newParent: GroupVersioned) { @@ -622,6 +565,42 @@ class GroupActivity : LockingActivity(), }.start() } + override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?, + nodes: List): Boolean { + when (pasteMode) { + ListNodesFragment.PasteMode.PASTE_FROM_COPY -> { + // COPY + when (mNodeToCopy?.type) { + Type.GROUP -> Log.e(TAG, "Copy not allowed for group") + Type.ENTRY -> { + mCurrentGroup?.let { currentGroup -> + copyEntry(mNodeToCopy as EntryVersioned, currentGroup) + } + } + } + mNodeToCopy = null + } + + ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> { + // Move + when (mNodeToMove?.type) { + Type.GROUP -> { + mCurrentGroup?.let { currentGroup -> + moveGroup(mNodeToMove as GroupVersioned, currentGroup) + } + } + Type.ENTRY -> { + mCurrentGroup?.let { currentGroup -> + moveEntry(mNodeToMove as EntryVersioned, currentGroup) + } + } + } + mNodeToMove = null + } + } + return true + } + override fun onResume() { super.onResume() // Refresh the elements @@ -845,7 +824,7 @@ class GroupActivity : LockingActivity(), && positionNode != null) { mListNodesFragment?.removeNodeAt(positionNode) } else { - // else use the old Node that was the entry unchanged with the old parent + // Use the old Node that was the entry unchanged with the old parent actionNodeValues.oldNode?.let { oldNode -> mListNodesFragment?.removeNode(oldNode) } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt index 3ea63bf78..01793cd23 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt @@ -40,8 +40,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis private set private var mAdapter: NodeAdapter? = null - private var nodeActionMode = false - private val listNodesSelected = LinkedList() + private var nodeActionSelectionMode = false + private val listActionNodes = LinkedList() private var notFoundView: View? = null private var isASearchResult: Boolean = false @@ -100,38 +100,30 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis mAdapter?.apply { setOnNodeClickListener(object : NodeAdapter.NodeClickCallback { override fun onNodeClick(node: NodeVersioned, position: Int) { - if (nodeActionMode) { - if (listNodesSelected.contains(node)) { + if (nodeActionSelectionMode) { + if (listActionNodes.contains(node)) { // Remove selected item if already selected - listNodesSelected.remove(node) + listActionNodes.remove(node) } else { - // TODO multiple selection - if (listNodesSelected.size == 0) { // Add selected item if not already selected - listNodesSelected.add(node) - } + listActionNodes.add(node) } - nodeClickListener?.onNodeSelected(listNodesSelected) - setActionNodes(listNodesSelected) + nodeClickListener?.onNodeSelected(listActionNodes) + setActionNodes(listActionNodes) notifyItemChanged(position) } else { nodeClickListener?.onNodeClick(node) } - - // End the selected mode if no more item selected - if (listNodesSelected.isEmpty()) - nodeActionMode = false } override fun onNodeLongClick(node: NodeVersioned, position: Int): Boolean { // Select the first item after a long click - nodeActionMode = true - if (!listNodesSelected.contains(node)) - listNodesSelected.add(node) + if (!listActionNodes.contains(node)) + listActionNodes.add(node) - nodeClickListener?.onNodeSelected(listNodesSelected) + nodeClickListener?.onNodeSelected(listActionNodes) - setActionNodes(listNodesSelected) + setActionNodes(listActionNodes) notifyItemChanged(position) return true } @@ -257,40 +249,50 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis menuListener: NodesActionMenuListener?) : ActionMode.Callback { return object : ActionMode.Callback { + + private var pasteMode: PasteMode? = null + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + nodeActionSelectionMode = false return true } override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { menu?.clear() - activity?.menuInflater?.inflate(R.menu.node_menu, menu) - val database = Database.getInstance() + if (pasteMode != null) { + mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu) + } else { + nodeActionSelectionMode = true + mode?.menuInflater?.inflate(R.menu.node_menu, menu) - // Open and Edit for a single item - if (nodes.size == 1) { - // Edition - if (readOnly || nodes[0] == database.recycleBin) { + val database = Database.getInstance() + + // Open and Edit for a single item + if (nodes.size == 1) { + // Edition + if (readOnly || nodes[0] == database.recycleBin) { + menu?.removeItem(R.id.menu_edit) + } + } else { + menu?.removeItem(R.id.menu_open) menu?.removeItem(R.id.menu_edit) } - } else { - menu?.removeItem(R.id.menu_open) - menu?.removeItem(R.id.menu_edit) - } - // Copy and Move (not for groups) - if (readOnly - || isASearchResult - || nodes.any { it == database.recycleBin } - || nodes.any { it.type == Type.GROUP }) { - // TODO COPY For Group - menu?.removeItem(R.id.menu_copy) - menu?.removeItem(R.id.menu_move) - } + // Copy and Move (not for groups) + if (readOnly + || isASearchResult + || nodes.any { it == database.recycleBin } + || nodes.any { it.type == Type.GROUP }) { + // TODO COPY For Group + menu?.removeItem(R.id.menu_copy) + menu?.removeItem(R.id.menu_move) + } - // Deletion - if (readOnly || nodes.any { it == database.recycleBin }) { - menu?.removeItem(R.id.menu_delete) + // Deletion + if (readOnly || nodes.any { it == database.recycleBin }) { + menu?.removeItem(R.id.menu_delete) + } } return true @@ -302,16 +304,35 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis return when (item?.itemId) { R.id.menu_open -> menuListener.onOpenMenuClick(nodes[0]) R.id.menu_edit -> menuListener.onEditMenuClick(nodes[0]) - R.id.menu_copy -> menuListener.onCopyMenuClick(nodes) - R.id.menu_move -> menuListener.onMoveMenuClick(nodes) + R.id.menu_copy -> { + pasteMode = PasteMode.PASTE_FROM_COPY + mAdapter?.unselectActionNodes() + val returnValue = menuListener.onCopyMenuClick(nodes) + nodeActionSelectionMode = false + returnValue + } + R.id.menu_move -> { + pasteMode = PasteMode.PASTE_FROM_MOVE + mAdapter?.unselectActionNodes() + val returnValue = menuListener.onMoveMenuClick(nodes) + nodeActionSelectionMode = false + returnValue + } R.id.menu_delete -> menuListener.onDeleteMenuClick(nodes) + R.id.menu_paste -> { + val returnValue = menuListener.onPasteMenuClick(pasteMode, nodes) + mode?.finish() + pasteMode = null + returnValue + } else -> false } } override fun onDestroyActionMode(mode: ActionMode?) { - listNodesSelected.clear() - mAdapter?.removeActionNodes() + listActionNodes.clear() + mAdapter?.unselectActionNodes() + nodeActionSelectionMode = false } } } @@ -375,6 +396,11 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis fun onCopyMenuClick(nodes: List): Boolean fun onMoveMenuClick(nodes: List): Boolean fun onDeleteMenuClick(nodes: List): Boolean + fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List): Boolean + } + + enum class PasteMode { + PASTE_FROM_COPY, PASTE_FROM_MOVE } interface OnScrollListener { diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt index 686687844..2a27ec631 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt @@ -169,6 +169,8 @@ class NodeAdapter */ fun removeNodeAt(position: Int) { nodeSortedList.removeItemAt(position) + // Refresh all the next items + notifyItemRangeChanged(position, nodeSortedList.size() - position) } /** @@ -190,7 +192,7 @@ class NodeAdapter } } - fun removeActionNodes() { + fun unselectActionNodes() { actionNodesList.forEach { notifyItemChanged(nodeSortedList.indexOf(it)) } diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt b/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt new file mode 100644 index 000000000..2a91cdf56 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt @@ -0,0 +1,110 @@ +package com.kunzisoft.keepass.view + +import android.content.Context +import android.util.AttributeSet +import android.view.Menu +import android.view.MenuInflater +import android.view.View +import androidx.appcompat.view.ActionMode +import androidx.appcompat.view.SupportMenuInflater +import androidx.appcompat.widget.Toolbar +import com.kunzisoft.keepass.R + +class ToolbarAction @JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, + defStyle: Int = androidx.appcompat.R.attr.toolbarStyle) + : Toolbar(context, attrs, defStyle) { + + private var mActionModeCallback: ActionMode.Callback? = null + private val actionMode = NodeActionMode(this) + + init { + visibility = View.GONE + } + + fun startSupportActionMode(actionModeCallback: ActionMode.Callback): ActionMode { + mActionModeCallback?.onDestroyActionMode(actionMode) + mActionModeCallback = actionModeCallback + mActionModeCallback?.onCreateActionMode(actionMode, menu) + mActionModeCallback?.onPrepareActionMode(actionMode, menu) + + setOnMenuItemClickListener { + mActionModeCallback?.onActionItemClicked(actionMode, it) ?: false + } + setNavigationOnClickListener{ + close() + } + + setNavigationIcon(R.drawable.ic_close_white_24dp) + + open() + + return actionMode + } + + fun invalidateMenu() { + open() + mActionModeCallback?.onPrepareActionMode(actionMode, menu) + } + + fun open() { + visibility = View.VISIBLE + //expand() + } + + fun close() { + //collapse() + visibility = View.GONE + mActionModeCallback?.onDestroyActionMode(actionMode) + } + + private class NodeActionMode(var toolbarAction: ToolbarAction): ActionMode() { + + override fun finish() { + menu.clear() + toolbarAction.close() + } + + override fun getMenu(): Menu { + return toolbarAction.menu + } + + override fun getCustomView(): View { + return toolbarAction + } + + override fun setCustomView(view: View?) {} + + override fun getMenuInflater(): MenuInflater { + return SupportMenuInflater(toolbarAction.context) + } + + override fun invalidate() { + toolbarAction.invalidateMenu() + } + + override fun getSubtitle(): CharSequence { + return toolbarAction.subtitle + } + + override fun setTitle(title: CharSequence?) { + toolbarAction.title = title + } + + override fun setTitle(resId: Int) { + toolbarAction.setTitle(resId) + } + + override fun getTitle(): CharSequence { + return toolbarAction.title + } + + override fun setSubtitle(subtitle: CharSequence?) { + toolbarAction.subtitle = subtitle + } + + override fun setSubtitle(resId: Int) { + toolbarAction.setSubtitle(resId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt index 7dc913c3f..5fb620b0f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt @@ -19,6 +19,7 @@ */ package com.kunzisoft.keepass.view +import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator import android.app.Activity @@ -63,7 +64,7 @@ fun Activity.unlockScreenOrientation() { private var actionBarHeight: Int = 0 -fun Toolbar.collapse(animate: Boolean = true) { +fun Toolbar.collapse(animate: Boolean = true, listener: Animator.AnimatorListener? = null) { if (layoutParams.height > 5) actionBarHeight = layoutParams.height @@ -72,6 +73,9 @@ fun Toolbar.collapse(animate: Boolean = true) { .ofInt(height, 0) if (animate) slideAnimator.duration = 300L + listener?.let { + slideAnimator.addListener(it) + } slideAnimator.addUpdateListener { animation -> layoutParams.height = animation.animatedValue as Int requestLayout() @@ -82,12 +86,15 @@ fun Toolbar.collapse(animate: Boolean = true) { }.start() } -fun Toolbar.expand(animate: Boolean = true) { +fun Toolbar.expand(animate: Boolean = true, listener: Animator.AnimatorListener? = null) { val slideAnimator = ValueAnimator .ofInt(0, actionBarHeight) if (animate) slideAnimator.duration = 300L + listener?.let { + slideAnimator.addListener(it) + } slideAnimator.addUpdateListener { animation -> layoutParams.height = animation.animatedValue as Int requestLayout() diff --git a/app/src/main/res/layout/activity_group.xml b/app/src/main/res/layout/activity_group.xml index eeace1184..5413536d5 100644 --- a/app/src/main/res/layout/activity_group.xml +++ b/app/src/main/res/layout/activity_group.xml @@ -27,7 +27,7 @@ + android:layout_above="@+id/toolbar_action"> - \ No newline at end of file diff --git a/app/src/main/res/values/style_red.xml b/app/src/main/res/values/style_red.xml index 5bae67bb6..2314fc58f 100644 --- a/app/src/main/res/values/style_red.xml +++ b/app/src/main/res/values/style_red.xml @@ -43,6 +43,6 @@ \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a6b78e89e..ced54b83f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -180,7 +180,7 @@