From 8e02c6933682d31df7d35f46d9198a2b2c5aeed2 Mon Sep 17 00:00:00 2001 From: kon3gor Date: Sun, 26 Mar 2023 18:10:23 +0300 Subject: [PATCH 01/11] Fix conflicts --- .../keepass/adapters/NodesAdapter.kt | 51 +++++++++++++++++++ .../background_rounded_hollow_square.xml | 13 +++++ .../main/res/layout/item_list_nodes_entry.xml | 21 ++++++++ 3 files changed, 85 insertions(+) create mode 100644 app/src/main/res/drawable/background_rounded_hollow_square.xml diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt index 61a1eaaab..df15be44a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt @@ -20,7 +20,12 @@ package com.kunzisoft.keepass.adapters import android.content.Context +import android.content.res.ColorStateList import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.LayerDrawable +import android.os.Build +import android.text.TextUtils import android.util.Log import android.util.TypedValue import android.view.LayoutInflater @@ -29,6 +34,15 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt +import androidx.annotation.RequiresApi +import androidx.constraintlayout.helper.widget.Flow +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.ViewCompat.generateViewId +import androidx.core.view.children +import androidx.core.view.isGone +import androidx.core.view.setPadding import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedListAdapterCallback @@ -49,6 +63,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.view.setTextSize import com.kunzisoft.keepass.view.strikeOut +import org.w3c.dom.Text import java.util.LinkedList /** @@ -343,6 +358,7 @@ class NodesAdapter ( return nodeViewHolder } + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onBindViewHolder(holder: NodeViewHolder, position: Int) { val subNode = mNodeSortedList.get(position) @@ -465,6 +481,39 @@ class NodesAdapter ( holder.meta.setTextColor(mColorOnAccentColor) } + holder.tagsContainer?.children?.forEach { child -> + if (child !is Flow) holder.tagsContainer?.removeView(child) + } + holder.tagsFlow?.referencedIds = IntArray(0) + holder.tagsContainer?.apply { + val referencedIds = IntArray(subNode.tags.size()) + subNode.tags.toList().forEachIndexed { i, tag -> + val id = generateViewId() + referencedIds[i] = id + val view = TextView(context) + view.text = tag + view.id = id + val bg = ContextCompat.getDrawable(context, R.drawable.background_rounded_hollow_square) as GradientDrawable + val color = when { + holder.container.isSelected -> { + mColorOnAccentColor + } + foregroundColor != null -> { + foregroundColor + } + else -> { + mTextColorSecondary + } + } + bg?.setStroke(3, color) + view.background = bg + view.setPadding(20, 10, 20, 10) + view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) + addView(view) + } + holder.tagsFlow?.referencedIds = referencedIds + } + database.stopManageEntry(entry) } @@ -584,6 +633,8 @@ class NodesAdapter ( var otpRunnable: OtpRunnable = OtpRunnable(otpContainer) var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers) var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon) + var tagsFlow: Flow? = itemView.findViewById(R.id.node_tags_flow) + var tagsContainer: ConstraintLayout? = itemView.findViewById(R.id.node_tags_container) } companion object { diff --git a/app/src/main/res/drawable/background_rounded_hollow_square.xml b/app/src/main/res/drawable/background_rounded_hollow_square.xml new file mode 100644 index 000000000..e14e0a02c --- /dev/null +++ b/app/src/main/res/drawable/background_rounded_hollow_square.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_list_nodes_entry.xml b/app/src/main/res/layout/item_list_nodes_entry.xml index 7bfa59ca6..38423074b 100644 --- a/app/src/main/res/layout/item_list_nodes_entry.xml +++ b/app/src/main/res/layout/item_list_nodes_entry.xml @@ -111,6 +111,27 @@ android:maxLines="2" android:visibility="gone" tools:text="Database / Group A / Group B" /> + + + + + + + Date: Mon, 27 Mar 2023 20:43:37 +0300 Subject: [PATCH 02/11] Fix conflicts again --- .../keepass/adapters/NodesAdapter.kt | 55 +++---- .../kunzisoft/keepass/view/TagsListView.kt | 138 ++++++++++++++++++ .../main/res/layout/item_list_nodes_entry.xml | 18 +-- 3 files changed, 162 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt index df15be44a..575f1a48c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt @@ -22,19 +22,23 @@ package com.kunzisoft.keepass.adapters import android.content.Context import android.content.res.ColorStateList import android.graphics.Color +import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.LayerDrawable import android.os.Build import android.text.TextUtils import android.util.Log import android.util.TypedValue +import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import android.widget.Toast import androidx.annotation.ColorInt import androidx.annotation.RequiresApi +import androidx.appcompat.widget.AppCompatTextView import androidx.constraintlayout.helper.widget.Flow import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat @@ -61,6 +65,7 @@ import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpType import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.timeout.ClipboardHelper +import com.kunzisoft.keepass.view.TagsListView import com.kunzisoft.keepass.view.setTextSize import com.kunzisoft.keepass.view.strikeOut import org.w3c.dom.Text @@ -358,7 +363,6 @@ class NodesAdapter ( return nodeViewHolder } - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onBindViewHolder(holder: NodeViewHolder, position: Int) { val subNode = mNodeSortedList.get(position) @@ -481,37 +485,8 @@ class NodesAdapter ( holder.meta.setTextColor(mColorOnAccentColor) } - holder.tagsContainer?.children?.forEach { child -> - if (child !is Flow) holder.tagsContainer?.removeView(child) - } - holder.tagsFlow?.referencedIds = IntArray(0) holder.tagsContainer?.apply { - val referencedIds = IntArray(subNode.tags.size()) - subNode.tags.toList().forEachIndexed { i, tag -> - val id = generateViewId() - referencedIds[i] = id - val view = TextView(context) - view.text = tag - view.id = id - val bg = ContextCompat.getDrawable(context, R.drawable.background_rounded_hollow_square) as GradientDrawable - val color = when { - holder.container.isSelected -> { - mColorOnAccentColor - } - foregroundColor != null -> { - foregroundColor - } - else -> { - mTextColorSecondary - } - } - bg?.setStroke(3, color) - view.background = bg - view.setPadding(20, 10, 20, 10) - view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) - addView(view) - } - holder.tagsFlow?.referencedIds = referencedIds + currentTags = subNode.tags.toList() } database.stopManageEntry(entry) @@ -552,6 +527,21 @@ class NodesAdapter ( } } + private fun GradientDrawable.adjustBgColor(holder: NodeViewHolder, foregroundColor: Int?) { + val color = when { + holder.container.isSelected -> { + mColorOnAccentColor + } + foregroundColor != null -> { + foregroundColor + } + else -> { + mTextColorSecondary + } + } + setStroke(3, color) + } + private fun populateOtpView(holder: NodeViewHolder?, otpElement: OtpElement?) { when (otpElement?.type) { OtpType.HOTP -> { @@ -633,8 +623,7 @@ class NodesAdapter ( var otpRunnable: OtpRunnable = OtpRunnable(otpContainer) var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers) var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon) - var tagsFlow: Flow? = itemView.findViewById(R.id.node_tags_flow) - var tagsContainer: ConstraintLayout? = itemView.findViewById(R.id.node_tags_container) + var tagsContainer: TagsListView? = itemView.findViewById(R.id.node_tags_container) } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt new file mode 100644 index 000000000..1c1b87b60 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -0,0 +1,138 @@ +package com.kunzisoft.keepass.view + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.os.Build +import android.util.AttributeSet +import android.util.TypedValue +import android.view.View +import androidx.appcompat.widget.AppCompatTextView +import androidx.constraintlayout.helper.widget.Flow +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.view.ViewCompat.generateViewId +import androidx.core.view.children +import com.kunzisoft.keepass.R + +class TagsListView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : ConstraintLayout(context, attrs) { + + init { + initialize() + } + + var textColor: Int? = null + var bgColor: Int? = null + + private var flow: Flow? = null + private var dotsView: View? = null + private var expanded: Boolean = false + + var currentTags: List = emptyList() + set(value) { + field = value + clear() + makeTagsList() + } + + private fun initialize() { + flow = Flow(context).apply { + setWrapMode(Flow.WRAP_CHAIN) + setHorizontalAlign(Flow.HORIZONTAL_ALIGN_START) + setHorizontalStyle(Flow.CHAIN_PACKED) + setHorizontalGap(4) + setVerticalGap(2) + setHorizontalBias(0f) + setVerticalBias(0f) + } + addView(flow) + dotsView = createTagView("...") + dotsView?.setOnClickListener { + clear() + expanded = !expanded + makeTagsList() + } + } + + private fun clear() { + for (child in children.toList()) { + if (child == flow) continue + removeView(child) + flow?.removeView(child) + } + } + + private fun makeTagsList() { + val upperBound = calculateUpperBound() + val showDots = shouldShowDots() + val tags = currentTags.subList(0, upperBound) + + for (i in tags.indices) { + val view = createTagView(tags[i]) + addView(view) + flow?.addView(view) + } + + if (showDots) { + addView(dotsView) + flow?.addView(dotsView) + } + } + + private fun calculateUpperBound(): Int { + return when { + expanded -> currentTags.size + currentTags.size > MAX_TAGS_IN_COLLAPSED -> MAX_TAGS_IN_COLLAPSED + else -> currentTags.size + } + } + + private fun shouldShowDots(): Boolean { + return currentTags.size > MAX_TAGS_IN_COLLAPSED + } + + companion object { + private const val MAX_TAGS_IN_COLLAPSED = 4 + } +} + +private fun TagsListView.createTagView(tag: String): View { + val view = AppCompatTextView(context) + view.text = tag + view.id = generateViewId() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + view.setTextAppearance(R.style.KeepassDXStyle_TextAppearance_Entry_Meta) + } else { + view.setTextAppearance(context, R.style.KeepassDXStyle_TextAppearance_Entry_Meta) + } + + textColor?.let { + view.setTextColor(it) + } + view.setPadding(30, 30, 30, 30) + view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) + + val bg = createTagBg() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.background = bg + } else { + view.setBackgroundDrawable(bg) + } + + return view +} + +private fun TagsListView.createTagBg(): Drawable? { + val bg = ContextCompat.getDrawable( + context, + R.drawable.background_rounded_hollow_square, + ) as? GradientDrawable + + bgColor?.let { + bg?.setStroke(3, it) + } + + return bg +} diff --git a/app/src/main/res/layout/item_list_nodes_entry.xml b/app/src/main/res/layout/item_list_nodes_entry.xml index 38423074b..4a7c3e5e6 100644 --- a/app/src/main/res/layout/item_list_nodes_entry.xml +++ b/app/src/main/res/layout/item_list_nodes_entry.xml @@ -113,25 +113,11 @@ tools:text="Database / Group A / Group B" /> - + android:layout_height="wrap_content"/> - - - Date: Tue, 28 Mar 2023 06:55:55 +0300 Subject: [PATCH 03/11] Added dimensional units --- .../kunzisoft/keepass/utils/Conversions.kt | 68 +++++++++++++++++++ .../kunzisoft/keepass/view/TagsListView.kt | 13 ++-- 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/kunzisoft/keepass/utils/Conversions.kt diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/Conversions.kt b/app/src/main/java/com/kunzisoft/keepass/utils/Conversions.kt new file mode 100644 index 000000000..5e335de6e --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/utils/Conversions.kt @@ -0,0 +1,68 @@ +package com.kunzisoft.keepass.utils + +import android.content.res.Resources + +sealed class Dimension( + protected val value: Float +) : Comparable { + + + abstract fun toDp(): Float + abstract fun toPx(): Float + abstract fun toSp(): Float + + val intPx: Int get() = toPx().toInt() + val floatPx: Float get() = toPx() + + override fun compareTo(other: Dimension): Int { + return toPx().compareTo(other.toPx()) + } + + class Dp(value: Float): Dimension(value) { + override fun toDp(): Float { + return value + } + + override fun toPx(): Float { + return value * Resources.getSystem().displayMetrics.density + } + + override fun toSp(): Float { + return Px(toPx()).toSp() + } + } + + class Px(value: Float): Dimension(value) { + override fun toDp(): Float { + return value / Resources.getSystem().displayMetrics.density + } + + override fun toPx(): Float { + return value + } + + override fun toSp(): Float { + return value / Resources.getSystem().displayMetrics.scaledDensity + } + } + + class Sp(value: Float): Dimension(value) { + override fun toDp(): Float { + return Px(toPx()).toDp() + } + + override fun toPx(): Float { + return value * Resources.getSystem().displayMetrics.scaledDensity + } + + override fun toSp(): Float { + return value + } + } +} + +val Float.dp get() = Dimension.Dp(this) +val Int.dp get() = toFloat().dp + +val Float.sp get() = Dimension.Sp(this) +val Int.sp get() = toFloat().sp diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index 1c1b87b60..7eaa9cc18 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat.generateViewId import androidx.core.view.children import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.utils.dp class TagsListView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -42,8 +43,8 @@ class TagsListView @JvmOverloads constructor( setWrapMode(Flow.WRAP_CHAIN) setHorizontalAlign(Flow.HORIZONTAL_ALIGN_START) setHorizontalStyle(Flow.CHAIN_PACKED) - setHorizontalGap(4) - setVerticalGap(2) + setHorizontalGap(4.dp.intPx) + setVerticalGap(2.dp.intPx) setHorizontalBias(0f) setVerticalBias(0f) } @@ -98,6 +99,9 @@ class TagsListView @JvmOverloads constructor( } } +private val VERTICAL_PADDING = 5.dp.intPx +private val HORIZONTAL_PADDING = 10.dp.intPx + private fun TagsListView.createTagView(tag: String): View { val view = AppCompatTextView(context) view.text = tag @@ -111,7 +115,8 @@ private fun TagsListView.createTagView(tag: String): View { textColor?.let { view.setTextColor(it) } - view.setPadding(30, 30, 30, 30) + + view.setPadding(VERTICAL_PADDING, HORIZONTAL_PADDING, VERTICAL_PADDING, HORIZONTAL_PADDING) view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) val bg = createTagBg() @@ -131,7 +136,7 @@ private fun TagsListView.createTagBg(): Drawable? { ) as? GradientDrawable bgColor?.let { - bg?.setStroke(3, it) + bg?.setStroke(1.dp.intPx, it) } return bg From a53341f2af090556d69d3a386acf1450ec661fa3 Mon Sep 17 00:00:00 2001 From: kon3gor Date: Tue, 28 Mar 2023 07:09:18 +0300 Subject: [PATCH 04/11] Added colors --- .../keepass/adapters/NodesAdapter.kt | 21 +++++---------- .../kunzisoft/keepass/view/TagsListView.kt | 27 ++++++++++++++++++- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt index 575f1a48c..bad789572 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt @@ -468,6 +468,8 @@ class NodesAdapter ( holder.attachmentIcon?.setColorFilter(foregroundColor) holder.meta.setTextColor(foregroundColor) iconColor = foregroundColor + holder.tagsContainer?.textColor = foregroundColor + holder.tagsContainer?.bgColor = foregroundColor } else { holder.text.setTextColor(mTextColor) holder.subText?.setTextColor(mTextColorSecondary) @@ -475,6 +477,8 @@ class NodesAdapter ( holder.otpProgress?.setIndicatorColor(mTextColorSecondary) holder.attachmentIcon?.setColorFilter(mTextColorSecondary) holder.meta.setTextColor(mTextColor) + holder.tagsContainer?.textColor = mTextColorSecondary + holder.tagsContainer?.bgColor = mTextColorSecondary } } else { holder.text.setTextColor(mColorOnAccentColor) @@ -483,6 +487,8 @@ class NodesAdapter ( holder.otpProgress?.setIndicatorColor(mColorOnAccentColor) holder.attachmentIcon?.setColorFilter(mColorOnAccentColor) holder.meta.setTextColor(mColorOnAccentColor) + holder.tagsContainer?.textColor = mColorOnAccentColor + holder.tagsContainer?.bgColor = mColorOnAccentColor } holder.tagsContainer?.apply { @@ -527,21 +533,6 @@ class NodesAdapter ( } } - private fun GradientDrawable.adjustBgColor(holder: NodeViewHolder, foregroundColor: Int?) { - val color = when { - holder.container.isSelected -> { - mColorOnAccentColor - } - foregroundColor != null -> { - foregroundColor - } - else -> { - mTextColorSecondary - } - } - setStroke(3, color) - } - private fun populateOtpView(holder: NodeViewHolder?, otpElement: OtpElement?) { when (otpElement?.type) { OtpType.HOTP -> { diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index 7eaa9cc18..9915eb392 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -14,7 +14,9 @@ import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat.generateViewId import androidx.core.view.children import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.app.App import com.kunzisoft.keepass.utils.dp +import com.kunzisoft.keepass.utils.sp class TagsListView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -25,7 +27,21 @@ class TagsListView @JvmOverloads constructor( } var textColor: Int? = null + set(value) { + if (field == value) { + return + } + field = value + styleDotsView() + } var bgColor: Int? = null + set(value) { + if (field == value) { + return + } + field = value + styleDotsView() + } private var flow: Flow? = null private var dotsView: View? = null @@ -57,6 +73,11 @@ class TagsListView @JvmOverloads constructor( } } + private fun styleDotsView() { + val view = dotsView as? AppCompatTextView ?: return + styleTagView(view) + } + private fun clear() { for (child in children.toList()) { if (child == flow) continue @@ -106,6 +127,10 @@ private fun TagsListView.createTagView(tag: String): View { val view = AppCompatTextView(context) view.text = tag view.id = generateViewId() + return styleTagView(view) +} + +private fun TagsListView.styleTagView(view: AppCompatTextView): View { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { view.setTextAppearance(R.style.KeepassDXStyle_TextAppearance_Entry_Meta) } else { @@ -117,7 +142,7 @@ private fun TagsListView.createTagView(tag: String): View { } view.setPadding(VERTICAL_PADDING, HORIZONTAL_PADDING, VERTICAL_PADDING, HORIZONTAL_PADDING) - view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) + view.textSize = 13.sp.floatPx val bg = createTagBg() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { From b9fdf0c247f985b445a1c92cecddf66d095a3954 Mon Sep 17 00:00:00 2001 From: kon3gor Date: Tue, 28 Mar 2023 17:34:25 +0300 Subject: [PATCH 05/11] Some ui fixes --- .../kunzisoft/keepass/utils/Conversions.kt | 1 - .../kunzisoft/keepass/view/TagsListView.kt | 25 ++++++++++--------- .../background_rounded_hollow_square.xml | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/Conversions.kt b/app/src/main/java/com/kunzisoft/keepass/utils/Conversions.kt index 5e335de6e..fd9c58f0b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/Conversions.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/Conversions.kt @@ -12,7 +12,6 @@ sealed class Dimension( abstract fun toSp(): Float val intPx: Int get() = toPx().toInt() - val floatPx: Float get() = toPx() override fun compareTo(other: Dimension): Int { return toPx().compareTo(other.toPx()) diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index 9915eb392..645bb1fdc 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -13,6 +13,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat.generateViewId import androidx.core.view.children +import androidx.core.view.updatePadding import com.kunzisoft.keepass.R import com.kunzisoft.keepass.app.App import com.kunzisoft.keepass.utils.dp @@ -120,8 +121,8 @@ class TagsListView @JvmOverloads constructor( } } -private val VERTICAL_PADDING = 5.dp.intPx -private val HORIZONTAL_PADDING = 10.dp.intPx +private val VERTICAL_PADDING = 2.dp.intPx +private val HORIZONTAL_PADDING = 5.dp.intPx private fun TagsListView.createTagView(tag: String): View { val view = AppCompatTextView(context) @@ -131,6 +132,13 @@ private fun TagsListView.createTagView(tag: String): View { } private fun TagsListView.styleTagView(view: AppCompatTextView): View { + val bg = createTagBg() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.background = bg + } else { + view.setBackgroundDrawable(bg) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { view.setTextAppearance(R.style.KeepassDXStyle_TextAppearance_Entry_Meta) } else { @@ -141,15 +149,8 @@ private fun TagsListView.styleTagView(view: AppCompatTextView): View { view.setTextColor(it) } - view.setPadding(VERTICAL_PADDING, HORIZONTAL_PADDING, VERTICAL_PADDING, HORIZONTAL_PADDING) - view.textSize = 13.sp.floatPx - - val bg = createTagBg() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - view.background = bg - } else { - view.setBackgroundDrawable(bg) - } + view.setPadding(HORIZONTAL_PADDING, VERTICAL_PADDING, HORIZONTAL_PADDING, VERTICAL_PADDING) + view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) return view } @@ -161,7 +162,7 @@ private fun TagsListView.createTagBg(): Drawable? { ) as? GradientDrawable bgColor?.let { - bg?.setStroke(1.dp.intPx, it) + bg?.setStroke(1.2f.dp.intPx, it) } return bg diff --git a/app/src/main/res/drawable/background_rounded_hollow_square.xml b/app/src/main/res/drawable/background_rounded_hollow_square.xml index e14e0a02c..cdcb83aab 100644 --- a/app/src/main/res/drawable/background_rounded_hollow_square.xml +++ b/app/src/main/res/drawable/background_rounded_hollow_square.xml @@ -1,6 +1,6 @@ - + Date: Tue, 28 Mar 2023 19:40:08 +0300 Subject: [PATCH 06/11] some shitty animation --- .../kunzisoft/keepass/view/TagsListView.kt | 156 ++++++++++++++---- .../com/kunzisoft/keepass/view/ViewUtil.kt | 32 ++++ app/src/main/res/layout/tags_list_view.xml | 32 ++++ 3 files changed, 187 insertions(+), 33 deletions(-) create mode 100644 app/src/main/res/layout/tags_list_view.xml diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index 645bb1fdc..2dc76357a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -1,36 +1,39 @@ package com.kunzisoft.keepass.view +import android.animation.AnimatorSet +import android.animation.ValueAnimator import android.content.Context import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build import android.util.AttributeSet +import android.util.Log import android.util.TypedValue import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import androidx.constraintlayout.helper.widget.Flow import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat.generateViewId import androidx.core.view.children -import androidx.core.view.updatePadding import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.app.App import com.kunzisoft.keepass.utils.dp -import com.kunzisoft.keepass.utils.sp class TagsListView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : ConstraintLayout(context, attrs) { init { + inflate(context, R.layout.tags_list_view, this) initialize() } var textColor: Int? = null set(value) { if (field == value) { - return + return } field = value styleDotsView() @@ -43,34 +46,36 @@ class TagsListView @JvmOverloads constructor( field = value styleDotsView() } - - private var flow: Flow? = null - private var dotsView: View? = null - private var expanded: Boolean = false - var currentTags: List = emptyList() set(value) { field = value clear() - makeTagsList() + drawAllTagsAndMeasure() + clear() + makeTagsList(true) } + private var flow: Flow? = null + private var dotsView: View? = null + private var expanded: Boolean = false + private var cachedTopPadding: Int = 0 + private var expandCllbacks: List<(() -> Unit)> = emptyList() + private var initialMeasure = false + private fun initialize() { - flow = Flow(context).apply { - setWrapMode(Flow.WRAP_CHAIN) - setHorizontalAlign(Flow.HORIZONTAL_ALIGN_START) - setHorizontalStyle(Flow.CHAIN_PACKED) - setHorizontalGap(4.dp.intPx) - setVerticalGap(2.dp.intPx) - setHorizontalBias(0f) - setVerticalBias(0f) - } - addView(flow) + flow = findViewById(R.id.flow) dotsView = createTagView("...") - dotsView?.setOnClickListener { + + findViewById(R.id.button)?.setOnClickListener { clear() expanded = !expanded makeTagsList() + it.animate().rotationBy(180f).start() + if (expanded) { + //animateTagsChange(lastHeight, lastHeight + 30.dp.intPx) + expandCllbacks.forEach { it() } + expandCllbacks = emptyList() + } } } @@ -81,29 +86,58 @@ class TagsListView @JvmOverloads constructor( private fun clear() { for (child in children.toList()) { - if (child == flow) continue + if (child == flow || child.id == R.id.button) continue removeView(child) flow?.removeView(child) } } - private fun makeTagsList() { - val upperBound = calculateUpperBound() - val showDots = shouldShowDots() - val tags = currentTags.subList(0, upperBound) - + private fun drawAllTagsAndMeasure() { + initialMeasure = true + val tags = currentTags for (i in tags.indices) { val view = createTagView(tags[i]) addView(view) flow?.addView(view) } + requestLayout() + invalidate() + } - if (showDots) { - addView(dotsView) - flow?.addView(dotsView) + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (initialMeasure) { + initialMeasure = false + cachedTopPadding = measuredHeight + Log.d("DAMN", "Dfasfas $cachedTopPadding") } } + private fun makeTagsList(initial: Boolean = false) { + val lastHeight = measuredHeight + val upperBound = calculateUpperBound() + val tags = currentTags.subList(0, upperBound) + val callbacks = mutableListOf<(() -> Unit)>() + + for (i in tags.indices) { + val view = createTagView(tags[i]) + addView(view) + if (i >= MAX_TAGS_IN_COLLAPSED) { + view.alpha = 0f + callbacks.add { + view.animate().alpha(1f).start() + } + } + flow?.addView(view) + } + + expandCllbacks = callbacks + + val currentHeight = measuredHeight + requestLayout() + Log.d("DAMN", "${lastHeight} ${currentHeight}") + } + private fun calculateUpperBound(): Int { return when { expanded -> currentTags.size @@ -112,8 +146,64 @@ class TagsListView @JvmOverloads constructor( } } - private fun shouldShowDots(): Boolean { - return currentTags.size > MAX_TAGS_IN_COLLAPSED + private fun animateTagsChange(from: Int, to: Int) { + if (expanded) { + animateExpand(from, to) + } else { + animateCollapse(from, to) + } + } + + private fun animateExpand(from: Int, to: Int) { + layoutParams.height = 0 + val slideAnimator = ValueAnimator.ofInt(from, to) + slideAnimator.duration = 300L + slideAnimator.addUpdateListener { animation -> + layoutParams.height = animation.animatedValue as Int + requestLayout() + } + + val f = flow ?: return + val slideAnimator2 = ValueAnimator.ofInt(cachedTopPadding, 0) + slideAnimator2.duration = 248L + slideAnimator2.addUpdateListener { animation -> + f.paddingTop = animation.animatedValue as Int + requestLayout() + } + AnimatorSet().apply { + play(slideAnimator) + interpolator = AccelerateDecelerateInterpolator() + }.start() + AnimatorSet().apply { + play(slideAnimator2) + interpolator = AccelerateDecelerateInterpolator() + }.start() + } + + private fun animateCollapse(from: Int, to: Int) { + layoutParams.height = 0 + val slideAnimator = ValueAnimator.ofInt(from, to) + slideAnimator.duration = 300L + slideAnimator.addUpdateListener { animation -> + layoutParams.height = animation.animatedValue as Int + requestLayout() + } + + val f = flow ?: return + val slideAnimator2 = ValueAnimator.ofInt(0, cachedTopPadding) + slideAnimator2.duration = 300L + slideAnimator2.addUpdateListener { animation -> + f.paddingTop = animation.animatedValue as Int + requestLayout() + } + AnimatorSet().apply { + play(slideAnimator) + interpolator = AccelerateDecelerateInterpolator() + }.start() + AnimatorSet().apply { + play(slideAnimator2) + interpolator = AccelerateDecelerateInterpolator() + }.start() } companion object { @@ -166,4 +256,4 @@ private fun TagsListView.createTagBg(): Drawable? { } return bg -} +} \ 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 8c9b021d3..a1ab226a1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt @@ -166,6 +166,38 @@ fun View.expand(animate: Boolean = true, }.start() } +fun View.expand( + from: Int, + to: Int, + onExpandFinished: (() -> Unit)? = null, +) { + layoutParams.height = 0 + val slideAnimator = ValueAnimator + .ofInt(from, to) + slideAnimator.duration = 300L + var alreadyVisible = false + slideAnimator.addUpdateListener { animation -> + layoutParams.height = animation.animatedValue as Int + if (!alreadyVisible && layoutParams.height > 0) { + visibility = View.VISIBLE + alreadyVisible = true + } + requestLayout() + } + AnimatorSet().apply { + play(slideAnimator) + interpolator = AccelerateDecelerateInterpolator() + addListener(object: Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator?) {} + override fun onAnimationRepeat(animation: Animator?) {} + override fun onAnimationEnd(animation: Animator?) { + onExpandFinished?.invoke() + } + override fun onAnimationCancel(animation: Animator?) {} + }) + }.start() +} + /*** * This function returns the actual height the layout. * The getHeight() function returns the current height which might be zero if diff --git a/app/src/main/res/layout/tags_list_view.xml b/app/src/main/res/layout/tags_list_view.xml new file mode 100644 index 000000000..7b06b4003 --- /dev/null +++ b/app/src/main/res/layout/tags_list_view.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file From 9c28d0d99f2b0c6a132d1f19dc190c40ede551b2 Mon Sep 17 00:00:00 2001 From: kon3gor Date: Wed, 29 Mar 2023 07:31:00 +0300 Subject: [PATCH 07/11] some pretier animation --- .../kunzisoft/keepass/view/TagsListView.kt | 87 ++++++++++--------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index 2dc76357a..dd1018d57 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -1,15 +1,19 @@ package com.kunzisoft.keepass.view +import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator import android.content.Context import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build +import android.os.Handler +import android.os.Looper import android.util.AttributeSet import android.util.Log import android.util.TypedValue import android.view.View +import android.view.ViewTreeObserver import android.view.animation.AccelerateDecelerateInterpolator import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView @@ -18,8 +22,11 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat.generateViewId import androidx.core.view.children +import androidx.core.view.isGone import com.kunzisoft.keepass.R import com.kunzisoft.keepass.utils.dp +import kotlin.math.max +import kotlin.math.min class TagsListView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -30,6 +37,9 @@ class TagsListView @JvmOverloads constructor( initialize() } + private var measureingCollapsedHeight: Boolean = false + private var currentHeight: Int = 0 + var textColor: Int? = null set(value) { if (field == value) { @@ -51,8 +61,6 @@ class TagsListView @JvmOverloads constructor( field = value clear() drawAllTagsAndMeasure() - clear() - makeTagsList(true) } private var flow: Flow? = null @@ -60,22 +68,20 @@ class TagsListView @JvmOverloads constructor( private var expanded: Boolean = false private var cachedTopPadding: Int = 0 private var expandCllbacks: List<(() -> Unit)> = emptyList() - private var initialMeasure = false + private var expandedHeight = 0 + private var measureingExpandedHeight = false private fun initialize() { flow = findViewById(R.id.flow) dotsView = createTagView("...") + viewTreeObserver.addOnGlobalLayoutListener(Observer()) findViewById(R.id.button)?.setOnClickListener { clear() expanded = !expanded + animateTagsChange(currentHeight, expandedHeight) makeTagsList() it.animate().rotationBy(180f).start() - if (expanded) { - //animateTagsChange(lastHeight, lastHeight + 30.dp.intPx) - expandCllbacks.forEach { it() } - expandCllbacks = emptyList() - } } } @@ -93,24 +99,17 @@ class TagsListView @JvmOverloads constructor( } private fun drawAllTagsAndMeasure() { - initialMeasure = true + measureingExpandedHeight = true val tags = currentTags for (i in tags.indices) { val view = createTagView(tags[i]) addView(view) flow?.addView(view) } - requestLayout() - invalidate() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) - if (initialMeasure) { - initialMeasure = false - cachedTopPadding = measuredHeight - Log.d("DAMN", "Dfasfas $cachedTopPadding") - } } private fun makeTagsList(initial: Boolean = false) { @@ -123,8 +122,10 @@ class TagsListView @JvmOverloads constructor( val view = createTagView(tags[i]) addView(view) if (i >= MAX_TAGS_IN_COLLAPSED) { + view.isGone = true view.alpha = 0f callbacks.add { + view.isGone = false view.animate().alpha(1f).start() } } @@ -133,8 +134,6 @@ class TagsListView @JvmOverloads constructor( expandCllbacks = callbacks - val currentHeight = measuredHeight - requestLayout() Log.d("DAMN", "${lastHeight} ${currentHeight}") } @@ -150,7 +149,7 @@ class TagsListView @JvmOverloads constructor( if (expanded) { animateExpand(from, to) } else { - animateCollapse(from, to) + animateExpand(to, from) } } @@ -162,22 +161,21 @@ class TagsListView @JvmOverloads constructor( layoutParams.height = animation.animatedValue as Int requestLayout() } + slideAnimator.addListener(object : Animator.AnimatorListener{ + override fun onAnimationStart(p0: Animator?) {} + override fun onAnimationEnd(p0: Animator?) { + expandCllbacks.forEach { it() } + expandCllbacks = emptyList() + } + override fun onAnimationCancel(p0: Animator?) {} + override fun onAnimationRepeat(p0: Animator?) {} + + }) - val f = flow ?: return - val slideAnimator2 = ValueAnimator.ofInt(cachedTopPadding, 0) - slideAnimator2.duration = 248L - slideAnimator2.addUpdateListener { animation -> - f.paddingTop = animation.animatedValue as Int - requestLayout() - } AnimatorSet().apply { play(slideAnimator) interpolator = AccelerateDecelerateInterpolator() }.start() - AnimatorSet().apply { - play(slideAnimator2) - interpolator = AccelerateDecelerateInterpolator() - }.start() } private fun animateCollapse(from: Int, to: Int) { @@ -189,21 +187,28 @@ class TagsListView @JvmOverloads constructor( requestLayout() } - val f = flow ?: return - val slideAnimator2 = ValueAnimator.ofInt(0, cachedTopPadding) - slideAnimator2.duration = 300L - slideAnimator2.addUpdateListener { animation -> - f.paddingTop = animation.animatedValue as Int - requestLayout() - } AnimatorSet().apply { play(slideAnimator) interpolator = AccelerateDecelerateInterpolator() }.start() - AnimatorSet().apply { - play(slideAnimator2) - interpolator = AccelerateDecelerateInterpolator() - }.start() + } + + private inner class Observer : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + if (measureingExpandedHeight) { + measureingExpandedHeight = false + expandedHeight = measuredHeight + Handler(Looper.getMainLooper()).post { + measureingCollapsedHeight = true + clear() + makeTagsList() + } + } else if (measureingCollapsedHeight) { + measureingCollapsedHeight = false + currentHeight = measuredHeight + viewTreeObserver.removeGlobalOnLayoutListener(this) + } + } } companion object { From 9bb5fc4548ad13bae9671759f9cf2fdf524769a6 Mon Sep 17 00:00:00 2001 From: kon3gor Date: Fri, 31 Mar 2023 18:12:04 +0300 Subject: [PATCH 08/11] Working animation --- .../kunzisoft/keepass/view/TagsListView.kt | 135 ++++++++---------- 1 file changed, 58 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index dd1018d57..730f4cef7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -7,14 +7,16 @@ import android.content.Context import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build -import android.os.Handler -import android.os.Looper import android.util.AttributeSet import android.util.Log import android.util.TypedValue import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewTreeObserver import android.view.animation.AccelerateDecelerateInterpolator +import android.widget.LinearLayout import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import androidx.constraintlayout.helper.widget.Flow @@ -25,8 +27,7 @@ import androidx.core.view.children import androidx.core.view.isGone import com.kunzisoft.keepass.R import com.kunzisoft.keepass.utils.dp -import kotlin.math.max -import kotlin.math.min +import kotlin.math.exp class TagsListView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -38,7 +39,6 @@ class TagsListView @JvmOverloads constructor( } private var measureingCollapsedHeight: Boolean = false - private var currentHeight: Int = 0 var textColor: Int? = null set(value) { @@ -46,7 +46,6 @@ class TagsListView @JvmOverloads constructor( return } field = value - styleDotsView() } var bgColor: Int? = null set(value) { @@ -54,93 +53,77 @@ class TagsListView @JvmOverloads constructor( return } field = value - styleDotsView() } var currentTags: List = emptyList() set(value) { field = value - clear() drawAllTagsAndMeasure() } private var flow: Flow? = null - private var dotsView: View? = null + private var expandBtn: View? = null private var expanded: Boolean = false - private var cachedTopPadding: Int = 0 private var expandCllbacks: List<(() -> Unit)> = emptyList() - private var expandedHeight = 0 private var measureingExpandedHeight = false + private var hiddenViews: MutableList = mutableListOf() + private var initialLayoutParams: ViewGroup.LayoutParams? = null + private var capturingInitialLp = true + + private var expandedHeight: Int = 0 + private var collapsedHeight: Int = 0 private fun initialize() { - flow = findViewById(R.id.flow) - dotsView = createTagView("...") viewTreeObserver.addOnGlobalLayoutListener(Observer()) - - findViewById(R.id.button)?.setOnClickListener { - clear() + flow = findViewById(R.id.flow) + expandBtn = findViewById(R.id.button) + expandBtn?.setOnClickListener { expanded = !expanded - animateTagsChange(currentHeight, expandedHeight) - makeTagsList() + animateTagsChange(collapsedHeight, expandedHeight) it.animate().rotationBy(180f).start() } } - private fun styleDotsView() { - val view = dotsView as? AppCompatTextView ?: return - styleTagView(view) - } - private fun clear() { for (child in children.toList()) { if (child == flow || child.id == R.id.button) continue removeView(child) flow?.removeView(child) } + hiddenViews.clear() } private fun drawAllTagsAndMeasure() { - measureingExpandedHeight = true - val tags = currentTags - for (i in tags.indices) { - val view = createTagView(tags[i]) - addView(view) - flow?.addView(view) + clear() + layoutParams?.apply { + height = WRAP_CONTENT + } + post { + measureingExpandedHeight = true + makeTagsList(false) } } - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - } - - private fun makeTagsList(initial: Boolean = false) { - val lastHeight = measuredHeight - val upperBound = calculateUpperBound() - val tags = currentTags.subList(0, upperBound) - val callbacks = mutableListOf<(() -> Unit)>() - - for (i in tags.indices) { - val view = createTagView(tags[i]) + private fun makeTagsList(hideExtra: Boolean) { + for (i in currentTags.indices) { + val view = createTagView(currentTags[i]) addView(view) if (i >= MAX_TAGS_IN_COLLAPSED) { - view.isGone = true - view.alpha = 0f - callbacks.add { - view.isGone = false - view.animate().alpha(1f).start() - } + hiddenViews.add(view) } flow?.addView(view) } + } - expandCllbacks = callbacks - - Log.d("DAMN", "${lastHeight} ${currentHeight}") + private fun hideViews() { + hiddenViews.forEach { + it.isGone = true + it.alpha = 0f + } } private fun calculateUpperBound(): Int { return when { expanded -> currentTags.size - currentTags.size > MAX_TAGS_IN_COLLAPSED -> MAX_TAGS_IN_COLLAPSED else -> currentTags.size } } @@ -154,18 +137,29 @@ class TagsListView @JvmOverloads constructor( } private fun animateExpand(from: Int, to: Int) { - layoutParams.height = 0 val slideAnimator = ValueAnimator.ofInt(from, to) slideAnimator.duration = 300L slideAnimator.addUpdateListener { animation -> layoutParams.height = animation.animatedValue as Int requestLayout() } - slideAnimator.addListener(object : Animator.AnimatorListener{ - override fun onAnimationStart(p0: Animator?) {} + slideAnimator.addListener(object : Animator.AnimatorListener { + override fun onAnimationStart(p0: Animator?) { + if (!expanded) { + hiddenViews.forEach { + it.isGone = true + it.animate().alpha(0f).start() + } + } + } + override fun onAnimationEnd(p0: Animator?) { - expandCllbacks.forEach { it() } - expandCllbacks = emptyList() + if (expanded) { + hiddenViews.forEach { + it.isGone = false + it.animate().alpha(1f).start() + } + } } override fun onAnimationCancel(p0: Animator?) {} override fun onAnimationRepeat(p0: Animator?) {} @@ -178,35 +172,22 @@ class TagsListView @JvmOverloads constructor( }.start() } - private fun animateCollapse(from: Int, to: Int) { - layoutParams.height = 0 - val slideAnimator = ValueAnimator.ofInt(from, to) - slideAnimator.duration = 300L - slideAnimator.addUpdateListener { animation -> - layoutParams.height = animation.animatedValue as Int - requestLayout() - } - - AnimatorSet().apply { - play(slideAnimator) - interpolator = AccelerateDecelerateInterpolator() - }.start() - } - private inner class Observer : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { if (measureingExpandedHeight) { measureingExpandedHeight = false + Log.d("DAMN", expandedHeight.toString()) expandedHeight = measuredHeight - Handler(Looper.getMainLooper()).post { + Log.d("DAMN", measuredHeight.toString()) + Log.d("DAMN", children.filter { it is AppCompatTextView }.map { "(${it.x}, ${it.y})" }.toList().toString()) + post { measureingCollapsedHeight = true - clear() - makeTagsList() + hideViews() } - } else if (measureingCollapsedHeight) { + } + if (measureingCollapsedHeight) { measureingCollapsedHeight = false - currentHeight = measuredHeight - viewTreeObserver.removeGlobalOnLayoutListener(this) + collapsedHeight = measuredHeight } } } From e7ec829708b9e2ddebaf51fba31752dfc0615e07 Mon Sep 17 00:00:00 2001 From: kon3gor Date: Fri, 31 Mar 2023 19:48:14 +0300 Subject: [PATCH 09/11] Almost clean solution --- .../keepass/utils/StubAnimatorListener.kt | 21 ++ .../kunzisoft/keepass/view/TagsListView.kt | 225 ++++++++++-------- 2 files changed, 146 insertions(+), 100 deletions(-) create mode 100644 app/src/main/java/com/kunzisoft/keepass/utils/StubAnimatorListener.kt diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/StubAnimatorListener.kt b/app/src/main/java/com/kunzisoft/keepass/utils/StubAnimatorListener.kt new file mode 100644 index 000000000..15ed2347f --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/utils/StubAnimatorListener.kt @@ -0,0 +1,21 @@ +package com.kunzisoft.keepass.utils + +import android.animation.Animator + +abstract class StubAnimatorListener : Animator.AnimatorListener{ + override fun onAnimationStart(p0: Animator?) { + // no-op + } + + override fun onAnimationEnd(p0: Animator?) { + // no-op + } + + override fun onAnimationCancel(p0: Animator?) { + // no-op + } + + override fun onAnimationRepeat(p0: Animator?) { + // no-op + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index 730f4cef7..b8d0402c0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -8,15 +8,11 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build import android.util.AttributeSet -import android.util.Log import android.util.TypedValue import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewTreeObserver import android.view.animation.AccelerateDecelerateInterpolator -import android.widget.LinearLayout import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import androidx.constraintlayout.helper.widget.Flow @@ -26,20 +22,13 @@ import androidx.core.view.ViewCompat.generateViewId import androidx.core.view.children import androidx.core.view.isGone import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.utils.StubAnimatorListener import com.kunzisoft.keepass.utils.dp -import kotlin.math.exp class TagsListView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : ConstraintLayout(context, attrs) { - init { - inflate(context, R.layout.tags_list_view, this) - initialize() - } - - private var measureingCollapsedHeight: Boolean = false - var textColor: Int? = null set(value) { if (field == value) { @@ -60,50 +49,47 @@ class TagsListView @JvmOverloads constructor( drawAllTagsAndMeasure() } + init { + inflate(context, R.layout.tags_list_view, this) + initialize() + } + private var flow: Flow? = null private var expandBtn: View? = null - private var expanded: Boolean = false - private var expandCllbacks: List<(() -> Unit)> = emptyList() - private var measureingExpandedHeight = false private var hiddenViews: MutableList = mutableListOf() - private var initialLayoutParams: ViewGroup.LayoutParams? = null - private var capturingInitialLp = true - - private var expandedHeight: Int = 0 - private var collapsedHeight: Int = 0 + private var currentState: State = State.IDLE + private var animationHelper: AnimationHelper? = null private fun initialize() { - viewTreeObserver.addOnGlobalLayoutListener(Observer()) + viewTreeObserver.addOnGlobalLayoutListener(InitialMeasuringObserver()) flow = findViewById(R.id.flow) expandBtn = findViewById(R.id.button) expandBtn?.setOnClickListener { - expanded = !expanded - animateTagsChange(collapsedHeight, expandedHeight) - it.animate().rotationBy(180f).start() + animationHelper?.startAnimation() + val sign = if (currentState == State.EXPANDED) -1 else 1 + it.animate().rotationBy(180f * sign).start() + } + } + + private fun drawAllTagsAndMeasure() { + clear() + post { + layoutParams.height = WRAP_CONTENT + currentState = State.MEASURING_EXPANDED + makeTagsList() } } private fun clear() { for (child in children.toList()) { - if (child == flow || child.id == R.id.button) continue + if (child.id == R.id.flow || child.id == R.id.button) continue removeView(child) flow?.removeView(child) } hiddenViews.clear() } - private fun drawAllTagsAndMeasure() { - clear() - layoutParams?.apply { - height = WRAP_CONTENT - } - post { - measureingExpandedHeight = true - makeTagsList(false) - } - } - - private fun makeTagsList(hideExtra: Boolean) { + private fun makeTagsList() { for (i in currentTags.indices) { val view = createTagView(currentTags[i]) addView(view) @@ -114,86 +100,125 @@ class TagsListView @JvmOverloads constructor( } } - private fun hideViews() { - hiddenViews.forEach { - it.isGone = true - it.alpha = 0f + private fun toggleHiddenViews(animate: Boolean) { + for (ind in hiddenViews.indices) { + toggleHiddenView(ind, animate) } } - private fun calculateUpperBound(): Int { - return when { - expanded -> currentTags.size - else -> currentTags.size + private fun toggleHiddenView(ind: Int, animate: Boolean) { + val isGone = hiddenViews[ind].isGone + val alpha = if (isGone) 1f else 0f + if (!animate) { + hiddenViews[ind].isGone = !isGone + hiddenViews[ind].alpha = alpha + return } - } - private fun animateTagsChange(from: Int, to: Int) { - if (expanded) { - animateExpand(from, to) - } else { - animateExpand(to, from) + if (isGone) { + hiddenViews[ind].isGone = !isGone } - } - - private fun animateExpand(from: Int, to: Int) { - val slideAnimator = ValueAnimator.ofInt(from, to) - slideAnimator.duration = 300L - slideAnimator.addUpdateListener { animation -> - layoutParams.height = animation.animatedValue as Int - requestLayout() - } - slideAnimator.addListener(object : Animator.AnimatorListener { - override fun onAnimationStart(p0: Animator?) { - if (!expanded) { - hiddenViews.forEach { - it.isGone = true - it.animate().alpha(0f).start() - } - } - } - + hiddenViews[ind].animate().setListener(object : StubAnimatorListener() { override fun onAnimationEnd(p0: Animator?) { - if (expanded) { - hiddenViews.forEach { - it.isGone = false - it.animate().alpha(1f).start() - } - } + if (isGone) return + hiddenViews[ind].isGone = !isGone } - override fun onAnimationCancel(p0: Animator?) {} - override fun onAnimationRepeat(p0: Animator?) {} - - }) - - AnimatorSet().apply { - play(slideAnimator) - interpolator = AccelerateDecelerateInterpolator() - }.start() + }).alpha(alpha).start() } - private inner class Observer : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - if (measureingExpandedHeight) { - measureingExpandedHeight = false - Log.d("DAMN", expandedHeight.toString()) - expandedHeight = measuredHeight - Log.d("DAMN", measuredHeight.toString()) - Log.d("DAMN", children.filter { it is AppCompatTextView }.map { "(${it.x}, ${it.y})" }.toList().toString()) + private inner class AnimationHelper( + expandedHeight: Int, + collapsedHeight: Int, + ) : StubAnimatorListener() { + + private val collapsingAnimator = setupAnimator(expandedHeight, collapsedHeight) + private val expandingAnimator = setupAnimator(collapsedHeight, expandedHeight) + + fun startAnimation() { + when (currentState) { + State.EXPANDED -> animateInternal(collapsingAnimator) + State.COLLAPSED -> animateInternal(expandingAnimator) + else -> { /* np-op */ } + } + } + + private fun animateInternal(animator: Animator) { + AnimatorSet().apply { + play(animator) + interpolator = AccelerateDecelerateInterpolator() + }.start() + } + + override fun onAnimationStart(p0: Animator?) { + if (currentState == State.COLLAPSED) return + toggleHiddenViews(false) + } + + override fun onAnimationEnd(p0: Animator?) { + currentState = currentState.next() + if (currentState == State.EXPANDED) { + toggleHiddenViews(true) + } + } + + private fun setupAnimator(from: Int, to: Int): Animator { + val animator = ValueAnimator.ofInt(from, to) + animator.duration = ANIMATION_DURATION + animator.addUpdateListener { animation -> post { - measureingCollapsedHeight = true - hideViews() + layoutParams.height = animation.animatedValue as Int + requestLayout() } } - if (measureingCollapsedHeight) { - measureingCollapsedHeight = false - collapsedHeight = measuredHeight + animator.addListener(this) + return animator + } + } + + private inner class InitialMeasuringObserver : ViewTreeObserver.OnGlobalLayoutListener { + private var expandedHeight = 0 + + override fun onGlobalLayout() { + when (currentState) { + State.MEASURING_EXPANDED -> { + expandedHeight = measuredHeight + post { + currentState = currentState.next() + toggleHiddenViews(false) + } + } + State.MEASURING_COLLAPSED -> { + currentState = currentState.next() + animationHelper = AnimationHelper(expandedHeight, measuredHeight) + } + else -> { /* no-op */ } } } } - companion object { - private const val MAX_TAGS_IN_COLLAPSED = 4 + private enum class State { + MEASURING_EXPANDED { + override fun next() = MEASURING_COLLAPSED + }, + MEASURING_COLLAPSED { + override fun next() = COLLAPSED + }, + EXPANDED { + override fun next() = COLLAPSED + }, + COLLAPSED { + override fun next() = EXPANDED + }, + IDLE { + override fun next() = MEASURING_EXPANDED + }; + + abstract fun next(): State + } + + private companion object { + const val MAX_TAGS_IN_COLLAPSED = 4 + const val ANIMATION_DURATION = 300L } } From c2bac10e9229c9ffdc196cfb46b3f0b0522bad87 Mon Sep 17 00:00:00 2001 From: kon3gor Date: Sat, 1 Apr 2023 08:55:35 +0300 Subject: [PATCH 10/11] Finished refactoring --- .../keepass/adapters/NodesAdapter.kt | 1 + .../kunzisoft/keepass/view/TagsListView.kt | 31 ++++++++++--------- .../keepass/database/element/Tags.kt | 11 +++++++ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt index bad789572..2de0d75fc 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt @@ -208,6 +208,7 @@ class NodesAdapter ( && oldItem.foregroundColor == newItem.foregroundColor && oldItem.getOtpElement() == newItem.getOtpElement() && oldItem.containsAttachment() == newItem.containsAttachment() + && oldItem.tags == newItem.tags } else if (oldItem is Group && newItem is Group) { typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries && oldItem.notes == newItem.notes diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index b8d0402c0..f3403c585 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -35,6 +35,7 @@ class TagsListView @JvmOverloads constructor( return } field = value + expandBtn?.setColorFilter(value!!) } var bgColor: Int? = null set(value) { @@ -49,17 +50,17 @@ class TagsListView @JvmOverloads constructor( drawAllTagsAndMeasure() } + private var flow: Flow? = null + private var expandBtn: AppCompatImageView? = null + private var hiddenViews: MutableList = mutableListOf() + private var currentState: State = State.IDLE + private var animationHelper: AnimationHelper? = null + init { inflate(context, R.layout.tags_list_view, this) initialize() } - private var flow: Flow? = null - private var expandBtn: View? = null - private var hiddenViews: MutableList = mutableListOf() - private var currentState: State = State.IDLE - private var animationHelper: AnimationHelper? = null - private fun initialize() { viewTreeObserver.addOnGlobalLayoutListener(InitialMeasuringObserver()) flow = findViewById(R.id.flow) @@ -120,8 +121,10 @@ class TagsListView @JvmOverloads constructor( } hiddenViews[ind].animate().setListener(object : StubAnimatorListener() { override fun onAnimationEnd(p0: Animator?) { - if (isGone) return - hiddenViews[ind].isGone = !isGone + if (!isGone) { + hiddenViews[ind].isGone = !isGone + } + requestLayout() } }).alpha(alpha).start() } @@ -182,10 +185,8 @@ class TagsListView @JvmOverloads constructor( when (currentState) { State.MEASURING_EXPANDED -> { expandedHeight = measuredHeight - post { - currentState = currentState.next() - toggleHiddenViews(false) - } + currentState = currentState.next() + toggleHiddenViews(false) } State.MEASURING_COLLAPSED -> { currentState = currentState.next() @@ -224,6 +225,8 @@ class TagsListView @JvmOverloads constructor( private val VERTICAL_PADDING = 2.dp.intPx private val HORIZONTAL_PADDING = 5.dp.intPx +private const val TAG_TEXT_SIZE = 13f +private val TAG_STROKE = 1.2f.dp.intPx private fun TagsListView.createTagView(tag: String): View { val view = AppCompatTextView(context) @@ -251,7 +254,7 @@ private fun TagsListView.styleTagView(view: AppCompatTextView): View { } view.setPadding(HORIZONTAL_PADDING, VERTICAL_PADDING, HORIZONTAL_PADDING, VERTICAL_PADDING) - view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) + view.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAG_TEXT_SIZE) return view } @@ -263,7 +266,7 @@ private fun TagsListView.createTagBg(): Drawable? { ) as? GradientDrawable bgColor?.let { - bg?.setStroke(1.2f.dp.intPx, it) + bg?.setStroke(TAG_STROKE, it) } return bg diff --git a/database/src/main/java/com/kunzisoft/keepass/database/element/Tags.kt b/database/src/main/java/com/kunzisoft/keepass/database/element/Tags.kt index 608a2f34b..de01af188 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/element/Tags.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/element/Tags.kt @@ -73,6 +73,17 @@ class Tags: Parcelable { return mTags.joinToString(DELIMITER.toString()) } + override fun equals(other: Any?): Boolean { + return when (other) { + !is Tags -> false + else -> mTags == other.toList() + } + } + + override fun hashCode(): Int { + return mTags.hashCode() + } + companion object CREATOR : Parcelable.Creator { const val DELIMITER= ',' const val DELIMITER1= ';' From 5ee264bfb8e9ed3aae5ee6ca536acb838e1a2682 Mon Sep 17 00:00:00 2001 From: kon3gor Date: Wed, 12 Apr 2023 07:09:55 +0300 Subject: [PATCH 11/11] Fix double bang --- app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt index f3403c585..5b9291b3d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TagsListView.kt @@ -4,6 +4,7 @@ import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator import android.content.Context +import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build @@ -35,7 +36,7 @@ class TagsListView @JvmOverloads constructor( return } field = value - expandBtn?.setColorFilter(value!!) + expandBtn?.setColorFilter(value ?: Color.TRANSPARENT) } var bgColor: Int? = null set(value) {