Fix the last couple of sorting bugs (#77)

This fixes the following bugs:
- Sort category is forgotten after lock/unlock
- The sort mode is not respected for new entries

I got a little carried away while working on this patch and also included the
following other enhancements:
- Simplify the SortCategory, Theme and ViewMode enums
- Simplify usage of string resources
- Don't call notifyDataSetChanged and runLayoutAnimation unnecessarily
This commit is contained in:
Alexander Bakker 2019-05-15 21:29:45 +02:00 committed by Michael Schättgen
parent 0a8dd56306
commit 6d26d1beb0
12 changed files with 264 additions and 309 deletions

View file

@ -43,24 +43,24 @@ public class Preferences {
_prefs.edit().putInt("pref_current_sort_category", category.ordinal()).apply();
}
public int getCurrentSortCategory() {
return _prefs.getInt("pref_current_sort_category", 0);
public SortCategory getCurrentSortCategory() {
return SortCategory.fromInteger(_prefs.getInt("pref_current_sort_category", 0));
}
public int getTapToRevealTime() {
return _prefs.getInt("pref_tap_to_reveal_time", 30);
}
public int getCurrentTheme() {
return _prefs.getInt("pref_current_theme", 0);
public Theme getCurrentTheme() {
return Theme.fromInteger(_prefs.getInt("pref_current_theme", 0));
}
public void setCurrentTheme(Theme theme) {
_prefs.edit().putInt("pref_current_theme", theme.ordinal()).apply();
}
public int getCurrentViewMode() {
return _prefs.getInt("pref_current_view_mode", 0);
public ViewMode getCurrentViewMode() {
return ViewMode.fromInteger(_prefs.getInt("pref_current_view_mode", 0));
}
public void setCurrentViewMode(ViewMode viewMode) {

View file

@ -1,69 +1,61 @@
package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.helpers.comparators.AccountNameComparator;
import com.beemdevelopment.aegis.helpers.comparators.IssuerNameComparator;
import java.util.Collections;
import java.util.Comparator;
public enum SortCategory {
CUSTOM,
ACCOUNT,
ACCOUNTREVERSED,
ACCOUNT_REVERSED,
ISSUER,
ISSUERREVERSED;
ISSUER_REVERSED;
private static SortCategory[] _values;
static {
_values = values();
}
public static SortCategory fromInteger(int x) {
switch (x) {
case 0:
return CUSTOM;
case 1:
return ACCOUNT;
case 2:
return ACCOUNTREVERSED;
case 3:
return ISSUER;
case 4:
return ISSUERREVERSED;
}
return null;
return _values[x];
}
public static Comparator getComparator(SortCategory sortCategory) {
switch (sortCategory) {
public Comparator<DatabaseEntry> getComparator() {
Comparator<DatabaseEntry> comparator = null;
switch (this) {
case ACCOUNT:
case ACCOUNTREVERSED:
return new AccountNameComparator();
comparator = new AccountNameComparator();
break;
case ACCOUNT_REVERSED:
comparator = Collections.reverseOrder(new AccountNameComparator());
break;
case ISSUER:
case ISSUERREVERSED:
return new IssuerNameComparator();
case CUSTOM:
return new IssuerNameComparator();
comparator = new IssuerNameComparator();
break;
case ISSUER_REVERSED:
comparator = Collections.reverseOrder(new IssuerNameComparator());
break;
}
return null;
return comparator;
}
public static boolean isReversed(SortCategory sortCategory) {
switch (sortCategory) {
case ACCOUNTREVERSED:
case ISSUERREVERSED:
return true;
default:
return false;
}
}
public static int getMenuItem(SortCategory sortCategory) {
switch (sortCategory) {
public int getMenuItem() {
switch (this) {
case CUSTOM:
return R.id.menu_sort_custom;
case ACCOUNT:
return R.id.menu_sort_alphabetically_name;
case ACCOUNTREVERSED:
case ACCOUNT_REVERSED:
return R.id.menu_sort_alphabetically_name_reverse;
case ISSUER:
return R.id.menu_sort_alphabetically;
case ISSUERREVERSED:
case ISSUER_REVERSED:
return R.id.menu_sort_alphabetically_reverse;
default:
return R.id.menu_sort_custom;

View file

@ -5,43 +5,13 @@ public enum Theme {
DARK,
AMOLED;
private static Theme[] _values;
static {
_values = values();
}
public static Theme fromInteger(int x) {
switch (x) {
case 0:
return LIGHT;
case 1:
return DARK;
case 2:
return AMOLED;
}
return null;
}
public static int getThemeNameResource(int x) {
switch (x) {
case 0:
return R.string.light_theme_title;
case 1:
return R.string.dark_theme_title;
case 2:
return R.string.amoled_theme_title;
}
return R.string.light_theme_title;
}
public static String[] getThemeNames() {
return new String[]{
"Light theme",
"Dark theme",
"Amoled theme"
};
}
public static int[] getThemeNameResources() {
return new int[] {
R.string.light_theme_title,
R.string.dark_theme_title,
R.string.amoled_theme_title
};
return _values[x];
}
}

View file

@ -1,48 +1,33 @@
package com.beemdevelopment.aegis;
import androidx.annotation.LayoutRes;
public enum ViewMode {
NORMAL,
COMPACT,
SMALL;
private static ViewMode[] _values;
static {
_values = values();
}
public static ViewMode fromInteger(int x) {
switch (x) {
case 0:
return NORMAL;
case 1:
return COMPACT;
case 2:
return SMALL;
return _values[x];
}
@LayoutRes
public int getLayoutId() {
switch (this) {
case NORMAL:
return R.layout.card_entry;
case COMPACT:
return R.layout.card_entry_compact;
case SMALL:
return R.layout.card_entry_small;
default:
return R.layout.card_entry;
}
return null;
}
public static int getViewModeNameResource(int x) {
switch (x) {
case 0:
return R.string.normal_viewmode_title;
case 1:
return R.string.compact_mode_title;
case 2:
return R.string.small_mode_title;
}
return R.string.normal_viewmode_title;
}
public static String[] getViewModeNames() {
return new String[]{
"Normal",
"Compact",
"Small"
};
}
public static int[] getViewModeNameResources() {
return new int[] {
R.string.normal_viewmode_title,
R.string.compact_mode_title,
R.string.small_mode_title
};
}
}

View file

@ -38,7 +38,7 @@ public abstract class AegisActivity extends AppCompatActivity {
}
// set the theme
setPreferredTheme(Theme.fromInteger(getPreferences().getCurrentTheme()));
setPreferredTheme(getPreferences().getCurrentTheme());
// apply a dirty hack to make progress bars work even if animations are disabled
setGlobalAnimationDurationScale();

View file

@ -1,8 +1,6 @@
package com.beemdevelopment.aegis.ui;
import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
@ -16,9 +14,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.Toast;
@ -48,15 +43,12 @@ import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
public class MainActivity extends AegisActivity implements EntryListView.Listener {
// activity request codes
private static final int CODE_SCAN = 0;
@ -98,7 +90,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.setShowAccountName(getPreferences().isAccountNameVisible());
_entryListView.setTapToReveal(getPreferences().isTapToRevealEnabled());
_entryListView.setTapToRevealTime(getPreferences().getTapToRevealTime());
_entryListView.setViewMode(ViewMode.fromInteger(getPreferences().getCurrentViewMode()));
_entryListView.setSortCategory(getPreferences().getCurrentSortCategory(), false);
_entryListView.setViewMode(getPreferences().getCurrentViewMode());
// set up the floating action button
_fabMenu = findViewById(R.id.fab);
@ -203,9 +196,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
boolean showAccountName = getPreferences().isAccountNameVisible();
boolean tapToReveal = getPreferences().isTapToRevealEnabled();
int tapToRevealTime = getPreferences().getTapToRevealTime();
ViewMode viewMode = getPreferences().getCurrentViewMode();
_entryListView.setShowAccountName(showAccountName);
_entryListView.setTapToReveal(tapToReveal);
_entryListView.setTapToRevealTime(tapToRevealTime);
_entryListView.setViewMode(viewMode);
_entryListView.refresh(true);
}
}
@ -317,23 +312,15 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
menu.setGroupCheckable(R.id.action_filter_group, true, true);
}
private void updateSortCategoryMenu() {
SortCategory category = getPreferences().getCurrentSortCategory();
_menu.findItem(category.getMenuItem()).setChecked(true);
}
private void setGroupFilter(String group) {
getSupportActionBar().setSubtitle(group);
_checkedGroup = group;
_entryListView.setGroupFilter(group);
}
private void setSortCategory(SortCategory sortCategory) {
if (sortCategory == SortCategory.CUSTOM) {
_entryListView.clearEntries();
loadEntries();
}
_entryListView.setSortCategory(sortCategory);
}
private void updateSortCategoryMenu(SortCategory sortCategory) {
_menu.findItem(SortCategory.getMenuItem(sortCategory)).setChecked(true);
_entryListView.setGroupFilter(group, true);
}
private void addEntry(DatabaseEntry entry) {
@ -412,7 +399,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
// refresh all codes to prevent showing old ones
_entryListView.refresh(true);
_entryListView.refresh(false);
} else {
loadEntries();
}
@ -481,7 +468,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
updateLockIcon();
if (_loaded) {
updateGroupFilterMenu();
updateSortCategoryMenu(SortCategory.fromInteger(getPreferences().getCurrentSortCategory()));
updateSortCategoryMenu();
}
return true;
}
@ -517,13 +504,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
sortCategory = SortCategory.ISSUER;
break;
case R.id.menu_sort_alphabetically_reverse:
sortCategory = SortCategory.ISSUERREVERSED;
sortCategory = SortCategory.ISSUER_REVERSED;
break;
case R.id.menu_sort_alphabetically_name:
sortCategory = SortCategory.ACCOUNT;
break;
case R.id.menu_sort_alphabetically_name_reverse:
sortCategory = SortCategory.ACCOUNTREVERSED;
sortCategory = SortCategory.ACCOUNT_REVERSED;
break;
case R.id.menu_sort_custom:
default:
@ -531,7 +518,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
break;
}
setSortCategory(sortCategory);
_entryListView.setSortCategory(sortCategory, true);
getPreferences().setCurrentSortCategory(sortCategory);
}
return super.onOptionsItemSelected(item);
@ -570,14 +557,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
loadEntries();
SortCategory currentSortCategory = SortCategory.fromInteger(getPreferences().getCurrentSortCategory());
setSortCategory(currentSortCategory);
}
private void loadEntries() {
// load all entries
List<DatabaseEntry> entries = new ArrayList<DatabaseEntry>(_db.getEntries());
_entryListView.addEntries(entries);
_entryListView.runEntriesAnimation();
_loaded = true;
}

View file

@ -89,18 +89,17 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
// set the result intent in advance
setResult(new Intent());
int currentTheme = app.getPreferences().getCurrentTheme();
int currentTheme = app.getPreferences().getCurrentTheme().ordinal();
Preference darkModePreference = findPreference("pref_dark_mode");
darkModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getString(Theme.getThemeNameResource(currentTheme))));
darkModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.theme_titles)[currentTheme]));
darkModePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
String[] themeNames = getStringsFromResourceIds(Theme.getThemeNameResources());
int checkedTheme = app.getPreferences().getCurrentTheme();
int currentTheme = app.getPreferences().getCurrentTheme().ordinal();
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_theme))
.setSingleChoiceItems(themeNames, checkedTheme, (dialog, which) -> {
.setTitle(R.string.choose_theme)
.setSingleChoiceItems(R.array.theme_titles, currentTheme, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
app.getPreferences().setCurrentTheme(Theme.fromInteger(i));
@ -116,34 +115,22 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
});
darkModePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
_result.putExtra("needsRecreate", true);
getActivity().recreate();
return true;
}
});
int currentViewMode = app.getPreferences().getCurrentViewMode();
int currentViewMode = app.getPreferences().getCurrentViewMode().ordinal();
Preference viewModePreference = findPreference("pref_view_mode");
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getString(ViewMode.getViewModeNameResource(currentViewMode))));
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.view_mode_titles)[currentViewMode]));
viewModePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
String[] viewModes = getStringsFromResourceIds(ViewMode.getViewModeNameResources());
int checkedMode = app.getPreferences().getCurrentViewMode();
int currentViewMode = app.getPreferences().getCurrentViewMode().ordinal();
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_view_mode))
.setSingleChoiceItems(viewModes, checkedMode, (dialog, which) -> {
.setTitle(R.string.choose_view_mode)
.setSingleChoiceItems(R.array.view_mode_titles, currentViewMode, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
app.getPreferences().setCurrentViewMode(ViewMode.fromInteger(i));
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.view_mode_titles)[i]));
_result.putExtra("needsRefresh", true);
dialog.dismiss();
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getString(ViewMode.getViewModeNameResource(i))));
_result.putExtra("needsRecreate", true);
})
.setPositiveButton(android.R.string.ok, null)
.create());
@ -245,14 +232,14 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
Dialogs.showSetPasswordDialog(getActivity(), new EnableEncryptionListener());
} else {
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.disable_encryption))
.setTitle(R.string.disable_encryption)
.setMessage(getString(R.string.disable_encryption_description))
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try {
_db.disableEncryption();
} catch (DatabaseManagerException e) {
Toast.makeText(getActivity(), getString(R.string.disable_encryption_error), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), R.string.disable_encryption_error, Toast.LENGTH_SHORT).show();
return;
}
@ -343,7 +330,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (!PermissionHelper.checkResults(grantResults)) {
Toast.makeText(getActivity(), getString(R.string.permission_denied), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), R.string.permission_denied, Toast.LENGTH_SHORT).show();
return;
}
@ -400,7 +387,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
String[] names = importers.keySet().toArray(new String[0]);
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_application))
.setTitle(R.string.choose_application)
.setSingleChoiceItems(names, 0, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@ -420,7 +407,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
String[] names = importers.keySet().toArray(new String[0]);
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_application))
.setTitle(R.string.choose_application)
.setSingleChoiceItems(names, 0, null)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
@ -449,7 +436,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
Toast.makeText(getActivity(), R.string.app_lookup_error, Toast.LENGTH_SHORT).show();
} catch (IOException | DatabaseImporterException e) {
e.printStackTrace();
Toast.makeText(getActivity(), getString(R.string.reading_file_error), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), R.string.reading_file_error, Toast.LENGTH_SHORT).show();
}
}
@ -517,10 +504,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
DatabaseImporter importer = DatabaseImporter.create(getContext(), _importerType);
importDatabase(importer, reader);
} catch (FileNotFoundException e) {
Toast.makeText(getActivity(), getString(R.string.file_not_found), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), R.string.file_not_found, Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getActivity(), getString(R.string.reading_file_error), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), R.string.reading_file_error, Toast.LENGTH_SHORT).show();
}
}
@ -558,7 +545,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
try {
filename = _db.export(checked.get());
} catch (DatabaseManagerException e) {
Toast.makeText(getActivity(), getString(R.string.exporting_database_error), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), R.string.exporting_database_error, Toast.LENGTH_SHORT).show();
return;
}
@ -578,7 +565,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
});
} else {
builder.setMessage(getString(R.string.export_warning));
builder.setMessage(R.string.export_warning);
}
Dialogs.showSecureDialog(builder.create());
}
@ -637,7 +624,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
try {
_db.save();
} catch (DatabaseManagerException e) {
Toast.makeText(getActivity(), getString(R.string.saving_error), Toast.LENGTH_LONG).show();
Toast.makeText(getActivity(), R.string.saving_error, Toast.LENGTH_LONG).show();
return false;
}
@ -668,15 +655,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
}
private String[] getStringsFromResourceIds(int[] resourceIds) {
String[] strings = new String[resourceIds.length];
for(int i = 0; i < resourceIds.length; i++) {
strings[i] = getString(resourceIds[i]);
}
return strings;
}
private class SetPasswordListener implements Dialogs.SlotListener {
@Override
public void onSlotResult(Slot slot, Cipher cipher) {

View file

@ -97,7 +97,7 @@ public class SelectEntriesActivity extends AegisActivity {
ClipboardManager clipboard = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text/plain", message);
clipboard.setPrimaryClip(clip);
Toast.makeText(this, getString(R.string.errors_copied), Toast.LENGTH_SHORT).show();
Toast.makeText(this, R.string.errors_copied, Toast.LENGTH_SHORT).show();
})
.create());
}

