Merge pull request #1652 from michaelschattgen/feature/duplicate-check

Add check for duplicates upon saving entry
This commit is contained in:
Alexander Bakker 2025-05-29 13:18:15 +02:00 committed by GitHub
commit 8d667cd26c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 264 additions and 0 deletions

View file

@ -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<String> 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<VaultEntry> 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);

View file

@ -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.
*/

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m240,800 l40,-160L120,640l20,-80h160l40,-160L180,400l20,-80h160l40,-160h80l-40,160h160l40,-160h80l-40,160h160l-20,80L660,400l-40,160h160l-20,80L600,640l-40,160h-80l40,-160L360,640l-40,160h-80ZM380,560h160l40,-160L420,400l-40,160Z"
android:fillColor="#e3e3e3"/>
</vector>

View file

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="16dp"
android:text="@string/dialog_duplicate_entry_title"
android:textSize="20sp" />
<TextView
android:id="@+id/duplicate_warning_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_duplicate_entry_message"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"
android:paddingTop="10dp"
android:paddingBottom="20dp" />
<LinearLayout
android:id="@+id/overwrite_entry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingVertical="15dp"
android:paddingHorizontal="10dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_outline_brush_24"
app:tint="?attr/colorOnSurfaceVariant" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_duplicate_entry_overwrite_title"
android:textSize="17sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_duplicate_entry_overwrite_subtitle"
android:textSize="14sp"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/create_new_entry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingVertical="15dp"
android:paddingHorizontal="10dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_tag_24"
app:tint="?attr/colorOnSurfaceVariant" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_duplicate_entry_suffix_title"
android:textSize="17sp"
android:textStyle="bold" />
<TextView
android:id="@+id/duplicate_suffix_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_duplicate_entry_suffix_subtitle"
android:textSize="14sp"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/cancel_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingVertical="15dp"
android:paddingHorizontal="10dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_outline_close_24"
app:tint="?attr/colorOnSurfaceVariant" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_duplicate_entry_cancel_title"
android:textSize="17sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_duplicate_entry_cancel_subtitle"
android:textSize="14sp"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -373,6 +373,21 @@
<string name="note" comment="Users can add a note to an entry">Note</string>
<string name="clear">Clear</string>
<string name="dialog_duplicate_entry_title">Duplicate entry</string>
<string name="dialog_duplicate_entry_message">This entry has the same name and issuer as one or more existing entries. How would you like to proceed?</string>
<string name="dialog_duplicate_entry_overwrite_title">Overwrite existing entry/entries</string>
<string name="dialog_duplicate_entry_overwrite_subtitle">Replace the existing entry or entries with the new one. This action cannot be undone</string>
<string name="dialog_duplicate_entry_suffix_title">Add suffix</string>
<string name="dialog_duplicate_entry_suffix_subtitle">Add a suffix to the name of this new entry. The new name will be: %s</string>
<string name="dialog_duplicate_entry_cancel_title">Cancel save</string>
<string name="dialog_duplicate_entry_cancel_subtitle">Allows you to edit the entry before attempting to save it again</string>
<plurals name="dialog_duplicate_entry_overwrite_dialog_message">
<item quantity="one">Are you sure you want to delete %d entry with the following name:\n\n%s - %s</item>
<item quantity="other">Are you sure you want to delete %d entries with the following name:\n\n%s - %s</item>
</plurals>
<string name="dialog_duplicate_entry_overwrite_dialog_title">Confirm deletion</string>
<string name="pref_haptic_feedback_summary">Make your device vibrate when codes are refreshing</string>
<string name="pref_haptic_feedback_title">Haptic feedback</string>
<string name="pref_highlight_entry_title">Highlight tokens when tapped</string>