Skip to content

Commit d86dbe7

Browse files
Add check for duplicates upon saving entry
1 parent 6f27014 commit d86dbe7

File tree

5 files changed

+266
-0
lines changed

5 files changed

+266
-0
lines changed

app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.beemdevelopment.aegis.ui;
22

33
import android.content.Intent;
4+
import android.content.res.Resources;
45
import android.graphics.Bitmap;
56
import android.graphics.drawable.BitmapDrawable;
67
import android.graphics.drawable.Drawable;
@@ -27,6 +28,7 @@
2728
import androidx.annotation.NonNull;
2829
import androidx.annotation.Nullable;
2930
import androidx.appcompat.app.ActionBar;
31+
import androidx.appcompat.app.AlertDialog;
3032

3133
import com.amulyakhare.textdrawable.TextDrawable;
3234
import com.avito.android.krop.KropView;
@@ -87,6 +89,7 @@
8789
import java.util.Comparator;
8890
import java.util.Date;
8991
import java.util.HashSet;
92+
import java.util.Iterator;
9093
import java.util.List;
9194
import java.util.Locale;
9295
import java.util.Set;
@@ -852,10 +855,94 @@ private boolean onSave() {
852855
return false;
853856
}
854857

858+
for (VaultEntry existing : _vaultManager.getVault().getEntries()) {
859+
if (entry.hasSameNameAndIssuer(existing)) {
860+
861+
showDuplicateBottomSheet(entry);
862+
return false;
863+
}
864+
}
865+
855866
addAndFinish(entry);
856867
return true;
857868
}
858869

870+
private void showDuplicateBottomSheet(VaultEntry newEntry)
871+
{
872+
BottomSheetDialog dialog = new BottomSheetDialog(this);
873+
View view = getLayoutInflater().inflate(R.layout.dialog_duplicate_entry, null);
874+
dialog.setContentView(view);
875+
876+
dialog.setCancelable(false);
877+
878+
View overwrite = view.findViewById(R.id.overwrite_entry);
879+
View addSuffix = view.findViewById(R.id.create_new_entry);
880+
View cancel = view.findViewById(R.id.cancel_save);
881+
882+
TextView suffixSubtext = view.findViewById(R.id.duplicate_suffix_subtitle);
883+
884+
String baseName = newEntry.getName();
885+
Set<String> existingNames = new HashSet<>();
886+
for (VaultEntry e : _vaultManager.getVault().getEntries()) {
887+
if (e.getIssuer().equals(newEntry.getIssuer())) {
888+
existingNames.add(e.getName());
889+
}
890+
}
891+
892+
int counter = 2;
893+
String newName;
894+
do {
895+
newName = baseName + " #" + counter++;
896+
} while (existingNames.contains(newName));
897+
898+
if (suffixSubtext != null) {
899+
suffixSubtext.setText(getString(R.string.dialog_duplicate_entry_suffix_subtitle, newName));
900+
}
901+
902+
overwrite.setOnClickListener(v -> {
903+
List<VaultEntry> duplicates = new ArrayList<>();
904+
for (VaultEntry existing : _vaultManager.getVault().getEntries()) {
905+
if (existing.hasSameNameAndIssuer(newEntry)) {
906+
duplicates.add(existing);
907+
}
908+
}
909+
910+
Resources res = getResources();
911+
String message = res.getQuantityString(
912+
R.plurals.dialog_duplicate_entry_overwrite_dialog_message,
913+
duplicates.size(),
914+
duplicates.size(),
915+
newEntry.getIssuer(),
916+
newEntry.getName()
917+
);
918+
919+
new AlertDialog.Builder(this)
920+
.setTitle(R.string.dialog_duplicate_entry_overwrite_dialog_title)
921+
.setMessage(message)
922+
.setPositiveButton(R.string.action_delete, (d, which) -> {
923+
for (VaultEntry dup : duplicates) {
924+
_vaultManager.getVault().removeEntry(dup);
925+
}
926+
927+
dialog.dismiss();
928+
addAndFinish(newEntry);
929+
})
930+
.setNegativeButton(android.R.string.no, null)
931+
.show();
932+
});
933+
934+
String finalNewName = newName;
935+
addSuffix.setOnClickListener(v -> {
936+
newEntry.setName(finalNewName);
937+
dialog.dismiss();
938+
addAndFinish(newEntry);
939+
});
940+
941+
cancel.setOnClickListener(v -> dialog.dismiss());
942+
943+
Dialogs.showSecureDialog(dialog);
944+
}
945+
859946
private static void setViewEnabled(View view, boolean enabled) {
860947
view.setEnabled(enabled);
861948

app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ && isFavorite() == entry.isFavorite()
239239
&& getGroups().equals(entry.getGroups());
240240
}
241241

