Support for Adding Multiple Groups

This commit is contained in:
Praveen Kumar 2024-04-10 10:24:56 +05:30
parent 372bbaa3fb
commit 2e9efa0617
No known key found for this signature in database
GPG key ID: FAAA373053D29923
7 changed files with 198 additions and 67 deletions

View file

@ -7,6 +7,7 @@ import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.longClick; import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.action.ViewActions.pressBack; 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.action.ViewActions.typeText;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA; import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
@ -118,10 +119,11 @@ public class OverallTest extends AegisTest {
onView(withId(R.id.rvKeyProfiles)).perform(RecyclerViewActions.actionOnItemAtPosition(entryPosOffset + 1, longClick())); onView(withId(R.id.rvKeyProfiles)).perform(RecyclerViewActions.actionOnItemAtPosition(entryPosOffset + 1, longClick()));
onView(withId(R.id.action_edit)).perform(click()); onView(withId(R.id.action_edit)).perform(click());
onView(withId(R.id.text_name)).perform(clearText(), typeText("Bob"), closeSoftKeyboard()); onView(withId(R.id.text_name)).perform(clearText(), typeText("Bob"), closeSoftKeyboard());
onView(withId(R.id.dropdown_group)).perform(click()); onView(withId(R.id.text_group)).perform(click());
onView(withText(R.string.new_group)).inRoot(RootMatchers.isPlatformPopup()).perform(click()); onView(withId(R.id.addGroup)).inRoot(RootMatchers.isDialog()).perform(click());
onView(withId(R.id.text_input)).perform(typeText(_groupName), closeSoftKeyboard()); onView(withId(R.id.text_input)).perform(typeText(_groupName), closeSoftKeyboard());
onView(withId(android.R.id.button1)).perform(click()); onView(withId(android.R.id.button1)).perform(click());
onView(withText(R.string.save)).perform(click());
onView(isRoot()).perform(pressBack()); onView(isRoot()).perform(pressBack());
onView(withId(android.R.id.button1)).perform(click()); onView(withId(android.R.id.button1)).perform(click());
@ -188,7 +190,7 @@ public class OverallTest extends AegisTest {
onView(withId(R.id.fab)).perform(click()); onView(withId(R.id.fab)).perform(click());
onView(withId(R.id.fab_enter)).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_name)).perform(typeText(entry.getName()), closeSoftKeyboard());
onView(withId(R.id.text_issuer)).perform(typeText(entry.getIssuer()), closeSoftKeyboard()); onView(withId(R.id.text_issuer)).perform(typeText(entry.getIssuer()), closeSoftKeyboard());
@ -208,7 +210,7 @@ public class OverallTest extends AegisTest {
throw new RuntimeException(String.format("Unexpected entry type: %s", entry.getInfo().getClass().getSimpleName())); 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()); onView(withText(otpType)).inRoot(RootMatchers.isPlatformPopup()).perform(click());
} }

View file