View file

@ -4,7 +4,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.db.DatabaseEntry;
@ -16,6 +15,7 @@ import com.beemdevelopment.aegis.otp.TotpInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
@ -30,8 +30,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
private int _tapToRevealTime;
private String _groupFilter;
private SortCategory _sortCategory;
private ViewMode _viewMode;
private boolean _isPeriodUniform = true;
// keeps track of the viewholders that are currently bound
private List<EntryHolder> _holders;
@ -57,26 +57,43 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
public void addEntry(DatabaseEntry entry) {
_entries.add(entry);
if (!isEntryFiltered(entry)) {
_shownEntries.add(entry);
if (isEntryFiltered(entry)) {
return;
}
int position = getItemCount() - 1;
if (position == 0) {
notifyDataSetChanged();
} else {
notifyItemInserted(position);
boolean added = false;
Comparator<DatabaseEntry> comparator = _sortCategory.getComparator();
if (comparator != null) {
// insert the entry in the correct order
// note: this assumes that _shownEntries has already been sorted
for (int i = 0; i < _shownEntries.size(); i++) {
if (comparator.compare(_shownEntries.get(i), entry) > 0) {
_shownEntries.add(i, entry);
notifyItemInserted(i);
added = true;
break;
}
}
}
if (!added){
_shownEntries.add(entry);
int position = getItemCount() - 1;
if (position == 0) {
notifyDataSetChanged();
} else {
notifyItemInserted(position);
}
}
checkPeriodUniformity();
}
public void addEntries(List<DatabaseEntry> entries) {
_entries.addAll(entries);
for (DatabaseEntry entry : entries) {
if (!isEntryFiltered(entry)) {
_shownEntries.add(entry);
}
}
notifyDataSetChanged();
updateShownEntries();
checkPeriodUniformity();
}
public void removeEntry(DatabaseEntry entry) {
@ -88,6 +105,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
_shownEntries.remove(position);
notifyItemRemoved(position);
}
checkPeriodUniformity();
}
public void clearEntries() {
@ -116,6 +135,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
int position = getItemCount() - 1;
notifyItemInserted(position);
}
checkPeriodUniformity();
}
private boolean isEntryFiltered(DatabaseEntry entry) {
@ -145,36 +166,47 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
}
}
public void setGroupFilter(String group) {
public void setGroupFilter(String group, boolean apply) {
if (_groupFilter != null && _groupFilter.equals(group)) {
return;
}
_groupFilter = group;
if (apply) {
updateShownEntries();
checkPeriodUniformity();
}
}
public void setSortCategory(SortCategory category, boolean apply) {
if (_sortCategory == category) {
return;
}
_sortCategory = category;
if (apply) {
updateShownEntries();
}
}
private void updateShownEntries() {
// clear the list of shown entries first
_shownEntries.clear();
// add entries back that are not filtered out
for (DatabaseEntry entry : _entries) {
if (!isEntryFiltered(entry)) {
_shownEntries.add(entry);
}
}
sortList(_sortCategory);
// sort the remaining list of entries
Comparator<DatabaseEntry> comparator = _sortCategory.getComparator();
if (comparator != null) {
Collections.sort(_shownEntries, comparator);
}
notifyDataSetChanged();
}
public void setSortCategory(SortCategory sortCategory) {
if (_sortCategory != sortCategory && sortCategory != SortCategory.CUSTOM) {
sortList(sortCategory);
notifyDataSetChanged();
}
_sortCategory = sortCategory;
}
private void sortList(SortCategory sortCategory) {
Collections.sort(_shownEntries, SortCategory.getComparator(sortCategory));
if (SortCategory.isReversed(sortCategory)) {
Collections.reverse(_shownEntries);
}
}
public void setViewMode(ViewMode viewMode) {
_viewMode = viewMode;
}
@ -211,16 +243,14 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
}
@Override
public EntryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry, parent, false);
if (_viewMode == ViewMode.NORMAL) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry, parent, false);
} else if (_viewMode == ViewMode.COMPACT) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry_compact, parent, false);
} else if (_viewMode == ViewMode.SMALL) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry_small, parent, false);
}
public int getItemViewType(int position) {
return _viewMode.getLayoutId();
}
@Override
public EntryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(_viewMode.getLayoutId(), parent, false);
return new EntryHolder(view);
}
@ -236,9 +266,6 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
boolean showProgress = !isPeriodUniform() && entry.getInfo() instanceof TotpInfo;
holder.setData(entry, _showAccountName, showProgress, _tapToReveal);
holder.setTapToRevealTime(_tapToRevealTime);
if (showProgress) {
holder.startRefreshLoop();
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
@ -280,6 +307,20 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
_holders.add(holder);
}
private void checkPeriodUniformity() {
boolean uniform = isPeriodUniform();
if (uniform == _isPeriodUniform) {
return;
}
_isPeriodUniform = uniform;
for (EntryHolder holder : _holders) {
holder.setShowProgress(!_isPeriodUniform);
}
_listener.onPeriodUniformityChanged(_isPeriodUniform);
}
public int getUniformPeriod() {
List<TotpInfo> infos = new ArrayList<>();
for (DatabaseEntry entry : _shownEntries) {
@ -318,5 +359,6 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2);
void onEntryDrop(DatabaseEntry entry);
void onEntryChange(DatabaseEntry entry);
void onPeriodUniformityChanged(boolean uniform);
}
}

