Add ability to sort based on last used timestamp

This commit is contained in:
Michael Schättgen 2024-03-16 00:59:43 +01:00
parent dfd720b406
commit 9bae4d6bbc
11 changed files with 130 additions and 3 deletions

View file

@ -235,10 +235,51 @@ public class Preferences {
setUsageCount(usageCounts); setUsageCount(usageCounts);
} }
public long getLastUsedTimestamp(UUID uuid) {
Map<UUID, Long> timestamps = getLastUsedTimestamps();
if (timestamps != null && timestamps.size() > 0){
Long timestamp = timestamps.get(uuid);
return timestamp != null ? timestamp : 0;
}
return 0;
}
public void clearUsageCount() { public void clearUsageCount() {
_prefs.edit().remove("pref_usage_count").apply(); _prefs.edit().remove("pref_usage_count").apply();
} }
public Map<UUID, Long> getLastUsedTimestamps() {
Map<UUID, Long> lastUsedTimestamps = new HashMap<>();
String lastUsedTimestamp = _prefs.getString("pref_last_used_timestamps", "");
try {
JSONArray arr = new JSONArray(lastUsedTimestamp);
for (int i = 0; i < arr.length(); i++) {
JSONObject json = arr.getJSONObject(i);
lastUsedTimestamps.put(UUID.fromString(json.getString("uuid")), json.getLong("timestamp"));
}
} catch (JSONException ignored) {
}
return lastUsedTimestamps;
}
public void setLastUsedTimestamps(Map<UUID, Long> lastUsedTimestamps) {
JSONArray lastUsedTimestampJson = new JSONArray();
for (Map.Entry<UUID, Long> entry : lastUsedTimestamps.entrySet()) {
JSONObject entryJson = new JSONObject();
try {
entryJson.put("uuid", entry.getKey());
entryJson.put("timestamp", entry.getValue());
lastUsedTimestampJson.put(entryJson);
} catch (JSONException e) {
e.printStackTrace();
}
}
_prefs.edit().putString("pref_last_used_timestamps", lastUsedTimestampJson.toString()).apply();
}
public Map<UUID, Integer> getUsageCounts() { public Map<UUID, Integer> getUsageCounts() {
Map<UUID, Integer> usageCounts = new HashMap<>(); Map<UUID, Integer> usageCounts = new HashMap<>();
String usageCount = _prefs.getString("pref_usage_count", ""); String usageCount = _prefs.getString("pref_usage_count", "");

View file

@ -1,5 +1,6 @@
package com.beemdevelopment.aegis; package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.helpers.comparators.LastUsedComparator;
import com.beemdevelopment.aegis.helpers.comparators.UsageCountComparator; import com.beemdevelopment.aegis.helpers.comparators.UsageCountComparator;
import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.helpers.comparators.AccountNameComparator; import com.beemdevelopment.aegis.helpers.comparators.AccountNameComparator;
@ -14,7 +15,8 @@ public enum SortCategory {
ACCOUNT_REVERSED, ACCOUNT_REVERSED,
ISSUER, ISSUER,
ISSUER_REVERSED, ISSUER_REVERSED,
USAGE_COUNT; USAGE_COUNT,
LAST_USED;
private static SortCategory[] _values; private static SortCategory[] _values;
@ -45,6 +47,8 @@ public enum SortCategory {
case USAGE_COUNT: case USAGE_COUNT:
comparator = Collections.reverseOrder(new UsageCountComparator()); comparator = Collections.reverseOrder(new UsageCountComparator());
break; break;
case LAST_USED:
comparator = Collections.reverseOrder(new LastUsedComparator());
} }
return comparator; return comparator;
@ -64,6 +68,8 @@ public enum SortCategory {
return R.id.menu_sort_alphabetically_reverse; return R.id.menu_sort_alphabetically_reverse;
case USAGE_COUNT: case USAGE_COUNT:
return R.id.menu_sort_usage_count; return R.id.menu_sort_usage_count;
case LAST_USED:
return R.id.menu_sort_last_used;
default: default:
return R.id.menu_sort_custom; return R.id.menu_sort_custom;
} }

View file

@ -0,0 +1,12 @@
package com.beemdevelopment.aegis.helpers.comparators;
import com.beemdevelopment.aegis.vault.VaultEntry;
import java.util.Comparator;
public class LastUsedComparator implements Comparator<VaultEntry> {
@Override
public int compare(VaultEntry a, VaultEntry b) {
return Long.compare(a.getLastUsedTimestamp(), b.getLastUsedTimestamp());
}
}

View file

@ -19,6 +19,7 @@ import android.widget.AutoCompleteTextView;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
@ -75,10 +76,12 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -113,6 +116,7 @@ public class EditEntryActivity extends AegisActivity {
private LinearLayout _textPinLayout; private LinearLayout _textPinLayout;
private TextInputEditText _textUsageCount; private TextInputEditText _textUsageCount;
private TextInputEditText _textNote; private TextInputEditText _textNote;
private TextView _textLastUsed;
private AutoCompleteTextView _dropdownType; private AutoCompleteTextView _dropdownType;
private AutoCompleteTextView _dropdownAlgo; private AutoCompleteTextView _dropdownAlgo;
@ -200,6 +204,7 @@ public class EditEntryActivity extends AegisActivity {
_textPinLayout = findViewById(R.id.layout_pin); _textPinLayout = findViewById(R.id.layout_pin);
_textUsageCount = findViewById(R.id.text_usage_count); _textUsageCount = findViewById(R.id.text_usage_count);
_textNote = findViewById(R.id.text_note); _textNote = findViewById(R.id.text_note);
_textLastUsed = findViewById(R.id.text_last_used);
_dropdownType = findViewById(R.id.dropdown_type); _dropdownType = findViewById(R.id.dropdown_type);
DropdownHelper.fillDropdown(this, _dropdownType, R.array.otp_types_array); DropdownHelper.fillDropdown(this, _dropdownType, R.array.otp_types_array);
_dropdownAlgoLayout = findViewById(R.id.dropdown_algo_layout); _dropdownAlgoLayout = findViewById(R.id.dropdown_algo_layout);
@ -378,6 +383,7 @@ public class EditEntryActivity extends AegisActivity {
}); });
_textUsageCount.setText(_prefs.getUsageCount(entryUUID).toString()); _textUsageCount.setText(_prefs.getUsageCount(entryUUID).toString());
setLastUsedTimestamp(_prefs.getLastUsedTimestamp(entryUUID));
} }
private void updateAdvancedFieldStatus(String otpType) { private void updateAdvancedFieldStatus(String otpType) {
@ -608,6 +614,16 @@ public class EditEntryActivity extends AegisActivity {
saveAndFinish(entry, false); saveAndFinish(entry, false);
} }
private void setLastUsedTimestamp(long timestamp) {
String readableDate = getString(R.string.last_used_never);
if (timestamp != 0) {
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.getDefault());
readableDate = dateFormat.format(new Date(timestamp));
}
_textLastUsed.setText(String.format("%s: %s", getString(R.string.last_used), readableDate));
}
private void deleteAndFinish(VaultEntry entry) { private void deleteAndFinish(VaultEntry entry) {
_vaultManager.getVault().removeEntry(entry); _vaultManager.getVault().removeEntry(entry);
saveAndFinish(entry, true); saveAndFinish(entry, true);

View file

@ -241,6 +241,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_prefs.setUsageCount(usageMap); _prefs.setUsageCount(usageMap);
} }
Map<UUID, Long> lastUsedMap = _entryListView.getLastUsedTimestamps();
if (lastUsedMap != null) {
_prefs.setLastUsedTimestamps(lastUsedMap);
}
super.onPause(); super.onPause();
} }
@ -696,6 +701,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
// update the usage counts in case they are edited outside of the EntryListView // update the usage counts in case they are edited outside of the EntryListView
_entryListView.setUsageCounts(_prefs.getUsageCounts()); _entryListView.setUsageCounts(_prefs.getUsageCounts());
_entryListView.setLastUsedTimestamps(_prefs.getLastUsedTimestamps());
// refresh all codes to prevent showing old ones // refresh all codes to prevent showing old ones
_entryListView.refresh(false); _entryListView.refresh(false);
} else { } else {
@ -825,6 +832,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
sortCategory = SortCategory.ACCOUNT_REVERSED; sortCategory = SortCategory.ACCOUNT_REVERSED;
} else if (subItemId == R.id.menu_sort_usage_count) { } else if (subItemId == R.id.menu_sort_usage_count) {
sortCategory = SortCategory.USAGE_COUNT; sortCategory = SortCategory.USAGE_COUNT;
} else if (subItemId == R.id.menu_sort_last_used) {
sortCategory = SortCategory.LAST_USED;
} else { } else {
sortCategory = SortCategory.CUSTOM; sortCategory = SortCategory.CUSTOM;
} }
@ -847,6 +856,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private void loadEntries() { private void loadEntries() {
if (!_loaded) { if (!_loaded) {
_entryListView.setUsageCounts(_prefs.getUsageCounts()); _entryListView.setUsageCounts(_prefs.getUsageCounts());
_entryListView.setLastUsedTimestamps(_prefs.getLastUsedTimestamps());
_entryListView.addEntries(_vaultManager.getVault().getEntries()); _entryListView.addEntries(_vaultManager.getVault().getEntries());
_entryListView.runEntriesAnimation(); _entryListView.runEntriesAnimation();
_loaded = true; _loaded = true;

View file

@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -51,6 +52,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
private List<VaultEntry> _shownEntries; private List<VaultEntry> _shownEntries;
private List<VaultEntry> _selectedEntries; private List<VaultEntry> _selectedEntries;
private Map<UUID, Integer> _usageCounts; private Map<UUID, Integer> _usageCounts;
private Map<UUID, Long> _lastUsedTimestamps;
private VaultEntry _focusedEntry; private VaultEntry _focusedEntry;
private VaultEntry _clickedEntry; private VaultEntry _clickedEntry;
private Preferences.CodeGrouping _codeGroupSize; private Preferences.CodeGrouping _codeGroupSize;
@ -190,6 +192,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public void addEntries(Collection<VaultEntry> entries) { public void addEntries(Collection<VaultEntry> entries) {
for (VaultEntry entry: entries) { for (VaultEntry entry: entries) {
entry.setUsageCount(_usageCounts.containsKey(entry.getUUID()) ? _usageCounts.get(entry.getUUID()) : 0); entry.setUsageCount(_usageCounts.containsKey(entry.getUUID()) ? _usageCounts.get(entry.getUUID()) : 0);
entry.setLastUsedTimestamp(_lastUsedTimestamps.containsKey(entry.getUUID()) ? _lastUsedTimestamps.get(entry.getUUID()) : 0);
} }
_entries.addAll(entries); _entries.addAll(entries);
@ -407,6 +410,10 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public Map<UUID, Integer> getUsageCounts() { return _usageCounts; } public Map<UUID, Integer> getUsageCounts() { return _usageCounts; }
public void setLastUsedTimestamps(Map<UUID, Long> lastUsedTimestamps) { _lastUsedTimestamps = lastUsedTimestamps; }
public Map<UUID, Long> getLastUsedTimestamps() { return _lastUsedTimestamps; }
public int getShownFavoritesCount() { public int getShownFavoritesCount() {
return (int) _shownEntries.stream().filter(VaultEntry::isFavorite).count(); return (int) _shownEntries.stream().filter(VaultEntry::isFavorite).count();
} }
@ -805,6 +812,8 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
int usageCount = _usageCounts.get(entry.getUUID()); int usageCount = _usageCounts.get(entry.getUUID());
_usageCounts.put(entry.getUUID(), ++usageCount); _usageCounts.put(entry.getUUID(), ++usageCount);
} }
_lastUsedTimestamps.put(entry.getUUID(), new Date().getTime());
} }
public boolean isDragAndDropAllowed() { public boolean isDragAndDropAllowed() {

View file

@ -223,6 +223,14 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
return _adapter.getUsageCounts(); return _adapter.getUsageCounts();
} }
public void setLastUsedTimestamps(Map<UUID, Long> lastUsedTimestamps) {
_adapter.setLastUsedTimestamps(lastUsedTimestamps);
}
public Map<UUID, Long> getLastUsedTimestamps() {
return _adapter.getLastUsedTimestamps();
}
public void setSearchFilter(String search) { public void setSearchFilter(String search) {
_adapter.setSearchFilter(search); _adapter.setSearchFilter(search);
_touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed()); _touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed());

View file

@ -23,6 +23,7 @@ public class VaultEntry extends UUIDMap.Value {
private VaultEntryIcon _icon; private VaultEntryIcon _icon;
private boolean _isFavorite; private boolean _isFavorite;
private int _usageCount; private int _usageCount;
private long _lastUsedTimestamp;
private String _note = ""; private String _note = "";
private String _oldGroup; private String _oldGroup;
private Set<UUID> _groups = new TreeSet<>(); private Set<UUID> _groups = new TreeSet<>();
@ -135,6 +136,10 @@ public class VaultEntry extends UUIDMap.Value {
return _usageCount; return _usageCount;
} }
public long getLastUsedTimestamp() {
return _lastUsedTimestamp;
}
public String getNote() { public String getNote() {
return _note; return _note;
} }
@ -143,8 +148,6 @@ public class VaultEntry extends UUIDMap.Value {
return _isFavorite; return _isFavorite;
} }
;
public void setName(String name) { public void setName(String name) {
_name = name; _name = name;
} }
@ -187,6 +190,8 @@ public class VaultEntry extends UUIDMap.Value {
_usageCount = usageCount; _usageCount = usageCount;
} }
public void setLastUsedTimestamp(long lastUsedTimestamp) { _lastUsedTimestamp = lastUsedTimestamp; }
public void setNote(String note) { public void setNote(String note) {
_note = note; _note = note;
} }

