Show OTP Token in entry list

This commit is contained in:
J-Jamet
2021-08-26 17:17:59 +02:00
parent 21c3ccd637
commit 9cce5f645f
9 changed files with 155 additions and 54 deletions

View File

@@ -26,6 +26,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -148,6 +149,7 @@ class NodeAdapter (private val context: Context,
if (oldItem is Entry && newItem is Entry) { if (oldItem is Entry && newItem is Entry) {
typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle() typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle()
&& oldItem.username == newItem.username && oldItem.username == newItem.username
&& oldItem.getOtpElement() == newItem.getOtpElement()
&& oldItem.containsAttachment() == newItem.containsAttachment() && oldItem.containsAttachment() == newItem.containsAttachment()
} else if (oldItem is Group && newItem is Group) { } else if (oldItem is Group && newItem is Group) {
typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries
@@ -357,6 +359,25 @@ class NodeAdapter (private val context: Context,
} }
} }
val otpElement = entry.getOtpElement()
holder.otpContainer?.removeCallbacks(holder.otpRunnable)
holder.otpRunnable = null
if (otpElement != null && otpElement.token.isNotEmpty()) {
holder.otpProgress?.apply {
max = otpElement.period
progress = otpElement.secondsRemaining
}
holder.otpToken?.text = otpElement.token
holder.otpRunnable = Runnable {
holder.otpProgress?.progress = otpElement.secondsRemaining
holder.otpToken?.text = otpElement.token
holder.otpContainer?.postDelayed(holder.otpRunnable, 200)
}
holder.otpContainer?.post(holder.otpRunnable)
holder.otpContainer?.visibility = View.VISIBLE
} else {
holder.otpContainer?.visibility = View.GONE
}
holder.attachmentIcon?.visibility = holder.attachmentIcon?.visibility =
if (entry.containsAttachment()) View.VISIBLE else View.GONE if (entry.containsAttachment()) View.VISIBLE else View.GONE
@@ -413,6 +434,10 @@ class NodeAdapter (private val context: Context,
var text: TextView = itemView.findViewById(R.id.node_text) var text: TextView = itemView.findViewById(R.id.node_text)
var subText: TextView = itemView.findViewById(R.id.node_subtext) var subText: TextView = itemView.findViewById(R.id.node_subtext)
var meta: TextView = itemView.findViewById(R.id.node_meta) var meta: TextView = itemView.findViewById(R.id.node_meta)
var otpContainer: ViewGroup? = itemView.findViewById(R.id.node_otp_container)
var otpProgress: ProgressBar? = itemView.findViewById(R.id.node_otp_progress)
var otpToken: TextView? = itemView.findViewById(R.id.node_otp_token)
var otpRunnable: Runnable? = null
var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers) var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers)
var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon) var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon)
} }

View File

