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(); _prefs.edit().putInt("pref_current_sort_category", category.ordinal()).apply();
} }
public int getCurrentSortCategory() { public SortCategory getCurrentSortCategory() {
return _prefs.getInt("pref_current_sort_category", 0); return SortCategory.fromInteger(_prefs.getInt("pref_current_sort_category", 0));
} }
public int getTapToRevealTime() { public int getTapToRevealTime() {
return _prefs.getInt("pref_tap_to_reveal_time", 30); return _prefs.getInt("pref_tap_to_reveal_time", 30);
} }
public int getCurrentTheme() { public Theme getCurrentTheme() {
return _prefs.getInt("pref_current_theme", 0); return Theme.fromInteger(_prefs.getInt("pref_current_theme", 0));
} }
public void setCurrentTheme(Theme theme) { public void setCurrentTheme(Theme theme) {
_prefs.edit().putInt("pref_current_theme", theme.ordinal()).apply(); _prefs.edit().putInt("pref_current_theme", theme.ordinal()).apply();
} }
public int getCurrentViewMode() { public ViewMode getCurrentViewMode() {
return _prefs.getInt("pref_current_view_mode", 0); return ViewMode.fromInteger(_prefs.getInt("pref_current_view_mode", 0));
} }
public void setCurrentViewMode(ViewMode viewMode) { public void setCurrentViewMode(ViewMode viewMode) {

View file

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

View file

@ -5,43 +5,13 @@ public enum Theme {
DARK, DARK,
AMOLED; AMOLED;
private static Theme[] _values;
static {
_values = values();
}
public static Theme fromInteger(int x) { public static Theme fromInteger(int x) {
switch (x) { return _values[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
};
} }
} }

View file

@ -1,48 +1,33 @@
package com.beemdevelopment.aegis; package com.beemdevelopment.aegis;
import androidx.annotation.LayoutRes;
public enum ViewMode { public enum ViewMode {
NORMAL, NORMAL,
COMPACT, COMPACT,
SMALL; SMALL;
private static ViewMode[] _values;
static {
_values = values();
}
public static ViewMode fromInteger(int x) { public static ViewMode fromInteger(int x) {
switch (x) { return _values[x];
case 0: }
return NORMAL;
case 1: @LayoutRes
return COMPACT; public int getLayoutId() {
case 2: switch (this) {
return SMALL; 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 // 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 // apply a dirty hack to make progress bars work even if animations are disabled
setGlobalAnimationDurationScale(); setGlobalAnimationDurationScale();

View file

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

View file

@ -89,18 +89,17 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
// set the result intent in advance // set the result intent in advance
setResult(new Intent()); setResult(new Intent());
int currentTheme = app.getPreferences().getCurrentTheme(); int currentTheme = app.getPreferences().getCurrentTheme().ordinal();
Preference darkModePreference = findPreference("pref_dark_mode"); 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() { darkModePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
String[] themeNames = getStringsFromResourceIds(Theme.getThemeNameResources()); int currentTheme = app.getPreferences().getCurrentTheme().ordinal();
int checkedTheme = app.getPreferences().getCurrentTheme();
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_theme)) .setTitle(R.string.choose_theme)
.setSingleChoiceItems(themeNames, checkedTheme, (dialog, which) -> { .setSingleChoiceItems(R.array.theme_titles, currentTheme, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
app.getPreferences().setCurrentTheme(Theme.fromInteger(i)); app.getPreferences().setCurrentTheme(Theme.fromInteger(i));
@ -116,34 +115,22 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
} }
}); });
darkModePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { int currentViewMode = app.getPreferences().getCurrentViewMode().ordinal();
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
_result.putExtra("needsRecreate", true);
getActivity().recreate();
return true;
}
});
int currentViewMode = app.getPreferences().getCurrentViewMode();
Preference viewModePreference = findPreference("pref_view_mode"); 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() { viewModePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
String[] viewModes = getStringsFromResourceIds(ViewMode.getViewModeNameResources()); int currentViewMode = app.getPreferences().getCurrentViewMode().ordinal();
int checkedMode = app.getPreferences().getCurrentViewMode();
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_view_mode)) .setTitle(R.string.choose_view_mode)
.setSingleChoiceItems(viewModes, checkedMode, (dialog, which) -> { .setSingleChoiceItems(R.array.view_mode_titles, currentViewMode, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
app.getPreferences().setCurrentViewMode(ViewMode.fromInteger(i)); 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(); 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) .setPositiveButton(android.R.string.ok, null)
.create()); .create());
@ -245,14 +232,14 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
Dialogs.showSetPasswordDialog(getActivity(), new EnableEncryptionListener()); Dialogs.showSetPasswordDialog(getActivity(), new EnableEncryptionListener());
} else { } else {
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.disable_encryption)) .setTitle(R.string.disable_encryption)
.setMessage(getString(R.string.disable_encryption_description)) .setMessage(getString(R.string.disable_encryption_description))
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
try { try {
_db.disableEncryption(); _db.disableEncryption();
} catch (DatabaseManagerException e) { } 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; return;
} }
@ -343,7 +330,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (!PermissionHelper.checkResults(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; return;
} }
@ -400,7 +387,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
String[] names = importers.keySet().toArray(new String[0]); String[] names = importers.keySet().toArray(new String[0]);
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_application)) .setTitle(R.string.choose_application)
.setSingleChoiceItems(names, 0, null) .setSingleChoiceItems(names, 0, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
@ -420,7 +407,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
String[] names = importers.keySet().toArray(new String[0]); String[] names = importers.keySet().toArray(new String[0]);
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_application)) .setTitle(R.string.choose_application)
.setSingleChoiceItems(names, 0, null) .setSingleChoiceItems(names, 0, null)
.setPositiveButton(android.R.string.ok, (dialog, which) -> { .setPositiveButton(android.R.string.ok, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); 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(); Toast.makeText(getActivity(), R.string.app_lookup_error, Toast.LENGTH_SHORT).show();
} catch (IOException | DatabaseImporterException e) { } catch (IOException | DatabaseImporterException e) {
e.printStackTrace(); 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); DatabaseImporter importer = DatabaseImporter.create(getContext(), _importerType);
importDatabase(importer, reader); importDatabase(importer, reader);
} catch (FileNotFoundException e) { } 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) { } catch (IOException e) {
e.printStackTrace(); 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 { try {
filename = _db.export(checked.get()); filename = _db.export(checked.get());
} catch (DatabaseManagerException e) { } 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; return;
} }
@ -578,7 +565,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
} }
}); });
} else { } else {
builder.setMessage(getString(R.string.export_warning)); builder.setMessage(R.string.export_warning);
} }
Dialogs.showSecureDialog(builder.create()); Dialogs.showSecureDialog(builder.create());
} }
@ -637,7 +624,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
try { try {
_db.save(); _db.save();
} catch (DatabaseManagerException e) { } 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; 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 { private class SetPasswordListener implements Dialogs.SlotListener {
@Override @Override
public void onSlotResult(Slot slot, Cipher cipher) { 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); ClipboardManager clipboard = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text/plain", message); ClipData clip = ClipData.newPlainText("text/plain", message);
clipboard.setPrimaryClip(clip); 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()); .create());
} }

