mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-16 06:53:01 +00:00
Merge pull request #1054 from orange-elephant/checkboxes-dropdown
Create checkboxes dropdown component
This commit is contained in:
commit
121c1dada9
6 changed files with 232 additions and 42 deletions
|
@ -0,0 +1,175 @@
|
||||||
|
package com.beemdevelopment.aegis.ui.components;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.Filter;
|
||||||
|
import android.widget.Filterable;
|
||||||
|
|
||||||
|
import androidx.annotation.PluralsRes;
|
||||||
|
import androidx.appcompat.widget.AppCompatAutoCompleteTextView;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class DropdownCheckBoxes extends AppCompatAutoCompleteTextView {
|
||||||
|
private @PluralsRes int _selectedCountPlural = R.plurals.dropdown_checkboxes_default_count;
|
||||||
|
|
||||||
|
private boolean _allowFiltering = false;
|
||||||
|
|
||||||
|
private final List<String> _items = new ArrayList<>();
|
||||||
|
private List<String> _visibleItems = new ArrayList<>();
|
||||||
|
private final Set<String> _checkedItems = new TreeSet<>();
|
||||||
|
|
||||||
|
private CheckboxAdapter _adapter;
|
||||||
|
|
||||||
|
public DropdownCheckBoxes(Context context) {
|
||||||
|
super(context);
|
||||||
|
initialise(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DropdownCheckBoxes(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initialise(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DropdownCheckBoxes(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initialise(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialise(Context context, AttributeSet attrs) {
|
||||||
|
_adapter = new CheckboxAdapter();
|
||||||
|
setAdapter(_adapter);
|
||||||
|
|
||||||
|
if (attrs != null) {
|
||||||
|
TypedArray a = context.obtainStyledAttributes(
|
||||||
|
attrs,
|
||||||
|
R.styleable.DropdownCheckBoxes,
|
||||||
|
0, 0);
|
||||||
|
|
||||||
|
_allowFiltering = a.getBoolean(R.styleable.DropdownCheckBoxes_allow_filtering, false);
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_allowFiltering) {
|
||||||
|
setInputType(0);
|
||||||
|
} else {
|
||||||
|
setInputType(InputType.TYPE_CLASS_TEXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItems(List<String> items, boolean startChecked) {
|
||||||
|
_items.addAll(items);
|
||||||
|
_visibleItems.addAll(items);
|
||||||
|
|
||||||
|
if (startChecked) {
|
||||||
|
_checkedItems.addAll(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCheckedItemsCountText();
|
||||||
|
_adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCheckedItemsCountText() {
|
||||||
|
if (_allowFiltering) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = _checkedItems.size();
|
||||||
|
String countString = getResources().getQuantityString(_selectedCountPlural, count, count);
|
||||||
|
|
||||||
|
setText(countString, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckedItemsCountTextRes(@PluralsRes int resId) {
|
||||||
|
_selectedCountPlural = resId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getCheckedItems() {
|
||||||
|
return _checkedItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CheckboxAdapter extends BaseAdapter implements Filterable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return _visibleItems.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getItem(int i) {
|
||||||
|
return _visibleItems.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int i) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int i, View convertView, ViewGroup viewGroup) {
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(getContext()).inflate(R.layout.dropdown_checkbox, viewGroup, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
String item = _visibleItems.get(i);
|
||||||
|
|
||||||
|
CheckBox checkBox = convertView.findViewById(R.id.checkbox_in_dropdown);
|
||||||
|
checkBox.setText(item);
|
||||||
|
checkBox.setChecked(_checkedItems.contains(item));
|
||||||
|
|
||||||
|
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||||
|
String label = buttonView.getText().toString();
|
||||||
|
|
||||||
|
if (isChecked) {
|
||||||
|
_checkedItems.add(label);
|
||||||
|
} else {
|
||||||
|
_checkedItems.remove(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCheckedItemsCountText();
|
||||||
|
});
|
||||||
|
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Filter getFilter() {
|
||||||
|
return new Filter() {
|
||||||
|
@Override
|
||||||
|
protected FilterResults performFiltering(CharSequence query) {
|
||||||
|
FilterResults results = new FilterResults();
|
||||||
|
results.values = (query == null || query.toString().isEmpty())
|
||||||
|
? _items
|
||||||
|
: _items.stream().filter(str -> {
|
||||||
|
String q = query.toString().toLowerCase();
|
||||||
|
String strLower = str.toLowerCase();
|
||||||
|
|
||||||
|
return strLower.contains(q);
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
|
||||||
|
_visibleItems = (List<String>) filterResults.values;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,6 @@ import android.view.View;
|
||||||
import android.widget.AutoCompleteTextView;
|
import android.widget.AutoCompleteTextView;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
@ -30,6 +29,7 @@ import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||||
import com.beemdevelopment.aegis.ui.ImportEntriesActivity;
|
import com.beemdevelopment.aegis.ui.ImportEntriesActivity;
|
||||||
import com.beemdevelopment.aegis.ui.TransferEntriesActivity;
|
import com.beemdevelopment.aegis.ui.TransferEntriesActivity;
|
||||||
|
import com.beemdevelopment.aegis.ui.components.DropdownCheckBoxes;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.ExportTask;
|
import com.beemdevelopment.aegis.ui.tasks.ExportTask;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
|
import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
|
||||||
|
@ -41,6 +41,7 @@ import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -162,8 +163,8 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
CheckBox checkBoxEncrypt = view.findViewById(R.id.checkbox_export_encrypt);
|
CheckBox checkBoxEncrypt = view.findViewById(R.id.checkbox_export_encrypt);
|
||||||
CheckBox checkBoxAccept = view.findViewById(R.id.checkbox_accept);
|
CheckBox checkBoxAccept = view.findViewById(R.id.checkbox_accept);
|
||||||
CheckBox checkBoxExportAllGroups = view.findViewById(R.id.export_selected_groups);
|
CheckBox checkBoxExportAllGroups = view.findViewById(R.id.export_selected_groups);
|
||||||
LinearLayout groupsSelection = view.findViewById(R.id.select_groups);
|
TextInputLayout groupsSelectionLayout = view.findViewById(R.id.group_selection_layout);
|
||||||
TextView groupsSelectionDescriptor = view.findViewById(R.id.select_groups_hint);
|
DropdownCheckBoxes groupsSelection = view.findViewById(R.id.group_selection_dropdown);
|
||||||
TextView passwordInfoText = view.findViewById(R.id.text_separate_password);
|
TextView passwordInfoText = view.findViewById(R.id.text_separate_password);
|
||||||
passwordInfoText.setVisibility(checkBoxEncrypt.isChecked() && isBackupPasswordSet ? View.VISIBLE : View.GONE);
|
passwordInfoText.setVisibility(checkBoxEncrypt.isChecked() && isBackupPasswordSet ? View.VISIBLE : View.GONE);
|
||||||
AutoCompleteTextView dropdown = view.findViewById(R.id.dropdown_export_format);
|
AutoCompleteTextView dropdown = view.findViewById(R.id.dropdown_export_format);
|
||||||
|
@ -179,12 +180,13 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
TreeSet<String> groups = _vaultManager.getVault().getGroups();
|
TreeSet<String> groups = _vaultManager.getVault().getGroups();
|
||||||
if (groups.size() > 0) {
|
if (groups.size() > 0) {
|
||||||
checkBoxExportAllGroups.setVisibility(View.VISIBLE);
|
checkBoxExportAllGroups.setVisibility(View.VISIBLE);
|
||||||
for (String group: groups) {
|
|
||||||
CheckBox box = new CheckBox(requireContext());
|
ArrayList<String> groupsArray = new ArrayList<>();
|
||||||
box.setText(group);
|
groupsArray.add(getString(R.string.no_group));
|
||||||
box.setChecked(false);
|
groupsArray.addAll(groups);
|
||||||
groupsSelection.addView(box);
|
|
||||||
}
|
groupsSelection.setCheckedItemsCountTextRes(R.plurals.export_groups_selected_count);
|
||||||
|
groupsSelection.addItems(groupsArray, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(requireContext())
|
AlertDialog dialog = new AlertDialog.Builder(requireContext())
|
||||||
|
@ -215,8 +217,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
checkBoxExportAllGroups.setOnCheckedChangeListener((button, isChecked) -> {
|
checkBoxExportAllGroups.setOnCheckedChangeListener((button, isChecked) -> {
|
||||||
int visibility = isChecked ? View.GONE : View.VISIBLE;
|
int visibility = isChecked ? View.GONE : View.VISIBLE;
|
||||||
groupsSelection.setVisibility(visibility);
|
groupsSelectionLayout.setVisibility(visibility);
|
||||||
groupsSelectionDescriptor.setVisibility(visibility);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
btnPos.setOnClickListener(v -> {
|
btnPos.setOnClickListener(v -> {
|
||||||
|
@ -303,14 +304,13 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
Dialogs.showSecureDialog(dialog);
|
Dialogs.showSecureDialog(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vault.EntryFilter getVaultEntryFilter(LinearLayout view) {
|
private Vault.EntryFilter getVaultEntryFilter(DropdownCheckBoxes dropdownCheckBoxes) {
|
||||||
Set<String> groups = new HashSet<>();
|
Set<String> groups = new HashSet<>();
|
||||||
for (int i=0; i<view.getChildCount(); i++) {
|
for (String group: dropdownCheckBoxes.getCheckedItems()) {
|
||||||
CheckBox group = (CheckBox) view.getChildAt(i);
|
if (group.equals(getString(R.string.no_group))) {
|
||||||
if (group.isChecked() && group.getText().toString().equals(getString(R.string.no_group))) {
|
|
||||||
groups.add(null);
|
groups.add(null);
|
||||||
} else if (group.isChecked()) {
|
} else {
|
||||||
groups.add(group.getText().toString());
|
groups.add(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
android:paddingTop="10dp">
|
android:paddingTop="10dp">
|
||||||
|
@ -76,31 +76,21 @@
|
||||||
android:checked="true"
|
android:checked="true"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/select_groups_hint"
|
android:id="@+id/group_selection_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="30dp"
|
android:layout_marginStart="25dp"
|
||||||
android:text="@string/export_choose_groups"
|
android:layout_marginEnd="25dp"
|
||||||
android:visibility="gone"/>
|
android:layout_marginTop="15dp"
|
||||||
|
android:hint="@string/export_choose_groups"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="?attr/dropdownStyle">
|
||||||
|
|
||||||
<ScrollView
|
<com.beemdevelopment.aegis.ui.components.DropdownCheckBoxes
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/group_selection_dropdown"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="20dp">
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/select_groups"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
app:allow_filtering="false" />
|
||||||
android:paddingHorizontal="20dp"
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/no_group"
|
|
||||||
android:checked="false"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
12
app/src/main/res/layout/dropdown_checkbox.xml
Normal file
12
app/src/main/res/layout/dropdown_checkbox.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkbox_in_dropdown"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:buttonTint="@color/colorSecondary"/>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
@ -19,4 +19,8 @@
|
||||||
<attr name="dot_color" format="color" />
|
<attr name="dot_color" format="color" />
|
||||||
<attr name="dot_color_selected" format="color" />
|
<attr name="dot_color_selected" format="color" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
<declare-styleable name="DropdownCheckBoxes">
|
||||||
|
<attr name="allow_filtering" format="boolean" />
|
||||||
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -108,7 +108,11 @@
|
||||||
<string name="export_format_html">Web page (.HTML)</string>
|
<string name="export_format_html">Web page (.HTML)</string>
|
||||||
<string name="export_format_hint">Export format</string>
|
<string name="export_format_hint">Export format</string>
|
||||||
<string name="export_all_groups">Export all groups</string>
|
<string name="export_all_groups">Export all groups</string>
|
||||||
<string name="export_choose_groups">Select which groups to export:</string>
|
<string name="export_choose_groups">Select which groups to export</string>
|
||||||
|
<plurals name="export_groups_selected_count">
|
||||||
|
<item quantity="one">%d group selected</item>
|
||||||
|
<item quantity="other">%d groups selected</item>
|
||||||
|
</plurals>
|
||||||
<string name="export_no_groups_selected">No groups selected to export</string>
|
<string name="export_no_groups_selected">No groups selected to export</string>
|
||||||
<string name="export_html_title" context="The title of an HTML export document">Aegis Authenticator Export</string>
|
<string name="export_html_title" context="The title of an HTML export document">Aegis Authenticator Export</string>
|
||||||
|
|
||||||
|
@ -500,4 +504,9 @@
|
||||||
<item quantity="one">%d year ago</item>
|
<item quantity="one">%d year ago</item>
|
||||||
<item quantity="other">%d years ago</item>
|
<item quantity="other">%d years ago</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="dropdown_checkboxes_default_count">
|
||||||
|
<item quantity="one">%d item selected</item>
|
||||||
|
<item quantity="other">%d items selected</item>
|
||||||
|
</plurals>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue