Custom Toolbar action

This commit is contained in:
J-Jamet
2019-10-09 20:16:11 +02:00
parent f79aa339e9
commit 39606e2676
9 changed files with 260 additions and 136 deletions

View File

@@ -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<NodeVersioned>): 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,36 +479,13 @@ class GroupActivity : LockingActivity(),
}
override fun onCopyMenuClick(nodes: List<NodeVersioned>): 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
}
}
private fun copyEntry(entryToCopy: EntryVersioned, newParent: GroupVersioned) {
ProgressDialogSaveDatabaseThread(this) {
@@ -529,40 +499,13 @@ class GroupActivity : LockingActivity(),
}
override fun onMoveMenuClick(nodes: List<NodeVersioned>): 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
}
}
private fun moveGroup(groupToMove: GroupVersioned, newParent: GroupVersioned) {
ProgressDialogSaveDatabaseThread(this) {
@@ -622,6 +565,42 @@ class GroupActivity : LockingActivity(),
}.start()
}
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
nodes: List<NodeVersioned>): 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)
}

View File

@@ -40,8 +40,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private set
private var mAdapter: NodeAdapter? = null
private var nodeActionMode = false
private val listNodesSelected = LinkedList<NodeVersioned>()
private var nodeActionSelectionMode = false
private val listActionNodes = LinkedList<NodeVersioned>()
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,13 +249,22 @@ 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)
if (pasteMode != null) {
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
} else {
nodeActionSelectionMode = true
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
val database = Database.getInstance()
@@ -292,6 +293,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
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<NodeVersioned>): Boolean
fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean
fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<NodeVersioned>): Boolean
}
enum class PasteMode {
PASTE_FROM_COPY, PASTE_FROM_MOVE
}
interface OnScrollListener {

View File

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

View File

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

View File

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

View File

@@ -27,7 +27,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/toolbar_paste">
android:layout_above="@+id/toolbar_action">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
@@ -147,8 +147,8 @@
app:layout_anchorGravity="right|bottom" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_paste"
<com.kunzisoft.keepass.view.ToolbarAction
android:id="@+id/toolbar_action"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentBottom="true"

View File

@@ -48,6 +48,6 @@
</style>
<!-- Contextual Action Bar Purple -->
<style name="KeepassDXStyle.ActionMode.Purple" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/purple_dark</item>
<item name="background">@color/red</item>
</style>
</resources>

View File

@@ -43,6 +43,6 @@
</style>
<!-- Contextual Action Bar Red -->
<style name="KeepassDXStyle.ActionMode.Red" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/red_dark</item>
<item name="background">@color/orange</item>
</style>
</resources>

View File

@@ -180,7 +180,7 @@
<!-- Contextual Action Bar -->
<style name="KeepassDXStyle.ActionMode" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/green_dark</item>
<item name="background">@color/orange</item>
</style>
<!-- CollapsingToolbarLayout -->