Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download icon #1146

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.INTERNET" />

<application
android:label="@string/app_name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.ReplaceFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
Expand All @@ -40,10 +41,7 @@ import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.view.TemplateEditView
import com.kunzisoft.keepass.view.collapse
import com.kunzisoft.keepass.view.expand
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel

class EntryEditFragment: DatabaseFragment() {
Expand Down Expand Up @@ -105,6 +103,9 @@ class EntryEditFragment: DatabaseFragment() {
setOnPasswordGenerationActionClickListener { field ->
mEntryEditViewModel.requestPasswordSelection(field)
}
setOnDownloadIconActionClickListener { url ->
mEntryEditViewModel.requestDownloadIcon(url, requireContext(), mDatabase)
}
setOnDateInstantClickListener { dateInstant ->
mEntryEditViewModel.requestDateTimeSelection(dateInstant)
}
Expand Down Expand Up @@ -151,6 +152,31 @@ class EntryEditFragment: DatabaseFragment() {
templateView.setPasswordField(passwordField)
}

mEntryEditViewModel.onIconDownloaded.observe(viewLifecycleOwner) { state ->
when (state.downloadState) {
EntryEditViewModel.DownloadState.NONE -> {
// Do nothing
}
EntryEditViewModel.DownloadState.START -> {
templateView.setDownloadIconProgressVisible(true)
}
EntryEditViewModel.DownloadState.COMPLETE -> {
templateView.setDownloadIconProgressVisible(false)
Snackbar
.make(rootView, R.string.download_complete, Snackbar.LENGTH_LONG)
.asSuccess()
.show()
}
EntryEditViewModel.DownloadState.ERROR -> {
templateView.setDownloadIconProgressVisible(false)
Snackbar
.make(rootView, R.string.download_icon_error, Snackbar.LENGTH_LONG)
.asError()
.show()
}
}
}

mEntryEditViewModel.onDateSelected.observe(viewLifecycleOwner) { viewModelDate ->
// Save the date
templateView.setCurrentDateTimeValue(viewModelDate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import kotlinx.coroutines.*
* Class to invoke action in a separate IO thread
*/
class IOActionTask<T>(
private val action: () -> T ,
private val action: suspend () -> T ,
private val afterActionDatabaseListener: ((T?) -> Unit)? = null) {

private val mainScope = CoroutineScope(Dispatchers.Main)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import com.kunzisoft.keepass.utils.readBytes4ToUInt
import java.io.*
import java.util.*
import kotlin.collections.ArrayList
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine


class Database {
Expand Down Expand Up @@ -138,10 +140,20 @@ class Database {
mDatabaseKDBX?.buildNewCustomIcon(null, result)
}

suspend fun buildNewCustomIcon(): Pair<IconImageCustom?, BinaryData?> {
return suspendCoroutine { coroutine ->
buildNewCustomIcon { icon, binary -> coroutine.resume(Pair(icon, binary)) }
}
}

fun isCustomIconBinaryDuplicate(binaryData: BinaryData): Boolean {
return mDatabaseKDBX?.isCustomIconBinaryDuplicate(binaryData) ?: false
}

fun getIcon(binary: BinaryData): IconImageCustom? {
return mDatabaseKDBX?.getCustomIcon(binary)
}

fun removeCustomIcon(customIcon: IconImageCustom) {
iconDrawableFactory.clearFromCache(customIcon)
iconsManager.removeCustomIcon(binaryCache, customIcon.uuid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return this.iconsManager.getIcon(iconUuid)
}

fun getCustomIcon(binary: BinaryData): IconImageCustom? {
return this.iconsManager.getIcon(binary)
}

fun isTemplatesGroupEnabled(): Boolean {
return entryTemplatesGroup != UUID_ZERO
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ class IconsManager(binaryCache: BinaryCache) {
return customCache.getCustomIcon(iconUuid) ?: IconImageCustom(iconUuid)
}

fun getIcon(data: BinaryData): IconImageCustom? {
var toReturn: IconImageCustom? = null
doForEachCustomIcon { customIcon, binary ->
if (data.binaryHash() == binary.binaryHash()) {
toReturn = customIcon
}
}
return toReturn
}

fun isCustomIconBinaryDuplicate(binaryData: BinaryData): Boolean {
return customCache.isBinaryDuplicate(binaryData)
}
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
this.mOnPasswordGenerationActionClickListener = listener
}

private var mOnDownloadIconActionClickListener: ((String) -> Unit)? = null
fun setOnDownloadIconActionClickListener(listener: ((String) -> Unit)?) {
this.mOnDownloadIconActionClickListener = listener
}

private var mOnDateInstantClickListener: ((DateInstant) -> Unit)? = null
fun setOnDateInstantClickListener(listener: ((DateInstant) -> Unit)?) {
this.mOnDateInstantClickListener = listener
Expand Down Expand Up @@ -114,6 +119,11 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
mOnPasswordGenerationActionClickListener?.invoke(field)
}, R.drawable.ic_generate_password_white_24dp)
}
if (templateAttribute.options.isLink()) {
setOnActionClickListener({
mOnDownloadIconActionClickListener?.invoke(value)
}, R.drawable.ic_downloading_white_24dp)
}
}
}

Expand Down Expand Up @@ -155,6 +165,13 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
return Field(TemplateField.LABEL_PASSWORD, ProtectedString(true, passwordView?.value ?: ""))
}

fun setDownloadIconProgressVisible(visible: Boolean) {
val urlView: TextEditFieldView? = findViewWithTag(FIELD_URL_TAG)
if (urlView != null) {
urlView.isProgressVisible = visible
}
}

private fun setCurrentDateTimeSelection(action: (dateInstant: DateInstant) -> DateInstant) {
mTempDateTimeViewId?.let { viewId ->
val dateTimeView = getFieldViewById(viewId)
Expand Down
70 changes: 53 additions & 17 deletions app/src/main/java/com/kunzisoft/keepass/view/TextEditFieldView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import android.text.InputType
import android.util.AttributeSet
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.Gravity
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.RelativeLayout
import androidx.annotation.DrawableRes
import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
Expand All @@ -27,7 +31,9 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,

private var labelViewId = ViewCompat.generateViewId()
private var valueViewId = ViewCompat.generateViewId()
private var actionImageContainerId = ViewCompat.generateViewId()
private var actionImageButtonId = ViewCompat.generateViewId()
private var actionImageProgressId = ViewCompat.generateViewId()

private val labelView = TextInputLayout(context).apply {
layoutParams = LayoutParams(
Expand All @@ -51,49 +57,67 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
}
maxLines = 1
}
private var actionImageButton = AppCompatImageButton(
ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0).apply {
private var actionImageContainer = FrameLayout(context).apply {
layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT).also {
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
).also {
it.topMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
12f,
resources.displayMetrics
TypedValue.COMPLEX_UNIT_DIP,
12f,
resources.displayMetrics
).toInt()
it.addRule(ALIGN_PARENT_RIGHT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it.addRule(ALIGN_PARENT_END)
}
}
visibility = View.GONE
}
private var actionImageButton = AppCompatImageButton(
ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0
).apply {
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
contentDescription = context.getString(R.string.menu_edit)
}
private var actionImageProgress = ProgressBar(
ContextThemeWrapper(context, R.style.KeepassDXStyle_ProgressBar_Circle_Indeterminate)
).apply {
val size = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
24f,
resources.displayMetrics
).toInt()
layoutParams = FrameLayout.LayoutParams(size, size).apply {
gravity = Gravity.CENTER
}
visibility = View.GONE
}

init {
// Manually write view to avoid view id bugs
buildViews()
labelView.addView(valueView)
addView(labelView)
addView(actionImageButton)
actionImageContainer.addView(actionImageProgress)
actionImageContainer.addView(actionImageButton)
addView(actionImageContainer)
}

private fun buildViews() {
labelView.apply {
id = labelViewId
layoutParams = (layoutParams as LayoutParams?).also {
it?.addRule(LEFT_OF, actionImageButtonId)
it?.addRule(LEFT_OF, actionImageContainerId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(START_OF, actionImageButtonId)
it?.addRule(START_OF, actionImageContainerId)
}
}
}
valueView.apply {
id = valueViewId
}
actionImageButton.apply {
id = actionImageButtonId
}
valueView.id = valueViewId
actionImageContainer.id = actionImageContainerId
actionImageButton.id = actionImageButtonId
actionImageProgress.id = actionImageProgressId
}

override fun applyFontVisibility(fontInVisibility: Boolean) {
Expand All @@ -114,7 +138,9 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
// Define views Ids with label value
labelViewId = "labelViewId $value".hashCode()
valueViewId = "valueViewId $value".hashCode()
actionImageContainerId = "actionImageContainerId $value".hashCode()
actionImageButtonId = "actionImageButtonId $value".hashCode()
actionImageProgressId = "actionImageProgressId $value".hashCode()
buildViews()
}

Expand Down Expand Up @@ -174,7 +200,7 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
actionImageButton.setImageDrawable(ContextCompat.getDrawable(context, it))
}
actionImageButton.setOnClickListener(onActionClickListener)
actionImageButton.visibility = if (onActionClickListener == null) View.GONE else View.VISIBLE
actionImageContainer.isVisible = onActionClickListener != null
}

override var isFieldVisible: Boolean
Expand All @@ -185,6 +211,16 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
isVisible = value
}

var isProgressVisible: Boolean
get() {
return actionImageProgress.isVisible
}
set(value) {
// Toggle visibility between the button and the progress
actionImageProgress.isVisible = value
actionImageButton.isInvisible = value
}

companion object {
const val MAX_CHARS_LIMIT = Integer.MAX_VALUE
const val MAX_LINES_LIMIT = Integer.MAX_VALUE
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.TextView
import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import com.google.android.material.snackbar.Snackbar
Expand Down Expand Up @@ -92,6 +93,14 @@ fun TextView.customLink(listener: (View) -> Unit) {
this.setText(spannableString, TextView.BufferType.SPANNABLE)
}

fun Snackbar.asSuccess(): Snackbar {
this.view.apply {
setBackgroundColor(ContextCompat.getColor(context, R.color.blue_light))
findViewById<TextView>(R.id.snackbar_text).setTextColor(Color.WHITE)
}
return this
}

fun Snackbar.asError(): Snackbar {
this.view.apply {
setBackgroundColor(Color.RED)
Expand Down
Loading