View file

@ -4,7 +4,6 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.SortCategory; import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode; import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.db.DatabaseEntry; import com.beemdevelopment.aegis.db.DatabaseEntry;
@ -16,6 +15,7 @@ import com.beemdevelopment.aegis.otp.TotpInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -30,8 +30,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
private int _tapToRevealTime; private int _tapToRevealTime;
private String _groupFilter; private String _groupFilter;
private SortCategory _sortCategory; private SortCategory _sortCategory;
private ViewMode _viewMode; private ViewMode _viewMode;
private boolean _isPeriodUniform = true;
// keeps track of the viewholders that are currently bound // keeps track of the viewholders that are currently bound
private List<EntryHolder> _holders; private List<EntryHolder> _holders;
@ -57,26 +57,43 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
public void addEntry(DatabaseEntry entry) { public void addEntry(DatabaseEntry entry) {
_entries.add(entry); _entries.add(entry);
if (!isEntryFiltered(entry)) { if (isEntryFiltered(entry)) {
_shownEntries.add(entry); return;
} }
int position = getItemCount() - 1; boolean added = false;
if (position == 0) { Comparator<DatabaseEntry> comparator = _sortCategory.getComparator();
notifyDataSetChanged(); if (comparator != null) {
} else { // insert the entry in the correct order
notifyItemInserted(position); // 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) { public void addEntries(List<DatabaseEntry> entries) {
_entries.addAll(entries); _entries.addAll(entries);
for (DatabaseEntry entry : entries) { updateShownEntries();
if (!isEntryFiltered(entry)) { checkPeriodUniformity();
_shownEntries.add(entry);
}
}
notifyDataSetChanged();
} }
public void removeEntry(DatabaseEntry entry) { public void removeEntry(DatabaseEntry entry) {
@ -88,6 +105,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
_shownEntries.remove(position); _shownEntries.remove(position);
notifyItemRemoved(position); notifyItemRemoved(position);
} }
checkPeriodUniformity();
} }
public void clearEntries() { public void clearEntries() {
@ -116,6 +135,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
int position = getItemCount() - 1; int position = getItemCount() - 1;
notifyItemInserted(position); notifyItemInserted(position);
} }
checkPeriodUniformity();
} }
private boolean isEntryFiltered(DatabaseEntry entry) { 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; _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(); _shownEntries.clear();
// add entries back that are not filtered out
for (DatabaseEntry entry : _entries) { for (DatabaseEntry entry : _entries) {
if (!isEntryFiltered(entry)) { if (!isEntryFiltered(entry)) {
_shownEntries.add(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(); 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) { public void setViewMode(ViewMode viewMode) {
_viewMode = viewMode; _viewMode = viewMode;
} }
@ -211,16 +243,14 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
} }
@Override @Override
public EntryHolder onCreateViewHolder(ViewGroup parent, int viewType) { public int getItemViewType(int position) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry, parent, false); return _viewMode.getLayoutId();
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);
}
@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); return new EntryHolder(view);
} }
@ -236,9 +266,6 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
boolean showProgress = !isPeriodUniform() && entry.getInfo() instanceof TotpInfo; boolean showProgress = !isPeriodUniform() && entry.getInfo() instanceof TotpInfo;
holder.setData(entry, _showAccountName, showProgress, _tapToReveal); holder.setData(entry, _showAccountName, showProgress, _tapToReveal);
holder.setTapToRevealTime(_tapToRevealTime); holder.setTapToRevealTime(_tapToRevealTime);
if (showProgress) {
holder.startRefreshLoop();
}
holder.itemView.setOnClickListener(new View.OnClickListener() { holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -280,6 +307,20 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
_holders.add(holder); _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() { public int getUniformPeriod() {
List<TotpInfo> infos = new ArrayList<>(); List<TotpInfo> infos = new ArrayList<>();
for (DatabaseEntry entry : _shownEntries) { for (DatabaseEntry entry : _shownEntries) {
@ -318,5 +359,6 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2); void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2);
void onEntryDrop(DatabaseEntry entry); void onEntryDrop(DatabaseEntry entry);
void onEntryChange(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 ImageView _profileDrawable;
private DatabaseEntry _entry; private DatabaseEntry _entry;
private ImageView _buttonRefresh; private ImageView _buttonRefresh;
private View _entryDivider; private View _entryDivider;
private View _currentView;
private boolean _hidden; private boolean _hidden;
private int _tapToRevealTime; private int _tapToRevealTime;
@ -42,7 +39,6 @@ public class EntryHolder extends RecyclerView.ViewHolder {
public EntryHolder(final View view) { public EntryHolder(final View view) {
super(view); super(view);
_currentView = view;
_profileName = view.findViewById(R.id.profile_account_name); _profileName = view.findViewById(R.id.profile_account_name);
_profileCode = view.findViewById(R.id.profile_code); _profileCode = view.findViewById(R.id.profile_code);
@ -80,14 +76,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_hidden = hidden; _hidden = hidden;
// only show the progress bar if there is no uniform period and the entry type is TotpInfo // only show the progress bar if there is no uniform period and the entry type is TotpInfo
_progressBar.setVisibility(showProgress ? View.VISIBLE : View.GONE); setShowProgress(showProgress);
if (showProgress) {
_progressBar.setPeriod(((TotpInfo)entry.getInfo()).getPeriod());
if (_entryDivider != null) {
_entryDivider.setVisibility(View.GONE);
}
}
// only show the button if this entry is of type HotpInfo // only show the button if this entry is of type HotpInfo
_buttonRefresh.setVisibility(entry.getInfo() instanceof HotpInfo ? View.VISIBLE : View.GONE); _buttonRefresh.setVisibility(entry.getInfo() instanceof HotpInfo ? View.VISIBLE : View.GONE);
@ -125,6 +114,25 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_buttonRefresh.setOnClickListener(listener); _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() { public void startRefreshLoop() {
_refresher.start(); _refresher.start();
} }
@ -162,7 +170,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
} }
private void hideCode() { private void hideCode() {
_profileCode.setText(_currentView.getContext().getResources().getString(R.string.tap_to_reveal)); _profileCode.setText(R.string.tap_to_reveal);
_hidden = true; _hidden = true;
} }

View file

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

View file

@ -17,4 +17,16 @@
<item>SHA256</item> <item>SHA256</item>
<item>SHA512</item> <item>SHA512</item>
</string-array> </string-array>
<string-array name="theme_titles">
<item>@string/light_theme_title</item>
<item>@string/dark_theme_title</item>
<item>@string/amoled_theme_title</item>
</string-array>
<string-array name="view_mode_titles">
<item>@string/normal_viewmode_title</item>
<item>@string/compact_mode_title</item>
<item>@string/small_mode_title</item>
</string-array>
</resources> </resources>