@ -1,6 +1,5 @@
package com.beemdevelopment.aegis.ui; package com.beemdevelopment.aegis.ui;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
@ -16,6 +15,7 @@ import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation; import android.view.animation.AlphaAnimation;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
@ -68,6 +68,8 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.bottomsheet.BottomSheetDialog; 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.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
@ -85,7 +87,6 @@ import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -107,6 +108,8 @@ public class EditEntryActivity extends AegisActivity {
private TextInputEditText _textName; private TextInputEditText _textName;
private TextInputEditText _textIssuer; private TextInputEditText _textIssuer;
private TextInputLayout _textGroupLayout;
private TextInputEditText _textGroup;
private TextInputEditText _textPeriodCounter; private TextInputEditText _textPeriodCounter;
private TextInputLayout _textPeriodCounterLayout; private TextInputLayout _textPeriodCounterLayout;
private TextInputEditText _textDigits; private TextInputEditText _textDigits;
@ -121,8 +124,7 @@ public class EditEntryActivity extends AegisActivity {
private AutoCompleteTextView _dropdownType; private AutoCompleteTextView _dropdownType;
private AutoCompleteTextView _dropdownAlgo; private AutoCompleteTextView _dropdownAlgo;
private TextInputLayout _dropdownAlgoLayout; private TextInputLayout _dropdownAlgoLayout;
private AutoCompleteTextView _dropdownGroup; private List<UUID> _selectedGroups = new ArrayList<>();
private List<VaultGroupModel> _dropdownGroupList = new ArrayList<>();
private KropView _kropView; private KropView _kropView;
@ -195,6 +197,8 @@ public class EditEntryActivity extends AegisActivity {
_saveImageButton = findViewById(R.id.iv_saveImage); _saveImageButton = findViewById(R.id.iv_saveImage);
_textName = findViewById(R.id.text_name); _textName = findViewById(R.id.text_name);
_textIssuer = findViewById(R.id.text_issuer); _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); _textPeriodCounter = findViewById(R.id.text_period_counter);
_textPeriodCounterLayout = findViewById(R.id.text_period_counter_layout); _textPeriodCounterLayout = findViewById(R.id.text_period_counter_layout);
_textDigits = findViewById(R.id.text_digits); _textDigits = findViewById(R.id.text_digits);
@ -210,9 +214,6 @@ public class EditEntryActivity extends AegisActivity {
_dropdownAlgoLayout = findViewById(R.id.dropdown_algo_layout); _dropdownAlgoLayout = findViewById(R.id.dropdown_algo_layout);
_dropdownAlgo = findViewById(R.id.dropdown_algo); _dropdownAlgo = findViewById(R.id.dropdown_algo);
DropdownHelper.fillDropdown(this, _dropdownAlgo, R.array.otp_algo_array); 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 this is NOT a manually entered entry, move the "Secret" field from basic to advanced settings
if (!_isNew || !_isManual) { if (!_isNew || !_isManual) {
@ -286,10 +287,15 @@ public class EditEntryActivity extends AegisActivity {
Set<UUID> groups = _origEntry.getGroups(); Set<UUID> groups = _origEntry.getGroups();
if (groups.isEmpty()) { if (groups.isEmpty()) {
setGroup(new VaultGroupModel(getString(R.string.no_group))); _textGroup.setText(getString(R.string.no_group));
} else { } else {
VaultGroup group = _vaultManager.getVault().getGroupByUUID(groups.iterator().next()); String text = groups.stream().map(uuid -> {
setGroup(new VaultGroupModel(group)); 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 // Update the icon if the issuer or name has changed
@ -298,11 +304,11 @@ public class EditEntryActivity extends AegisActivity {
// Register listeners to trigger validation // Register listeners to trigger validation
_textIssuer.addTextChangedListener(_validationListener); _textIssuer.addTextChangedListener(_validationListener);
_textGroup.addTextChangedListener(_validationListener);
_textName.addTextChangedListener(_validationListener); _textName.addTextChangedListener(_validationListener);
_textNote.addTextChangedListener(_validationListener); _textNote.addTextChangedListener(_validationListener);
_textSecret.addTextChangedListener(_validationListener); _textSecret.addTextChangedListener(_validationListener);
_dropdownType.addTextChangedListener(_validationListener); _dropdownType.addTextChangedListener(_validationListener);
_dropdownGroup.addTextChangedListener(_validationListener);
_dropdownAlgo.addTextChangedListener(_validationListener); _dropdownAlgo.addTextChangedListener(_validationListener);
_textPeriodCounter.addTextChangedListener(_validationListener); _textPeriodCounter.addTextChangedListener(_validationListener);
_textDigits.addTextChangedListener(_validationListener); _textDigits.addTextChangedListener(_validationListener);
@ -354,9 +360,42 @@ public class EditEntryActivity extends AegisActivity {
startIconSelection(); startIconSelection();
}); });
_dropdownGroup.setOnItemClickListener((parent, view, position, id) -> { _textGroup.setShowSoftInputOnFocus(false);
VaultGroupModel selectedGroup = _dropdownGroupList.get(position); _textGroup.setOnClickListener(v -> showGroupSelectionDialog());
if (selectedGroup.isPlaceholder() && Objects.equals(selectedGroup.getName(), getString(R.string.new_group))) { _textGroup.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
showGroupSelectionDialog();
}
});
_textGroupLayout.setOnClickListener(v -> {
showGroupSelectionDialog();
});
_textUsageCount.setText(_prefs.getUsageCount(entryUUID).toString());
setLastUsedTimestamp(_prefs.getLastUsedTimestamp(entryUUID));
}
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 -> { Dialogs.TextInputListener onAddGroup = text -> {
String groupName = new String(text).trim(); String groupName = new String(text).trim();
if (!groupName.isEmpty()) { if (!groupName.isEmpty()) {
@ -366,24 +405,62 @@ public class EditEntryActivity extends AegisActivity {
_vaultManager.getVault().addGroup(group); _vaultManager.getVault().addGroup(group);
} }
updateGroupDropdownList(); _selectedGroups.add(group.getUUID());
setGroup(new VaultGroupModel(group)); addChipTo(chipGroup, new VaultGroupModel(group), true);
} }
}; };
DialogInterface.OnCancelListener onCancel = dialogInterface -> { Dialogs.showTextInputDialog(EditEntryActivity.this, R.string.set_group, R.string.group_name_hint, onAddGroup);
VaultGroupModel previous = (VaultGroupModel) _dropdownGroup.getTag();
_dropdownGroup.setText(previous.getName(), false);
};
Dialogs.showTextInputDialog(EditEntryActivity.this, R.string.set_group, R.string.group_name_hint, onAddGroup, onCancel);
} else {
setGroup(_dropdownGroupList.get(position));
}
}); });
_textUsageCount.setText(_prefs.getUsageCount(entryUUID).toString()); saveButton.setOnClickListener(v1 -> {
setLastUsedTimestamp(_prefs.getLastUsedTimestamp(entryUUID)); if(getCheckedUUID(chipGroup).isEmpty()) {
_selectedGroups.clear();
_textGroup.setText(getString(R.string.no_group));
} else {
_selectedGroups.clear();
_selectedGroups.addAll(getCheckedUUID(chipGroup));
_textGroup.setText(getCheckedNames(chipGroup));
}
dialog.dismiss();
});
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) { private void updateAdvancedFieldStatus(String otpType) {
@ -400,11 +477,6 @@ public class EditEntryActivity extends AegisActivity {
_textPin.setHint(otpType.equals(MotpInfo.ID) ? R.string.motp_pin : R.string.yandex_pin); _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() { private void openAdvancedSettings() {
Animation fadeOut = new AlphaAnimation(1, 0); Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator()); fadeOut.setInterpolator(new AccelerateInterpolator());
@ -425,13 +497,6 @@ public class EditEntryActivity extends AegisActivity {
})); }));
} }
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) { private boolean hasUnsavedChanges(VaultEntry newEntry) {
return _hasChangedIcon || !_origEntry.equals(newEntry); return _hasChangedIcon || !_origEntry.equals(newEntry);
} }
@ -731,13 +796,10 @@ public class EditEntryActivity extends AegisActivity {
entry.setName(_textName.getText().toString()); entry.setName(_textName.getText().toString());
entry.setNote(_textNote.getText().toString()); entry.setNote(_textNote.getText().toString());
VaultGroupModel group = (VaultGroupModel) _dropdownGroup.getTag(); if (_selectedGroups.isEmpty()) {
if (group.isPlaceholder()) {
entry.setGroups(new HashSet<>()); entry.setGroups(new HashSet<>());
} else { } else {
Set<UUID> groups = new HashSet<>(); entry.setGroups(new HashSet<>(_selectedGroups));
groups.add(group.getUUID());
entry.setGroups(groups);
} }
if (_hasChangedIcon) { if (_hasChangedIcon) {

View file

@ -244,8 +244,8 @@ public class Dialogs {
showTextInputDialog(context, titleId, 0, hintId, listener, null, isSecret, null); 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) { public static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int hintId, TextInputListener listener) {
showTextInputDialog(context, titleId, 0, hintId, listener, onCancel, false, null); showTextInputDialog(context, titleId, 0, hintId, listener, null, false, null);
} }
public static void showPasswordInputDialog(Context context, TextInputListener listener) { public static void showPasswordInputDialog(Context context, TextInputListener listener) {

View file

@ -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>

View file

@ -110,7 +110,6 @@
android:hint="@string/issuer" android:hint="@string/issuer"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginEnd="5dp"
android:layout_weight="1"> android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/text_issuer" android:id="@+id/text_issuer"
@ -119,20 +118,36 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="text"/> android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout </LinearLayout>
android:layout_width="0dp"
android:layout_height="wrap_content" <LinearLayout
android:layout_marginStart="5dp"
android:layout_weight="1"
android:hint="@string/group"
style="?attr/dropdownStyle">
<AutoCompleteTextView
android:id="@+id/dropdown_group"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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_marginEnd="15dp"
android:layout_gravity="center_vertical"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_group_layout"
android:hint="@string/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"/> android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -25,6 +25,47 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/addGroupInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:padding="8dp"
android:text="@string/no_group_selection"
app:layout_constraintTop_toBottomOf="@+id/text_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/addGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginTop="15dp"
android:background="?android:attr/selectableItemBackground"
android:gravity="center"
android:orientation="horizontal"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@+id/addGroupInfo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/new_group"
android:src="@drawable/ic_outline_add_24" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/new_group"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/scrollView" android:id="@+id/scrollView"
android:layout_marginTop="15dp" android:layout_marginTop="15dp"
@ -32,7 +73,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:minWidth="150dp" android:minWidth="150dp"
app:layout_constraintHeight_default="wrap" app:layout_constraintHeight_default="wrap"
app:layout_constraintTop_toBottomOf="@+id/text_title" app:layout_constraintTop_toBottomOf="@+id/addGroup"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent">

View file

@ -311,6 +311,7 @@
<string name="no_tokens_can_be_imported">No tokens can be imported as a result</string> <string name="no_tokens_can_be_imported">No tokens can be imported as a result</string>
<string name="unlocking_vault">Unlocking the vault</string> <string name="unlocking_vault">Unlocking the vault</string>
<string name="rename_group">Rename Group</string> <string name="rename_group">Rename Group</string>
<string name="no_group_selection">If an entry is not part of any group, it can be found under \"No group\".</string>
<string name="remove_group">Remove group</string> <string name="remove_group">Remove group</string>
<string name="remove_group_description">Are you sure you want to remove this group? Entries in this group will automatically switch to \'No group\'.</string> <string name="remove_group_description">Are you sure you want to remove this group? Entries in this group will automatically switch to \'No group\'.</string>
<string name="remove_unused_groups">Delete unused groups</string> <string name="remove_unused_groups">Delete unused groups</string>