242+
public boolean hasSameNameAndIssuer(VaultEntry entry) {
243+
return getName().equals(entry.getName()) && getIssuer().equals(entry.getIssuer());
244+
}
245+
242246
/**
243247
* Reports whether this entry has its values set to the defaults.
244248
*/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="960"
5+
android:viewportHeight="960">
6+
<path
7+
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"
8+
android:fillColor="#e3e3e3"/>
9+
</vector>
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:app="http://schemas.android.com/apk/res-auto"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content"
7+
android:orientation="vertical"
8+
android:padding="20dp">
9+
10+
<TextView
11+
android:layout_width="match_parent"
12+
android:layout_height="wrap_content"
13+
android:gravity="center"
14+
android:paddingTop="16dp"
15+
android:text="@string/dialog_duplicate_entry_title"
16+
android:textSize="20sp" />
17+
18+
<TextView
19+
android:id="@+id/duplicate_warning_text"
20+
android:layout_width="match_parent"
21+
android:layout_height="wrap_content"
22+
android:text="@string/dialog_duplicate_entry_message"
23+
android:textSize="16sp"
24+
android:textColor="?android:attr/textColorPrimary"
25+
android:paddingTop="10dp"
26+
android:paddingBottom="20dp" />
27+
28+
<LinearLayout
29+
android:id="@+id/overwrite_entry"
30+
android:layout_width="match_parent"
31+
android:layout_height="wrap_content"
32+
android:orientation="horizontal"
33+
android:paddingVertical="15dp"
34+
android:paddingHorizontal="10dp"
35+
android:background="?android:attr/selectableItemBackground"
36+
android:clickable="true"
37+
android:focusable="true">
38+
39+
<ImageView
40+
android:layout_width="24dp"
41+
android:layout_height="24dp"
42+
android:layout_gravity="center_vertical"
43+
android:src="@drawable/ic_outline_brush_24"
44+
app:tint="?attr/colorOnSurfaceVariant" />
45+
46+
<LinearLayout
47+
android:layout_width="0dp"
48+
android:layout_height="wrap_content"
49+
android:layout_weight="1"
50+
android:orientation="vertical"
51+
android:layout_marginStart="16dp">
52+
53+
<TextView
54+
android:layout_width="wrap_content"
55+
android:layout_height="wrap_content"
56+
android:text="@string/dialog_duplicate_entry_overwrite_title"
57+
android:textSize="17sp"
58+
android:textStyle="bold" />
59+
60+
<TextView
61+
android:layout_width="wrap_content"
62+
android:layout_height="wrap_content"
63+
android:text="@string/dialog_duplicate_entry_overwrite_subtitle"
64+
android:textSize="14sp"
65+
android:textColor="?attr/colorOnSurfaceVariant" />
66+
</LinearLayout>
67+
</LinearLayout>
68+
69+
<LinearLayout
70+
android:id="@+id/create_new_entry"
71+
android:layout_width="match_parent"
72+
android:layout_height="wrap_content"
73+
android:orientation="horizontal"
74+
android:paddingVertical="15dp"
75+
android:paddingHorizontal="10dp"
76+
android:background="?android:attr/selectableItemBackground"
77+
android:clickable="true"
78+
android:focusable="true">
79+
80+
<ImageView
81+
android:layout_width="24dp"
82+
android:layout_height="24dp"
83+
android:layout_gravity="center_vertical"
84+
android:src="@drawable/ic_tag_24"
85+
app:tint="?attr/colorOnSurfaceVariant" />
86+
87+
<LinearLayout
88+
android:layout_width="0dp"
89+
android:layout_height="wrap_content"
90+
android:layout_weight="1"
91+
android:orientation="vertical"
92+
android:layout_marginStart="16dp">
93+
94+
<TextView
95+
android:layout_width="wrap_content"
96+
android:layout_height="wrap_content"
97+
android:text="@string/dialog_duplicate_entry_suffix_title"
98+
android:textSize="17sp"
99+
android:textStyle="bold" />
100+
101+
<TextView
102+
android:id="@+id/duplicate_suffix_subtitle"
103+
android:layout_width="wrap_content"
104+
android:layout_height="wrap_content"
105+
android:text="@string/dialog_duplicate_entry_suffix_subtitle"
106+
android:textSize="14sp"
107+
android:textColor="?attr/colorOnSurfaceVariant" />
108+
</LinearLayout>
109+
</LinearLayout>
110+
111+
<LinearLayout
112+
android:id="@+id/cancel_save"
113+
android:layout_width="match_parent"
114+
android:layout_height="wrap_content"
115+
android:orientation="horizontal"
116+
android:paddingVertical="15dp"
117+
android:paddingHorizontal="10dp"
118+
android:background="?android:attr/selectableItemBackground"
119+
android:clickable="true"
120+
android:focusable="true">
121+
122+
<ImageView
123+
android:layout_width="24dp"
124+
android:layout_height="24dp"
125+
android:layout_gravity="center_vertical"
126+
android:src="@drawable/ic_outline_close_24"
127+
app:tint="?attr/colorOnSurfaceVariant" />
128+
129+
<LinearLayout
130+
android:layout_width="0dp"
131+
android:layout_height="wrap_content"
132+
android:layout_weight="1"
133+
android:orientation="vertical"
134+
android:layout_marginStart="16dp">
135+
136+
<TextView
137+
android:layout_width="wrap_content"
138+
android:layout_height="wrap_content"
139+
android:text="@string/dialog_duplicate_entry_cancel_title"
140+
android:textSize="17sp"
141+
android:textStyle="bold" />
142+
143+
<TextView
144+
android:layout_width="wrap_content"
145+
android:layout_height="wrap_content"
146+
android:text="@string/dialog_duplicate_entry_cancel_subtitle"
147+
android:textSize="14sp"
148+
android:textColor="?attr/colorOnSurfaceVariant" />
149+
</LinearLayout>
150+
</LinearLayout>
151+
</LinearLayout>