View file

@ -376,8 +376,22 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/text_last_used"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:layout_centerInParent="true"
android:layout_gravity="bottom|center"
android:textSize="14sp" />
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -40,6 +40,9 @@
<item <item
android:id="@+id/menu_sort_usage_count" android:id="@+id/menu_sort_usage_count"
android:title="@string/sort_usage_count"/> android:title="@string/sort_usage_count"/>
<item
android:id="@+id/menu_sort_last_used"
android:title="@string/sort_last_used"/>
</group> </group>
</menu> </menu>
</item> </item>

View file

@ -303,7 +303,10 @@
<string name="sort_alphabetically_name">Account (A to Z)</string> <string name="sort_alphabetically_name">Account (A to Z)</string>
<string name="sort_alphabetically_name_reverse">Account (Z to A)</string> <string name="sort_alphabetically_name_reverse">Account (Z to A)</string>
<string name="sort_usage_count">Usage count</string> <string name="sort_usage_count">Usage count</string>
<string name="sort_last_used">Last used</string>
<string name="sort_custom">Custom</string> <string name="sort_custom">Custom</string>
<string name="last_used">Last used</string>
<string name="last_used_never">never</string>
<string name="new_group">New group…</string> <string name="new_group">New group…</string>
<string name="group">Group</string> <string name="group">Group</string>
<string name="group_name_hint">Group name</string> <string name="group_name_hint">Group name</string>