View file

@ -27,11 +27,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {
private ImageView _profileDrawable;
private DatabaseEntry _entry;
private ImageView _buttonRefresh;
private View _entryDivider;
private View _currentView;
private boolean _hidden;
private int _tapToRevealTime;
@ -42,7 +39,6 @@ public class EntryHolder extends RecyclerView.ViewHolder {
public EntryHolder(final View view) {
super(view);
_currentView = view;
_profileName = view.findViewById(R.id.profile_account_name);
_profileCode = view.findViewById(R.id.profile_code);
@ -80,14 +76,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_hidden = hidden;
// only show the progress bar if there is no uniform period and the entry type is TotpInfo
_progressBar.setVisibility(showProgress ? View.VISIBLE : View.GONE);
if (showProgress) {
_progressBar.setPeriod(((TotpInfo)entry.getInfo()).getPeriod());
if (_entryDivider != null) {
_entryDivider.setVisibility(View.GONE);
}
}
setShowProgress(showProgress);
// only show the button if this entry is of type HotpInfo
_buttonRefresh.setVisibility(entry.getInfo() instanceof HotpInfo ? View.VISIBLE : View.GONE);
@ -125,6 +114,25 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_buttonRefresh.setOnClickListener(listener);
}
public void setShowProgress(boolean showProgress) {
if (_entry.getInfo() instanceof HotpInfo) {
showProgress = false;
}
_progressBar.setVisibility(showProgress ? View.VISIBLE : View.GONE);
if (showProgress) {
_progressBar.setPeriod(((TotpInfo) _entry.getInfo()).getPeriod());
if (_entryDivider != null) {
_entryDivider.setVisibility(View.GONE);
}
startRefreshLoop();
} else {
stopRefreshLoop();
}
}
public void startRefreshLoop() {
_refresher.start();
}
@ -162,7 +170,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
}
private void hideCode() {
_profileCode.setText(_currentView.getContext().getResources().getString(R.string.tap_to_reveal));
_profileCode.setText(R.string.tap_to_reveal);
_hidden = true;
}

View file

@ -28,7 +28,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
private Listener _listener;
private SimpleItemTouchHelperCallback _touchCallback;
private RecyclerView _rvKeyProfiles;
private RecyclerView _recyclerView;
private PeriodProgressBar _progressBar;
private boolean _showProgress;
@ -44,12 +44,11 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_entry_list_view, container, false);
_progressBar = view.findViewById(R.id.progressBar);
// set up the recycler view
_rvKeyProfiles = view.findViewById(R.id.rvKeyProfiles);
_rvKeyProfiles.addOnScrollListener(new RecyclerView.OnScrollListener() {
_recyclerView = view.findViewById(R.id.rvKeyProfiles);
_recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
@ -58,15 +57,15 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
});
LinearLayoutManager mLayoutManager = new LinearLayoutManager(view.getContext());
_rvKeyProfiles.setLayoutManager(mLayoutManager);
_recyclerView.setLayoutManager(mLayoutManager);
_touchCallback = new SimpleItemTouchHelperCallback(_adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(_touchCallback);
touchHelper.attachToRecyclerView(_rvKeyProfiles);
_rvKeyProfiles.setAdapter(_adapter);
touchHelper.attachToRecyclerView(_recyclerView);
_recyclerView.setAdapter(_adapter);
int resId = R.anim.layout_animation_fall_down;
LayoutAnimationController animation = AnimationUtils.loadLayoutAnimation(getContext(), resId);
_rvKeyProfiles.setLayoutAnimation(animation);
_recyclerView.setLayoutAnimation(animation);
_refresher = new UiRefresher(new UiRefresher.Listener() {
@Override
@ -83,23 +82,26 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
return view;
}
public void setGroupFilter(String group) {
_adapter.setGroupFilter(group);
public void setGroupFilter(String group, boolean apply) {
_touchCallback.setIsLongPressDragEnabled(group == null);
checkPeriodUniformity();
_adapter.setGroupFilter(group, apply);
runLayoutAnimation(_rvKeyProfiles);
if (apply) {
runEntriesAnimation();
}
}
public void setSortCategory(SortCategory sortCategory) {
public void setSortCategory(SortCategory sortCategory, boolean apply) {
_touchCallback.setIsLongPressDragEnabled(sortCategory == SortCategory.CUSTOM);
_adapter.setSortCategory(sortCategory, apply);
_adapter.setSortCategory(sortCategory);
runLayoutAnimation(_rvKeyProfiles);
if (apply) {
runEntriesAnimation();
}
}
public void setViewMode(ViewMode viewMode) {
_adapter.setViewMode(viewMode);
public void setViewMode(ViewMode mode) {
_adapter.setViewMode(mode);
}
public void refresh(boolean hard) {
@ -109,33 +111,6 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
_adapter.refresh(hard);
}
private void checkPeriodUniformity() {
boolean uniform = _adapter.isPeriodUniform();
if (uniform == _showProgress) {
return;
}
_showProgress = uniform;
if (_showProgress) {
_progressBar.setVisibility(View.VISIBLE);
_progressBar.setPeriod(_adapter.getUniformPeriod());
startRefreshLoop();
} else {
_progressBar.setVisibility(View.GONE);
stopRefreshLoop();
}
}
private void startRefreshLoop() {
refresh(true);
_refresher.start();
}
private void stopRefreshLoop() {
refresh(true);
_refresher.stop();
}
public void setListener(Listener listener) {
_listener = listener;
}
@ -165,6 +140,19 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
_listener.onEntryChange(entry);
}
@Override
public void onPeriodUniformityChanged(boolean isUniform) {
_showProgress = isUniform;
if (_showProgress) {
_progressBar.setVisibility(View.VISIBLE);
_progressBar.setPeriod(_adapter.getUniformPeriod());
_refresher.start();
} else {
_progressBar.setVisibility(View.GONE);
_refresher.stop();
}
}
public void setShowAccountName(boolean showAccountName) {
_adapter.setShowAccountName(showAccountName);
}
@ -179,37 +167,31 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
public void addEntry(DatabaseEntry entry) {
_adapter.addEntry(entry);
checkPeriodUniformity();
}
public void addEntries(List<DatabaseEntry> entries) {
_adapter.addEntries(entries);
checkPeriodUniformity();
}
public void removeEntry(DatabaseEntry entry) {
_adapter.removeEntry(entry);
checkPeriodUniformity();
}
public void clearEntries() {
_adapter.clearEntries();
checkPeriodUniformity();
}
public void replaceEntry(DatabaseEntry entry) {
_adapter.replaceEntry(entry);
checkPeriodUniformity();
}
private void runLayoutAnimation(final RecyclerView recyclerView) {
final Context context = recyclerView.getContext();
public void runEntriesAnimation() {
final Context context = _recyclerView.getContext();
final LayoutAnimationController controller =
AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down);
recyclerView.setLayoutAnimation(controller);
recyclerView.getAdapter().notifyDataSetChanged();
recyclerView.scheduleLayoutAnimation();
_recyclerView.setLayoutAnimation(controller);
_recyclerView.scheduleLayoutAnimation();
}
public interface Listener {