Add notes in groups #734

This commit is contained in:
J-Jamet
2021-02-11 16:05:58 +01:00
parent 3efe43c0fe
commit 79dbb942f9
8 changed files with 155 additions and 68 deletions

View File

@@ -6,6 +6,7 @@ KeePassDX(2.9.13)
* Better UI #876 * Better UI #876
* Fix themes and add Purple Dark #889 * Fix themes and add Purple Dark #889
* Allow OTP with many padding #585 * Allow OTP with many padding #585
* Add notes in groups #734
KeePassDX(2.9.12) KeePassDX(2.9.12)
* Fix OTP token type #863 * Fix OTP token type #863

View File

@@ -57,13 +57,13 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
@@ -750,7 +750,7 @@ class GroupActivity : LockingActivity(),
when (node.type) { when (node.type) {
Type.GROUP -> { Type.GROUP -> {
mOldGroupToUpdate = node as Group mOldGroupToUpdate = node as Group
GroupEditDialogFragment.build(mOldGroupToUpdate!!) GroupEditDialogFragment.build(mOldGroupToUpdate!!.getGroupInfo())
.show(supportFragmentManager, .show(supportFragmentManager,
GroupEditDialogFragment.TAG_CREATE_GROUP) GroupEditDialogFragment.TAG_CREATE_GROUP)
} }
@@ -1036,19 +1036,17 @@ class GroupActivity : LockingActivity(),
} }
} }
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?, override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction,
name: String?, groupInfo: GroupInfo) {
icon: IconImage?) {
if (name != null && name.isNotEmpty() && icon != null) { if (groupInfo.name.isNotEmpty()) {
when (action) { when (action) {
GroupEditDialogFragment.EditGroupDialogAction.CREATION -> { GroupEditDialogFragment.EditGroupDialogAction.CREATION -> {
// If group creation // If group creation
mCurrentGroup?.let { currentGroup -> mCurrentGroup?.let { currentGroup ->
// Build the group // Build the group
mDatabase?.createGroup()?.let { newGroup -> mDatabase?.createGroup()?.let { newGroup ->
newGroup.title = name newGroup.setGroupInfo(groupInfo)
newGroup.icon = icon
// Not really needed here because added in runnable but safe // Not really needed here because added in runnable but safe
newGroup.parent = currentGroup newGroup.parent = currentGroup
@@ -1068,9 +1066,7 @@ class GroupActivity : LockingActivity(),
// WARNING remove parent and children to keep memory // WARNING remove parent and children to keep memory
removeParent() removeParent()
removeChildren() removeChildren()
this.setGroupInfo(groupInfo)
title = name
this.icon = icon // TODO custom icon #96
} }
} }
// If group updated save it in the database // If group updated save it in the database
@@ -1086,9 +1082,8 @@ class GroupActivity : LockingActivity(),
} }
} }
override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?, override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction,
name: String?, groupInfo: GroupInfo) {
icon: IconImage?) {
// Do nothing here // Do nothing here
} }

View File

@@ -23,34 +23,35 @@ import android.app.Dialog
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import com.google.android.material.textfield.TextInputLayout import android.view.View
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.widget.Button import android.widget.Button
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.GroupInfo
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener { class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
private var mDatabase: Database? = null private var mDatabase: Database? = null
private var editGroupListener: EditGroupListener? = null private var mEditGroupListener: EditGroupListener? = null
private var editGroupDialogAction: EditGroupDialogAction? = null private var mEditGroupDialogAction = EditGroupDialogAction.NONE
private var nameGroup: String? = null private var mGroupInfo = GroupInfo()
private var iconGroup: IconImage? = null
private var nameTextLayoutView: TextInputLayout? = null
private var nameTextView: TextView? = null
private var iconButtonView: ImageView? = null private var iconButtonView: ImageView? = null
private var iconColor: Int = 0 private var iconColor: Int = 0
private var nameTextLayoutView: TextInputLayout? = null
private var nameTextView: TextView? = null
private var notesTextLayoutView: TextInputLayout? = null
private var notesTextView: TextView? = null
enum class EditGroupDialogAction { enum class EditGroupDialogAction {
CREATION, UPDATE, NONE; CREATION, UPDATE, NONE;
@@ -67,7 +68,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
// Verify that the host activity implements the callback interface // Verify that the host activity implements the callback interface
try { try {
// Instantiate the NoticeDialogListener so we can send events to the host // Instantiate the NoticeDialogListener so we can send events to the host
editGroupListener = context as EditGroupListener mEditGroupListener = context as EditGroupListener
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception // The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString() throw ClassCastException(context.toString()
@@ -76,16 +77,18 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
} }
override fun onDetach() { override fun onDetach() {
editGroupListener = null mEditGroupListener = null
super.onDetach() super.onDetach()
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_group_edit, null) val root = activity.layoutInflater.inflate(R.layout.fragment_group_edit, null)
iconButtonView = root?.findViewById(R.id.group_edit_icon_button)
nameTextLayoutView = root?.findViewById(R.id.group_edit_name_container) nameTextLayoutView = root?.findViewById(R.id.group_edit_name_container)
nameTextView = root?.findViewById(R.id.group_edit_name) nameTextView = root?.findViewById(R.id.group_edit_name)
iconButtonView = root?.findViewById(R.id.group_edit_icon_button) notesTextLayoutView = root?.findViewById(R.id.group_edit_note_container)
notesTextView = root?.findViewById(R.id.group_edit_note)
// Retrieve the textColor to tint the icon // Retrieve the textColor to tint the icon
val ta = activity.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor)) val ta = activity.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
@@ -94,43 +97,40 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
// Init elements // Init elements
mDatabase = Database.getInstance() mDatabase = Database.getInstance()
editGroupDialogAction = EditGroupDialogAction.NONE
nameGroup = ""
iconGroup = mDatabase?.iconFactory?.folderIcon
if (savedInstanceState != null if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ACTION_ID) && savedInstanceState.containsKey(KEY_ACTION_ID)
&& savedInstanceState.containsKey(KEY_NAME) && savedInstanceState.containsKey(KEY_GROUP_INFO)) {
&& savedInstanceState.containsKey(KEY_ICON)) { mEditGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID))
editGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID)) mGroupInfo = savedInstanceState.getParcelable(KEY_GROUP_INFO) ?: mGroupInfo
nameGroup = savedInstanceState.getString(KEY_NAME)
iconGroup = savedInstanceState.getParcelable(KEY_ICON)
} else { } else {
arguments?.apply { arguments?.apply {
if (containsKey(KEY_ACTION_ID)) if (containsKey(KEY_ACTION_ID))
editGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(getInt(KEY_ACTION_ID)) mEditGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(getInt(KEY_ACTION_ID))
if (containsKey(KEY_GROUP_INFO)) {
if (containsKey(KEY_NAME) && containsKey(KEY_ICON)) { mGroupInfo = getParcelable(KEY_GROUP_INFO) ?: mGroupInfo
nameGroup = getString(KEY_NAME)
iconGroup = getParcelable(KEY_ICON)
} }
} }
} }
// populate the name
nameTextView?.text = nameGroup
// populate the icon // populate the icon
assignIconView() assignIconView()
// populate the name
nameTextView?.text = mGroupInfo.name
// populate the note
notesTextLayoutView?.visibility = if (mGroupInfo.notes == null) View.GONE else View.VISIBLE
mGroupInfo.notes?.let {
notesTextView?.text = it
}
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
builder.setView(root) builder.setView(root)
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
.setNegativeButton(android.R.string.cancel) { _, _ -> .setNegativeButton(android.R.string.cancel) { _, _ ->
editGroupListener?.cancelEditGroup( retrieveGroupInfo()
editGroupDialogAction, mEditGroupListener?.cancelEditGroup(
nameTextView?.text?.toString(), mEditGroupDialogAction,
iconGroup) mGroupInfo)
} }
iconButtonView?.setOnClickListener { _ -> iconButtonView?.setOnClickListener { _ ->
@@ -150,32 +150,40 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
if (d != null) { if (d != null) {
val positiveButton = d.getButton(Dialog.BUTTON_POSITIVE) as Button val positiveButton = d.getButton(Dialog.BUTTON_POSITIVE) as Button
positiveButton.setOnClickListener { positiveButton.setOnClickListener {
retrieveGroupInfo()
if (isValid()) { if (isValid()) {
editGroupListener?.approveEditGroup( mEditGroupListener?.approveEditGroup(
editGroupDialogAction, mEditGroupDialogAction,
nameTextView?.text?.toString(), mGroupInfo)
iconGroup)
d.dismiss() d.dismiss()
} }
} }
} }
} }
private fun retrieveGroupInfo() {
mGroupInfo.name = nameTextView?.text?.toString() ?: ""
// Only if there
val newNotes = notesTextView?.text?.toString()
if (newNotes != null && newNotes.isNotEmpty()) {
mGroupInfo.notes = newNotes
}
}
private fun assignIconView() { private fun assignIconView() {
if (mDatabase?.drawFactory != null && iconGroup != null) { if (mDatabase?.drawFactory != null) {
iconButtonView?.assignDatabaseIcon(mDatabase?.drawFactory!!, iconGroup!!, iconColor) iconButtonView?.assignDatabaseIcon(mDatabase?.drawFactory!!, mGroupInfo.icon, iconColor)
} }
} }
override fun iconPicked(bundle: Bundle) { override fun iconPicked(bundle: Bundle) {
iconGroup = IconPickerDialogFragment.getIconStandardFromBundle(bundle) mGroupInfo.icon = IconPickerDialogFragment.getIconStandardFromBundle(bundle) ?: mGroupInfo.icon
assignIconView() assignIconView()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(KEY_ACTION_ID, editGroupDialogAction!!.ordinal) outState.putInt(KEY_ACTION_ID, mEditGroupDialogAction.ordinal)
outState.putString(KEY_NAME, nameGroup) outState.putParcelable(KEY_GROUP_INFO, mGroupInfo)
outState.putParcelable(KEY_ICON, iconGroup)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
@@ -188,17 +196,17 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
} }
interface EditGroupListener { interface EditGroupListener {
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?) fun approveEditGroup(action: EditGroupDialogAction,
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?) groupInfo: GroupInfo)
fun cancelEditGroup(action: EditGroupDialogAction,
groupInfo: GroupInfo)
} }
companion object { companion object {
const val TAG_CREATE_GROUP = "TAG_CREATE_GROUP" const val TAG_CREATE_GROUP = "TAG_CREATE_GROUP"
const val KEY_NAME = "KEY_NAME"
const val KEY_ICON = "KEY_ICON"
const val KEY_ACTION_ID = "KEY_ACTION_ID" const val KEY_ACTION_ID = "KEY_ACTION_ID"
const val KEY_GROUP_INFO = "KEY_GROUP_INFO"
fun build(): GroupEditDialogFragment { fun build(): GroupEditDialogFragment {
val bundle = Bundle() val bundle = Bundle()
@@ -208,11 +216,10 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
return fragment return fragment
} }
fun build(group: Group): GroupEditDialogFragment { fun build(groupInfo: GroupInfo): GroupEditDialogFragment {
val bundle = Bundle() val bundle = Bundle()
bundle.putString(KEY_NAME, group.title)
bundle.putParcelable(KEY_ICON, group.icon)
bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal) bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal)
bundle.putParcelable(KEY_GROUP_INFO, groupInfo)
val fragment = GroupEditDialogFragment() val fragment = GroupEditDialogFragment()
fragment.arguments = bundle fragment.arguments = bundle
return fragment return fragment

View File

@@ -29,6 +29,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.* import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -232,6 +233,14 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
override val isCurrentlyExpires: Boolean override val isCurrentlyExpires: Boolean
get() = groupKDB?.isCurrentlyExpires ?: groupKDBX?.isCurrentlyExpires ?: false get() = groupKDB?.isCurrentlyExpires ?: groupKDBX?.isCurrentlyExpires ?: false
var notes: String?
get() = groupKDBX?.notes
set(value) {
value?.let {
groupKDBX?.notes = it
}
}
override fun getChildGroups(): List<Group> { override fun getChildGroups(): List<Group> {
return groupKDB?.getChildGroups()?.map { return groupKDB?.getChildGroups()?.map {
Group(it) Group(it)
@@ -391,6 +400,26 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
return groupKDBX?.containsCustomData() ?: false return groupKDBX?.containsCustomData() ?: false
} }
/*
------------
Converter
------------
*/
fun getGroupInfo(): GroupInfo {
val groupInfo = GroupInfo()
groupInfo.name = title
groupInfo.icon = icon
groupInfo.notes = notes
return groupInfo
}
fun setGroupInfo(groupInfo: GroupInfo) {
title = groupInfo.name
icon = groupInfo.icon
notes = groupInfo.notes
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false

View File

@@ -0,0 +1,39 @@
package com.kunzisoft.keepass.model
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
class GroupInfo() : Parcelable {
var name: String = ""
var icon: IconImage = IconImageStandard()
var notes: String? = null
constructor(parcel: Parcel) : this() {
name = parcel.readString() ?: name
icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
notes = parcel.readString()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeParcelable(icon, flags)
parcel.writeString(notes)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<GroupInfo> {
override fun createFromParcel(parcel: Parcel): GroupInfo {
return GroupInfo(parcel)
}
override fun newArray(size: Int): Array<GroupInfo?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -48,5 +48,19 @@
android:singleLine="true" android:singleLine="true"
android:hint="@string/hint_group_name"/> android:hint="@string/hint_group_name"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/group_edit_note_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/group_edit_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:inputType="text"
android:maxLines="5"
android:hint="@string/entry_notes"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>

View File

@@ -4,4 +4,5 @@
* Scroll and better UI in entry edition screen #876 * Scroll and better UI in entry edition screen #876
* Better UI #876 * Better UI #876
* Fix themes and add Purple Dark #889 * Fix themes and add Purple Dark #889
* Allow OTP with many padding #585 * Allow OTP with many padding #585
* Add notes in groups #734

View File

@@ -4,4 +4,5 @@
* Défilement et amélioration de l'UI dans l'écran d'édition #876 * Défilement et amélioration de l'UI dans l'écran d'édition #876
* Meilleure interface utilisateur #876 * Meilleure interface utilisateur #876
* Correction des thèmes et ajout de Pourpre Sombre #889 * Correction des thèmes et ajout de Pourpre Sombre #889
* Autoriser l'OTP avec de nombreux rembourrages #585 * Autoriser l'OTP avec de nombreux rembourrages #585
* Ajout des notes dans les groupes #734