From 1335be678764f746acf4b4dae84440ab6e6f27df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Sch=C3=A4ttgen?= Date: Wed, 28 May 2025 16:18:01 +0200 Subject: [PATCH] Add check for duplicates upon saving entry --- .../aegis/ui/EditEntryActivity.java | 85 ++++++++++ .../aegis/vault/VaultEntry.java | 4 + app/src/main/res/drawable/ic_tag_24.xml | 9 ++ .../res/layout/dialog_duplicate_entry.xml | 151 ++++++++++++++++++ app/src/main/res/values/strings.xml | 15 ++ 5 files changed, 264 insertions(+) create mode 100644 app/src/main/res/drawable/ic_tag_24.xml create mode 100644 app/src/main/res/layout/dialog_duplicate_entry.xml diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java index a7c92591..36011861 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java @@ -1,6 +1,7 @@ package com.beemdevelopment.aegis.ui; import android.content.Intent; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -27,6 +28,7 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; import com.amulyakhare.textdrawable.TextDrawable; import com.avito.android.krop.KropView; @@ -87,6 +89,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; @@ -852,10 +855,92 @@ public class EditEntryActivity extends AegisActivity { return false; } + if (_isNew) { + for (VaultEntry existing : _vaultManager.getVault().getEntries()) { + if (entry.hasSameNameAndIssuer(existing)) { + showDuplicateBottomSheet(entry); + return false; + } + } + } + addAndFinish(entry); return true; } + private void showDuplicateBottomSheet(VaultEntry newEntry) { + BottomSheetDialog dialog = new BottomSheetDialog(this); + View view = getLayoutInflater().inflate(R.layout.dialog_duplicate_entry, null); + dialog.setContentView(view); + + dialog.setCancelable(false); + + View overwrite = view.findViewById(R.id.overwrite_entry); + View addSuffix = view.findViewById(R.id.create_new_entry); + View cancel = view.findViewById(R.id.cancel_save); + + TextView suffixSubtext = view.findViewById(R.id.duplicate_suffix_subtitle); + + String baseName = newEntry.getName(); + Set existingNames = new HashSet<>(); + for (VaultEntry e : _vaultManager.getVault().getEntries()) { + if (e.getIssuer().equals(newEntry.getIssuer())) { + existingNames.add(e.getName()); + } + } + + int counter = 2; + String newName; + do { + newName = baseName + " #" + counter++; + } while (existingNames.contains(newName)); + + suffixSubtext.setText(getString(R.string.dialog_duplicate_entry_suffix_subtitle, newName)); + + overwrite.setOnClickListener(v -> { + List duplicates = new ArrayList<>(); + for (VaultEntry existing : _vaultManager.getVault().getEntries()) { + if (existing.hasSameNameAndIssuer(newEntry)) { + duplicates.add(existing); + } + } + + Resources res = getResources(); + String message = res.getQuantityString( + R.plurals.dialog_duplicate_entry_overwrite_dialog_message, + duplicates.size(), + duplicates.size(), + newEntry.getIssuer(), + newEntry.getName() + ); + + new MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_duplicate_entry_overwrite_dialog_title) + .setMessage(message) + .setPositiveButton(R.string.action_delete, (d, which) -> { + for (VaultEntry dup : duplicates) { + _vaultManager.getVault().removeEntry(dup); + } + + dialog.dismiss(); + addAndFinish(newEntry); + }) + .setNegativeButton(android.R.string.no, null) + .show(); + }); + + String finalNewName = newName; + addSuffix.setOnClickListener(v -> { + newEntry.setName(finalNewName); + dialog.dismiss(); + addAndFinish(newEntry); + }); + + cancel.setOnClickListener(v -> dialog.dismiss()); + + Dialogs.showSecureDialog(dialog); + } + private static void setViewEnabled(View view, boolean enabled) { view.setEnabled(enabled); diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java index 1fafb5ab..d351ed84 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java @@ -239,6 +239,10 @@ public class VaultEntry extends UUIDMap.Value { && getGroups().equals(entry.getGroups()); } + public boolean hasSameNameAndIssuer(VaultEntry entry) { + return getName().equals(entry.getName()) && getIssuer().equals(entry.getIssuer()); + } + /** * Reports whether this entry has its values set to the defaults. */ diff --git a/app/src/main/res/drawable/ic_tag_24.xml b/app/src/main/res/drawable/ic_tag_24.xml new file mode 100644 index 00000000..66360540 --- /dev/null +++ b/app/src/main/res/drawable/ic_tag_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/dialog_duplicate_entry.xml b/app/src/main/res/layout/dialog_duplicate_entry.xml new file mode 100644 index 00000000..f55d3d71 --- /dev/null +++ b/app/src/main/res/layout/dialog_duplicate_entry.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e627421..08ffbf1a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -373,6 +373,21 @@ Note Clear + Duplicate entry + This entry has the same name and issuer as one or more existing entries. How would you like to proceed? + Overwrite existing entry/entries + Replace the existing entry or entries with the new one. This action cannot be undone + Add suffix + Add a suffix to the name of this new entry. The new name will be: %s + Cancel save + Allows you to edit the entry before attempting to save it again + + Are you sure you want to delete %d entry with the following name:\n\n%s - %s + Are you sure you want to delete %d entries with the following name:\n\n%s - %s + + + Confirm deletion + Make your device vibrate when codes are refreshing Haptic feedback Highlight tokens when tapped