@@ -56,7 +56,7 @@ class OtpModel() : Parcelable {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as OtpElement other as OtpModel
if (type != other.type) return false if (type != other.type) return false
// Token type is important only if it's a TOTP // Token type is important only if it's a TOTP

View File

@@ -185,6 +185,19 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
return secondsRemaining == otpModel.period return secondsRemaining == otpModel.period
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is OtpElement) return false
if (otpModel != other.otpModel) return false
return true
}
override fun hashCode(): Int {
return otpModel.hashCode()
}
companion object { companion object {
const val MIN_HOTP_COUNTER = 0 const val MIN_HOTP_COUNTER = 0
const val MAX_HOTP_COUNTER = Long.MAX_VALUE const val MAX_HOTP_COUNTER = Long.MAX_VALUE

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_selected="true" android:color="@color/white"/>
<item android:color="?attr/colorAccent"/>
</selector>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="270"
android:toDegrees="270">
<shape
android:shape="ring"
android:innerRadiusRatio="2.5"
android:thickness="2dp"
android:useLevel="true">
<solid android:color="?attr/colorAccent" />
</shape>
</rotate>

View File

@@ -7,6 +7,6 @@
android:innerRadiusRatio="2.5" android:innerRadiusRatio="2.5"
android:thickness="2dp" android:thickness="2dp"
android:useLevel="true"> android:useLevel="true">
<solid android:color="@color/orange" /> <solid android:color="@color/entry_info_color" />
</shape> </shape>
</rotate> </rotate>

View File

@@ -25,15 +25,20 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/KeepassDXStyle.Selectable.Item"> style="@style/KeepassDXStyle.Selectable.Item">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:minHeight="56dp"
android:maxHeight="72dp"
app:layout_constraintWidth_percent="@dimen/content_percent" app:layout_constraintWidth_percent="@dimen/content_percent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:background="?android:attr/selectableItemBackground" > android:background="?android:attr/selectableItemBackground" >
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/node_icon" android:id="@+id/node_icon"
android:layout_width="32dp" android:layout_width="32dp"
@@ -44,67 +49,116 @@
android:layout_marginStart="@dimen/image_list_margin_vertical" android:layout_marginStart="@dimen/image_list_margin_vertical"
android:layout_marginRight="@dimen/image_list_margin_vertical" android:layout_marginRight="@dimen/image_list_margin_vertical"
android:layout_marginEnd="@dimen/image_list_margin_vertical" android:layout_marginEnd="@dimen/image_list_margin_vertical"
android:layout_gravity="center"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/ic_blank_32dp" android:src="@drawable/ic_blank_32dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent" /> app:layout_constraintLeft_toLeftOf="parent" />
<LinearLayout <LinearLayout
android:id="@+id/node_container_info"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:paddingTop="4dp"
android:paddingTop="2dp"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:layout_marginLeft="@dimen/image_list_margin_vertical"
android:layout_marginStart="@dimen/image_list_margin_vertical" android:layout_marginStart="@dimen/image_list_margin_vertical"
android:layout_marginRight="@dimen/image_list_margin_vertical" android:layout_marginLeft="@dimen/image_list_margin_vertical"
android:layout_marginEnd="@dimen/image_list_margin_vertical" android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/node_icon" app:layout_constraintStart_toEndOf="@+id/node_icon"
app:layout_constraintLeft_toRightOf="@+id/node_icon" app:layout_constraintLeft_toRightOf="@+id/node_icon"
app:layout_constraintEnd_toStartOf="@+id/node_attachment_icon" app:layout_constraintEnd_toStartOf="@+id/node_options"
app:layout_constraintRight_toLeftOf="@+id/node_attachment_icon"> app:layout_constraintRight_toLeftOf="@+id/node_options">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_text" android:id="@+id/node_text"
android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Entry.Title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
tools:text="Node Title" android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end" android:ellipsize="end"
style="@style/KeepassDXStyle.TextAppearance.Entry.Title" /> android:maxLines="2"
tools:text="Node Title" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_subtext" android:id="@+id/node_subtext"
android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-4dp" android:layout_marginTop="-4dp"
tools:text="Node SubTitle"
android:lines="1" android:lines="1"
android:singleLine="true" android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle" /> tools:text="Node SubTitle" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_meta" android:id="@+id/node_meta"
android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Entry.Meta"
android:layout_width="wrap_content" android:layout_width="wrap_content"
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" android:layout_height="wrap_content"
android:lines="1" android:lines="1"
android:singleLine="true" android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Entry.Meta" /> tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" />
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/node_attachment_icon" <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/node_options"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_marginLeft="@dimen/image_list_margin_vertical" android:layout_marginStart="12dp"
android:layout_marginStart="@dimen/image_list_margin_vertical" android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:src="@drawable/ic_attach_file_white_24dp"
style="@style/KeepassDXStyle.TextAppearance.Entry.Icon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintRight_toRightOf="parent" />
<LinearLayout
android:id="@+id/node_otp_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="18dp"
android:layout_marginRight="18dp"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/node_attachment_icon"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/node_otp_token"
style="@style/KeepassDXStyle.TextAppearance.Entry.Info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="5136" />
<ProgressBar
android:id="@+id/node_otp_progress"
style="@style/KeepassDXStyle.ProgressBar.Circle.NoBackground"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center"
android:layout_marginStart="6dp"
android:layout_marginLeft="6dp"
android:max="100"
android:progress="60" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/node_attachment_icon"
style="@style/KeepassDXStyle.TextAppearance.Entry.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:src="@drawable/ic_attach_file_white_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/node_otp_container" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -25,12 +25,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/KeepassDXStyle.Selectable.Item"> style="@style/KeepassDXStyle.Selectable.Item">
<RelativeLayout <RelativeLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:minHeight="56dp"
android:maxHeight="72dp"
app:layout_constraintWidth_percent="@dimen/content_percent" app:layout_constraintWidth_percent="@dimen/content_percent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:background="?android:attr/selectableItemBackground" > android:background="?android:attr/selectableItemBackground" >
@@ -66,14 +70,14 @@
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toEndOf="@+id/node_icon"
android:layout_toRightOf="@+id/node_icon"
android:layout_toStartOf="@+id/node_image_identifier" android:layout_toStartOf="@+id/node_image_identifier"
android:layout_toLeftOf="@+id/node_image_identifier" android:layout_toLeftOf="@+id/node_image_identifier"
android:orientation="vertical" android:layout_toEndOf="@+id/node_icon"
android:paddingTop="2dp" android:layout_toRightOf="@+id/node_icon"
android:paddingBottom="4dp"> android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_text" android:id="@+id/node_text"
@@ -84,25 +88,27 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLines="2" android:maxLines="2"
tools:text="Node Title" /> tools:text="Node Title" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_subtext" android:id="@+id/node_subtext"
style="@style/KeepassDXStyle.TextAppearance.Group.SubTitle" style="@style/KeepassDXStyle.TextAppearance.Group.SubTitle"
android:visibility="gone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="-4dp" android:layout_marginTop="-4dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:lines="1" android:lines="1"
android:singleLine="true" android:singleLine="true"
android:visibility="gone"
tools:text="Node SubTitle" /> tools:text="Node SubTitle" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_meta" android:id="@+id/node_meta"
android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Group.Meta"
android:layout_width="wrap_content" android:layout_width="wrap_content"
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" android:layout_height="wrap_content"
android:lines="1" android:lines="1"
android:singleLine="true" android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Group.Meta" /> tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" />
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView

View File

@@ -354,6 +354,7 @@
<style name="KeepassDXStyle.TextAppearance.Entry.Title" parent="KeepassDXStyle.TextAppearance"> <style name="KeepassDXStyle.TextAppearance.Entry.Title" parent="KeepassDXStyle.TextAppearance">
<item name="android:textColor">@color/entry_title_color</item> <item name="android:textColor">@color/entry_title_color</item>
<item name="android:tint">@color/entry_title_color</item> <item name="android:tint">@color/entry_title_color</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.Entry.SubTitle" parent="KeepassDXStyle.TextAppearance.Small"> <style name="KeepassDXStyle.TextAppearance.Entry.SubTitle" parent="KeepassDXStyle.TextAppearance.Small">
@@ -365,6 +366,12 @@
<item name="android:tint">@color/entry_title_color</item> <item name="android:tint">@color/entry_title_color</item>
<item name="android:textSize">11sp</item> <item name="android:textSize">11sp</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.Entry.Info" parent="KeepassDXStyle.TextAppearance.Small">
<item name="android:textColor">@color/entry_info_color</item>
<item name="android:tint">@color/entry_info_color</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item>
</style>
<style name="KeepassDXStyle.TextAppearance.Entry.Icon" parent="KeepassDXStyle.TextAppearance.Small"> <style name="KeepassDXStyle.TextAppearance.Entry.Icon" parent="KeepassDXStyle.TextAppearance.Small">
<item name="tint">@color/entry_subtitle_color</item> <item name="tint">@color/entry_subtitle_color</item>
</style> </style>
@@ -475,6 +482,10 @@
</style> </style>
<!-- Progress bar --> <!-- Progress bar -->
<style name="KeepassDXStyle.ProgressBar.Circle.NoBackground" parent="Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/foreground_progress_circle</item>
<item name="android:background">@null</item>
</style>
<style name="KeepassDXStyle.ProgressBar.Circle" parent="Widget.AppCompat.ProgressBar.Horizontal"> <style name="KeepassDXStyle.ProgressBar.Circle" parent="Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/foreground_progress_circle</item> <item name="android:progressDrawable">@drawable/foreground_progress_circle</item>
<item name="android:background">@drawable/background_progress_circle</item> <item name="android:background">@drawable/background_progress_circle</item>