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

Support for Adding Multiple Groups #1349

Open
wants to merge 1 commit into
base: master
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.action.ViewActions.pressBack;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
Expand Down Expand Up @@ -118,10 +119,11 @@ public void testOverall() {
onView(withId(R.id.rvKeyProfiles)).perform(RecyclerViewActions.actionOnItemAtPosition(entryPosOffset + 1, longClick()));
onView(withId(R.id.action_edit)).perform(click());
onView(withId(R.id.text_name)).perform(clearText(), typeText("Bob"), closeSoftKeyboard());
onView(withId(R.id.dropdown_group)).perform(click());
onView(withText(R.string.new_group)).inRoot(RootMatchers.isPlatformPopup()).perform(click());
onView(withId(R.id.text_group)).perform(click());
onView(withId(R.id.addGroup)).inRoot(RootMatchers.isDialog()).perform(click());
onView(withId(R.id.text_input)).perform(typeText(_groupName), closeSoftKeyboard());
onView(withId(android.R.id.button1)).perform(click());
onView(withText(R.string.save)).perform(click());
onView(isRoot()).perform(pressBack());
onView(withId(android.R.id.button1)).perform(click());

Expand Down Expand Up @@ -188,7 +190,7 @@ private void addEntry(VaultEntry entry) {
onView(withId(R.id.fab)).perform(click());
onView(withId(R.id.fab_enter)).perform(click());

onView(withId(R.id.accordian_header)).perform(click());
onView(withId(R.id.accordian_header)).perform(scrollTo(), click());
onView(withId(R.id.text_name)).perform(typeText(entry.getName()), closeSoftKeyboard());
onView(withId(R.id.text_issuer)).perform(typeText(entry.getIssuer()), closeSoftKeyboard());

Expand All @@ -208,7 +210,7 @@ private void addEntry(VaultEntry entry) {
throw new RuntimeException(String.format("Unexpected entry type: %s", entry.getInfo().getClass().getSimpleName()));
}

onView(withId(R.id.dropdown_type)).perform(click());
onView(withId(R.id.dropdown_type)).perform(scrollTo(), click());
onView(withText(otpType)).inRoot(RootMatchers.isPlatformPopup()).perform(click());
}

Expand Down
162 changes: 112 additions & 50 deletions app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.beemdevelopment.aegis.ui;

import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
Expand All @@ -16,6 +15,7 @@
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
Expand Down Expand Up @@ -68,6 +68,8 @@
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
Expand All @@ -85,7 +87,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
Expand All @@ -107,6 +108,8 @@ public class EditEntryActivity extends AegisActivity {

private TextInputEditText _textName;
private TextInputEditText _textIssuer;
private TextInputLayout _textGroupLayout;
private TextInputEditText _textGroup;
private TextInputEditText _textPeriodCounter;
private TextInputLayout _textPeriodCounterLayout;
private TextInputEditText _textDigits;
Expand All @@ -121,8 +124,7 @@ public class EditEntryActivity extends AegisActivity {
private AutoCompleteTextView _dropdownType;
private AutoCompleteTextView _dropdownAlgo;
private TextInputLayout _dropdownAlgoLayout;
private AutoCompleteTextView _dropdownGroup;
private List<VaultGroupModel> _dropdownGroupList = new ArrayList<>();
private List<UUID> _selectedGroups = new ArrayList<>();

private KropView _kropView;

Expand Down Expand Up @@ -195,6 +197,8 @@ protected void onCreate(Bundle savedInstanceState) {
_saveImageButton = findViewById(R.id.iv_saveImage);
_textName = findViewById(R.id.text_name);
_textIssuer = findViewById(R.id.text_issuer);
_textGroup = findViewById(R.id.text_group);
_textGroupLayout = findViewById(R.id.text_group_layout);
_textPeriodCounter = findViewById(R.id.text_period_counter);
_textPeriodCounterLayout = findViewById(R.id.text_period_counter_layout);
_textDigits = findViewById(R.id.text_digits);
Expand All @@ -210,9 +214,6 @@ protected void onCreate(Bundle savedInstanceState) {
_dropdownAlgoLayout = findViewById(R.id.dropdown_algo_layout);
_dropdownAlgo = findViewById(R.id.dropdown_algo);
DropdownHelper.fillDropdown(this, _dropdownAlgo, R.array.otp_algo_array);
_dropdownGroup = findViewById(R.id.dropdown_group);
updateGroupDropdownList();
DropdownHelper.fillDropdown(this, _dropdownGroup, _dropdownGroupList);

// if this is NOT a manually entered entry, move the "Secret" field from basic to advanced settings
if (!_isNew || !_isManual) {
Expand Down Expand Up @@ -286,10 +287,15 @@ protected void onCreate(Bundle savedInstanceState) {

Set<UUID> groups = _origEntry.getGroups();
if (groups.isEmpty()) {
setGroup(new VaultGroupModel(getString(R.string.no_group)));
_textGroup.setText(getString(R.string.no_group));
} else {
VaultGroup group = _vaultManager.getVault().getGroupByUUID(groups.iterator().next());
setGroup(new VaultGroupModel(group));
String text = groups.stream().map(uuid -> {
VaultGroup group = _vaultManager.getVault().getGroupByUUID(uuid);
return group.getName();
})
.collect(Collectors.joining(", "));
_selectedGroups.addAll(groups);
_textGroup.setText(text);
}

// Update the icon if the issuer or name has changed
Expand All @@ -298,11 +304,11 @@ protected void onCreate(Bundle savedInstanceState) {

// Register listeners to trigger validation
_textIssuer.addTextChangedListener(_validationListener);
_textGroup.addTextChangedListener(_validationListener);
_textName.addTextChangedListener(_validationListener);
_textNote.addTextChangedListener(_validationListener);
_textSecret.addTextChangedListener(_validationListener);
_dropdownType.addTextChangedListener(_validationListener);
_dropdownGroup.addTextChangedListener(_validationListener);
_dropdownAlgo.addTextChangedListener(_validationListener);
_textPeriodCounter.addTextChangedListener(_validationListener);
_textDigits.addTextChangedListener(_validationListener);
Expand Down Expand Up @@ -354,36 +360,107 @@ protected void onCreate(Bundle savedInstanceState) {
startIconSelection();
});

_dropdownGroup.setOnItemClickListener((parent, view, position, id) -> {
VaultGroupModel selectedGroup = _dropdownGroupList.get(position);
if (selectedGroup.isPlaceholder() && Objects.equals(selectedGroup.getName(), getString(R.string.new_group))) {
Dialogs.TextInputListener onAddGroup = text -> {
String groupName = new String(text).trim();
if (!groupName.isEmpty()) {
VaultGroup group = _vaultManager.getVault().findGroupByName(groupName);
if (group == null) {
group = new VaultGroup(groupName);
_vaultManager.getVault().addGroup(group);
}
_textGroup.setShowSoftInputOnFocus(false);
_textGroup.setOnClickListener(v -> showGroupSelectionDialog());
_textGroup.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
showGroupSelectionDialog();
}
});

_textGroupLayout.setOnClickListener(v -> {
showGroupSelectionDialog();
});

_textUsageCount.setText(_prefs.getUsageCount(entryUUID).toString());
setLastUsedTimestamp(_prefs.getLastUsedTimestamp(entryUUID));
}

updateGroupDropdownList();
setGroup(new VaultGroupModel(group));
private void showGroupSelectionDialog() {
BottomSheetDialog dialog = new BottomSheetDialog(this);
View view = getLayoutInflater().inflate(R.layout.dialog_select_groups, null);
dialog.setContentView(view);

ChipGroup chipGroup = view.findViewById(R.id.groupChipGroup);
TextView addGroupInfo = view.findViewById(R.id.addGroupInfo);
LinearLayout addGroup = view.findViewById(R.id.addGroup);
Button clearButton = view.findViewById(R.id.btnClear);
Button saveButton = view.findViewById(R.id.btnSave);

chipGroup.removeAllViews();
addGroupInfo.setVisibility(View.VISIBLE);
addGroup.setVisibility(View.VISIBLE);

for (VaultGroup group : _groups) {
addChipTo(chipGroup, new VaultGroupModel(group), false);
}

addGroup.setOnClickListener(v1 -> {
Dialogs.TextInputListener onAddGroup = text -> {
String groupName = new String(text).trim();
if (!groupName.isEmpty()) {
VaultGroup group = _vaultManager.getVault().findGroupByName(groupName);
if (group == null) {
group = new VaultGroup(groupName);
_vaultManager.getVault().addGroup(group);
}
};

DialogInterface.OnCancelListener onCancel = dialogInterface -> {
VaultGroupModel previous = (VaultGroupModel) _dropdownGroup.getTag();
_dropdownGroup.setText(previous.getName(), false);
};
_selectedGroups.add(group.getUUID());
addChipTo(chipGroup, new VaultGroupModel(group), true);
}
};

Dialogs.showTextInputDialog(EditEntryActivity.this, R.string.set_group, R.string.group_name_hint, onAddGroup, onCancel);
Dialogs.showTextInputDialog(EditEntryActivity.this, R.string.set_group, R.string.group_name_hint, onAddGroup);
});

saveButton.setOnClickListener(v1 -> {
if(getCheckedUUID(chipGroup).isEmpty()) {
_selectedGroups.clear();
_textGroup.setText(getString(R.string.no_group));
InfiniteCoder06 marked this conversation as resolved.
Show resolved Hide resolved
} else {
setGroup(_dropdownGroupList.get(position));
_selectedGroups.clear();
_selectedGroups.addAll(getCheckedUUID(chipGroup));
_textGroup.setText(getCheckedNames(chipGroup));
}
dialog.dismiss();
});

_textUsageCount.setText(_prefs.getUsageCount(entryUUID).toString());
setLastUsedTimestamp(_prefs.getLastUsedTimestamp(entryUUID));
clearButton.setOnClickListener(v1 -> {
chipGroup.clearCheck();
});

Dialogs.showSecureDialog(dialog);
}

private void addChipTo(ChipGroup chipGroup, VaultGroupModel group, Boolean isNew) {
Chip chip = (Chip) getLayoutInflater().inflate(R.layout.chip_group_filter, null, false);
chip.setText(group.getName());
chip.setCheckable(true);

chip.setChecked((!_selectedGroups.isEmpty() && _selectedGroups.contains(group.getUUID())) || isNew);
chip.setCheckedIconVisible(true);
chip.setTag(group);
chipGroup.addView(chip);
}

private static Set<UUID> getCheckedUUID(ChipGroup chipGroup) {
return chipGroup.getCheckedChipIds().stream()
.map(i -> {
Chip chip = chipGroup.findViewById(i);
VaultGroupModel group = (VaultGroupModel) chip.getTag();
return group.getUUID();
})
.collect(Collectors.toSet());
}

private static String getCheckedNames(ChipGroup chipGroup) {
return chipGroup.getCheckedChipIds().stream()
.map(i -> {
Chip chip = chipGroup.findViewById(i);
VaultGroupModel group = (VaultGroupModel) chip.getTag();
return group.getName();
})
.collect(Collectors.joining(", "));
}

private void updateAdvancedFieldStatus(String otpType) {
Expand All @@ -400,11 +477,6 @@ private void updatePinFieldVisibility(String otpType) {
_textPin.setHint(otpType.equals(MotpInfo.ID) ? R.string.motp_pin : R.string.yandex_pin);
}

private void setGroup(VaultGroupModel group) {
_dropdownGroup.setText(group.getName(), false);
_dropdownGroup.setTag(group);
}

private void openAdvancedSettings() {
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator());
Expand All @@ -425,13 +497,6 @@ private void openAdvancedSettings() {
}));
}

private void updateGroupDropdownList() {
_dropdownGroupList.clear();
_dropdownGroupList.add(new VaultGroupModel(getString(R.string.new_group)));
_dropdownGroupList.addAll(_groups.stream().map(VaultGroupModel::new).collect(Collectors.toList()));
_dropdownGroupList.add(new VaultGroupModel(getString(R.string.no_group)));
}

private boolean hasUnsavedChanges(VaultEntry newEntry) {
return _hasChangedIcon || !_origEntry.equals(newEntry);
}
Expand Down Expand Up @@ -731,13 +796,10 @@ private VaultEntry parseEntry() throws ParseException {
entry.setName(_textName.getText().toString());
entry.setNote(_textNote.getText().toString());

VaultGroupModel group = (VaultGroupModel) _dropdownGroup.getTag();
if (group.isPlaceholder()) {
if (_selectedGroups.isEmpty()) {
entry.setGroups(new HashSet<>());
} else {
Set<UUID> groups = new HashSet<>();
groups.add(group.getUUID());
entry.setGroups(groups);
entry.setGroups(new HashSet<>(_selectedGroups));
}

if (_hasChangedIcon) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ private static void showTextInputDialog(Context context, @StringRes int titleId,
showTextInputDialog(context, titleId, 0, hintId, listener, null, isSecret, null);
}

public static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int hintId, TextInputListener listener, @Nullable DialogInterface.OnCancelListener onCancel) {
showTextInputDialog(context, titleId, 0, hintId, listener, onCancel, false, null);
public static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int hintId, TextInputListener listener) {
showTextInputDialog(context, titleId, 0, hintId, listener, null, false, null);
}

public static void showPasswordInputDialog(Context context, TextInputListener listener) {
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_outline_group_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M40,800L40,688Q40,654 57.5,625.5Q75,597 104,582Q166,551 230,535.5Q294,520 360,520Q426,520 490,535.5Q554,551 616,582Q645,597 662.5,625.5Q680,654 680,688L680,800L40,800ZM760,800L760,680Q760,636 735.5,595.5Q711,555 666,526Q717,532 762,546.5Q807,561 846,582Q882,602 901,626.5Q920,651 920,680L920,800L760,800ZM360,480Q294,480 247,433Q200,386 200,320Q200,254 247,207Q294,160 360,160Q426,160 473,207Q520,254 520,320Q520,386 473,433Q426,480 360,480ZM760,320Q760,386 713,433Q666,480 600,480Q589,480 572,477.5Q555,475 544,472Q571,440 585.5,401Q600,362 600,320Q600,278 585.5,239Q571,200 544,168Q558,163 572,161.5Q586,160 600,160Q666,160 713,207Q760,254 760,320ZM120,720L600,720L600,688Q600,677 594.5,668Q589,659 580,654Q526,627 471,613.5Q416,600 360,600Q304,600 249,613.5Q194,627 140,654Q131,659 125.5,668Q120,677 120,688L120,720ZM360,400Q393,400 416.5,376.5Q440,353 440,320Q440,287 416.5,263.5Q393,240 360,240Q327,240 303.5,263.5Q280,287 280,320Q280,353 303.5,376.5Q327,400 360,400ZM360,720L360,720L360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720L360,720ZM360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Z"/>
</vector>
29 changes: 22 additions & 7 deletions app/src/main/res/layout/activity_edit_entry.xml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
android:hint="@string/issuer"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="5dp"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/text_issuer"
Expand All @@ -119,20 +118,36 @@
android:layout_height="wrap_content"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_outline_group_24"
app:tint="?attr/colorOnSurface"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:layout_marginEnd="15dp"
android:layout_gravity="center_vertical"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_group_layout"
android:hint="@string/group"
style="?attr/dropdownStyle">
<AutoCompleteTextView
android:id="@+id/dropdown_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/text_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="false"
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
Expand Down
Loading