diff --git a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java index 10f98e73d5..5b4041ab57 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java +++ b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java @@ -153,6 +153,10 @@ public boolean isIconVisible() { return _prefs.getBoolean("pref_show_icons", true); } + public boolean getShowNextCode() { + return _prefs.getBoolean("pref_show_next_code", false); + } + public boolean getShowExpirationState() { return _prefs.getBoolean("pref_expiration_state", true); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/TotpInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/TotpInfo.java index 5e7320f461..bd43698df5 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/otp/TotpInfo.java +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/TotpInfo.java @@ -37,7 +37,9 @@ public String getOtp() throws OtpInfoException { } } - public String getOtp(long time) { + public String getOtp(long time) throws OtpInfoException { + checkSecret(); + try { OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod(), time); return otp.toString(); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java index d9c03a59ba..88d2a53849 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -208,6 +208,7 @@ protected void onCreate(Bundle savedInstanceState) { _entryListView.setAccountNamePosition(_prefs.getAccountNamePosition()); _entryListView.setShowIcon(_prefs.isIconVisible()); _entryListView.setShowExpirationState(_prefs.getShowExpirationState()); + _entryListView.setShowNextCode(_prefs.getShowNextCode()); _entryListView.setOnlyShowNecessaryAccountNames(_prefs.onlyShowNecessaryAccountNames()); _entryListView.setHighlightEntry(_prefs.isEntryHighlightEnabled()); _entryListView.setPauseFocused(_prefs.isPauseFocusedEnabled()); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java index 696c7f4000..cea77364d8 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java @@ -60,6 +60,7 @@ public class EntryAdapter extends RecyclerView.Adapter private Preferences.CodeGrouping _codeGroupSize; private AccountNamePosition _accountNamePosition; private boolean _showIcon; + private boolean _showNextCode; private boolean _showExpirationState; private boolean _onlyShowNecessaryAccountNames; private boolean _highlightEntry; @@ -116,6 +117,10 @@ public void setShowIcon(boolean showIcon) { _showIcon = showIcon; } + public void setShowNextCode(boolean showNextCode) { + _showNextCode = showNextCode; + } + public void setShowExpirationState(boolean showExpirationState) { _showExpirationState = showExpirationState; } @@ -544,7 +549,7 @@ public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) } AccountNamePosition accountNamePosition = showAccountName ? _accountNamePosition : AccountNamePosition.HIDDEN; - entryHolder.setData(entry, _codeGroupSize, _viewMode, accountNamePosition, _showIcon, showProgress, hidden, paused, dimmed, _showExpirationState); + entryHolder.setData(entry, _codeGroupSize, _viewMode, accountNamePosition, _showIcon, showProgress, hidden, paused, dimmed, _showExpirationState, _showNextCode); entryHolder.setFocused(_selectedEntries.contains(entry)); entryHolder.setShowDragHandle(isEntryDraggable(entry)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java index 1f44f423d9..1e5b9cf0dd 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java @@ -50,6 +50,7 @@ public class EntryHolder extends RecyclerView.ViewHolder { private View _favoriteIndicator; private TextView _profileName; private TextView _profileCode; + private TextView _nextProfileCode; private TextView _profileIssuer; private TextView _profileCopied; private ImageView _profileDrawable; @@ -74,6 +75,7 @@ public class EntryHolder extends RecyclerView.ViewHolder { private UiRefresher _refresher; private Handler _animationHandler; private AnimatorSet _expirationAnimSet; + private boolean _showNextCode; private boolean _showExpirationState; private Animation _scaleIn; @@ -85,6 +87,7 @@ public EntryHolder(final View view) { _view = (MaterialCardView) view; _profileName = view.findViewById(R.id.profile_account_name); _profileCode = view.findViewById(R.id.profile_code); + _nextProfileCode = view.findViewById(R.id.next_profile_code); _profileIssuer = view.findViewById(R.id.profile_issuer); _profileCopied = view.findViewById(R.id.profile_copied); _description = view.findViewById(R.id.description); @@ -115,7 +118,7 @@ public long getMillisTillNextRefresh() { }); } - public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMode viewMode, AccountNamePosition accountNamePosition, boolean showIcon, boolean showProgress, boolean hidden, boolean paused, boolean dimmed, boolean showExpirationState) { + public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMode viewMode, AccountNamePosition accountNamePosition, boolean showIcon, boolean showProgress, boolean hidden, boolean paused, boolean dimmed, boolean showExpirationState, boolean showNextCode) { _entry = entry; _hidden = hidden; _paused = paused; @@ -131,6 +134,7 @@ public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMo _selected.setVisibility(View.GONE); _selectedHandler.removeCallbacksAndMessages(null); _animationHandler.removeCallbacksAndMessages(null); + _showNextCode = entry.getInfo() instanceof TotpInfo && showNextCode; _showExpirationState = _entry.getInfo() instanceof TotpInfo && showExpirationState; _favoriteIndicator.setVisibility(_entry.isFavorite() ? View.VISIBLE : View.INVISIBLE); @@ -140,6 +144,7 @@ public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMo // only show the button if this entry is of type HotpInfo _buttonRefresh.setVisibility(entry.getInfo() instanceof HotpInfo ? View.VISIBLE : View.GONE); + _nextProfileCode.setVisibility(_showNextCode ? View.VISIBLE : View.GONE); String profileIssuer = entry.getIssuer(); String profileName = entry.getName(); @@ -284,16 +289,24 @@ public void refresh() { public void refreshCode() { if (!_hidden && !_paused) { - updateCode(); + updateCodes(); startExpirationAnimation(); } } - private void updateCode() { + private void updateCodes() { _profileCode.setText(getOtp()); + + if (_showNextCode) { + _nextProfileCode.setText(getOtp(1)); + } } private String getOtp() { + return getOtp(0); + } + + private String getOtp(int offset) { OtpInfo info = _entry.getInfo(); // In previous versions of Aegis, it was possible to import entries with an empty @@ -302,7 +315,12 @@ private String getOtp() { // the OTP, instead of crashing. String otp; try { - otp = info.getOtp(); + if (info instanceof TotpInfo) { + otp = ((TotpInfo)info).getOtp((System.currentTimeMillis() / 1000) + ((long) (offset) * ((TotpInfo) _entry.getInfo()).getPeriod())); + } else { + otp = info.getOtp(); + } + if (!(info instanceof SteamInfo || info instanceof YandexInfo)) { otp = formatCode(otp); } @@ -343,7 +361,7 @@ private String formatCode(String code) { } public void revealCode() { - updateCode(); + updateCodes(); startExpirationAnimation(); _hidden = false; } @@ -352,6 +370,7 @@ public void hideCode() { String code = getOtp(); String hiddenText = code.replaceAll("\\S", Character.toString(HIDDEN_CHAR)); updateTextViewWithDots(_profileCode, hiddenText, code); + updateTextViewWithDots(_nextProfileCode, hiddenText, code); stopExpirationAnimation(); _hidden = true; } @@ -480,7 +499,7 @@ public void setPaused(boolean paused) { if (_paused) { stopExpirationAnimation(); } else if (!_hidden) { - updateCode(); + updateCodes(); startExpirationAnimation(); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java index af829d6dac..002db712ff 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java @@ -363,6 +363,10 @@ public void setShowIcon(boolean showIcon) { _adapter.setShowIcon(showIcon); } + public void setShowNextCode(boolean showNextCode) { + _adapter.setShowNextCode(showNextCode); + } + public void setShowExpirationState(boolean showExpirationState) { _showExpirationState = showExpirationState; _adapter.setShowExpirationState(showExpirationState); diff --git a/app/src/main/res/layout/card_entry.xml b/app/src/main/res/layout/card_entry.xml index 47b0533084..11f01859f2 100644 --- a/app/src/main/res/layout/card_entry.xml +++ b/app/src/main/res/layout/card_entry.xml @@ -71,7 +71,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/description" - android:layout_alignStart="@+id/profile_code"> + android:layout_alignStart="@+id/profile_codes_layout"> - + android:layout_width="match_parent" + android:layout_height="match_parent"> + + + + + diff --git a/app/src/main/res/layout/card_entry_compact.xml b/app/src/main/res/layout/card_entry_compact.xml index 053e2585e6..7769e9d249 100644 --- a/app/src/main/res/layout/card_entry_compact.xml +++ b/app/src/main/res/layout/card_entry_compact.xml @@ -85,8 +85,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/description" - android:layout_alignStart="@+id/profile_code"> - + android:layout_alignStart="@+id/profile_codes_layout"> @@ -112,23 +112,50 @@ - + android:layout_width="match_parent" + android:layout_height="match_parent"> + + + + + + + diff --git a/app/src/main/res/layout/card_entry_small.xml b/app/src/main/res/layout/card_entry_small.xml index 104d954e8c..c9b2b396ac 100644 --- a/app/src/main/res/layout/card_entry_small.xml +++ b/app/src/main/res/layout/card_entry_small.xml @@ -85,7 +85,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/description" - android:layout_alignStart="@+id/profile_code"> + android:layout_alignStart="@+id/profile_codes_layout"> - + android:layout_width="match_parent" + android:layout_height="match_parent"> + + + + + diff --git a/app/src/main/res/layout/card_entry_tile.xml b/app/src/main/res/layout/card_entry_tile.xml index eac01a6642..1f98dd40d2 100644 --- a/app/src/main/res/layout/card_entry_tile.xml +++ b/app/src/main/res/layout/card_entry_tile.xml @@ -104,22 +104,47 @@ tools:text=" - AccountName" /> - + android:layout_width="match_parent" + android:layout_height="match_parent"> + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bab03455a7..e847a4bfce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,6 +48,8 @@ Code digit grouping Select number of digits to group codes by Show the account name + Show next code + Generate and show the next code ahead of time Indicate when codes are about to expire Change the color of the codes and have them blink when they are about to expire Change the color of the codes when they are about to expire diff --git a/app/src/main/res/xml/preferences_appearance.xml b/app/src/main/res/xml/preferences_appearance.xml index e857f4eac8..b025315c83 100644 --- a/app/src/main/res/xml/preferences_appearance.xml +++ b/app/src/main/res/xml/preferences_appearance.xml @@ -41,6 +41,13 @@ android:summary="@string/pref_show_icons_summary" app:iconSpaceReserved="false"/> + +