app/src/main/res/values/strings.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,21 @@
373373
<string name="note" comment="Users can add a note to an entry">Note</string>
374374
<string name="clear">Clear</string>
375375

376+
<string name="dialog_duplicate_entry_title">Duplicate Entry</string>
377+
<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>
378+
<string name="dialog_duplicate_entry_overwrite_title">Overwrite existing entry/entries</string>
379+
<string name="dialog_duplicate_entry_overwrite_subtitle">Replace the existing entry or entries with the new one. This action cannot be undone</string>
380+
<string name="dialog_duplicate_entry_suffix_title">Add suffix</string>
381+
<string name="dialog_duplicate_entry_suffix_subtitle">Add a suffix to the name of this new entry. The new name will be: %s</string>
382+
<string name="dialog_duplicate_entry_cancel_title">Cancel save</string>
383+
<string name="dialog_duplicate_entry_cancel_subtitle">Allows you to edit the entry before attempting to save it again</string>
384+
<plurals name="dialog_duplicate_entry_overwrite_dialog_message">
385+
<item quantity="one">Are you sure you want to delete %d entry with the following name:\n\n%s - %s</item>
386+
<item quantity="other">Are you sure you want to delete %d entries with the following name:\n\n%s - %s</item>
387+
</plurals>
388+
389+
<string name="dialog_duplicate_entry_overwrite_dialog_title">Confirm Deletion</string>
390+
376391
<string name="pref_haptic_feedback_summary">Make your device vibrate when codes are refreshing</string>
377392
<string name="pref_haptic_feedback_title">Haptic feedback</string>
378393
<string name="pref_highlight_entry_title">Highlight tokens when tapped</string>

0 commit comments

Comments
 (0)