Add ability to show next code

Co-authored-by: Alexander Bakker <ab@alexbakker.me>
This commit is contained in:
Michael Schättgen 2024-09-30 23:36:08 +02:00
parent 08d900c0c0
commit e4c9a584f4
12 changed files with 218 additions and 73 deletions

View file

@ -153,6 +153,10 @@ public class Preferences {
return _prefs.getBoolean("pref_show_icons", true); return _prefs.getBoolean("pref_show_icons", true);
} }
public boolean getShowNextCode() {
return _prefs.getBoolean("pref_show_next_code", false);
}
public boolean getShowExpirationState() { public boolean getShowExpirationState() {
return _prefs.getBoolean("pref_expiration_state", true); return _prefs.getBoolean("pref_expiration_state", true);
} }

View file

@ -37,7 +37,9 @@ public class TotpInfo extends OtpInfo {
} }
} }
public String getOtp(long time) { public String getOtp(long time) throws OtpInfoException {
checkSecret();
try { try {
OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod(), time); OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod(), time);
return otp.toString(); return otp.toString();

View file

@ -208,6 +208,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.setAccountNamePosition(_prefs.getAccountNamePosition()); _entryListView.setAccountNamePosition(_prefs.getAccountNamePosition());
_entryListView.setShowIcon(_prefs.isIconVisible()); _entryListView.setShowIcon(_prefs.isIconVisible());
_entryListView.setShowExpirationState(_prefs.getShowExpirationState()); _entryListView.setShowExpirationState(_prefs.getShowExpirationState());
_entryListView.setShowNextCode(_prefs.getShowNextCode());
_entryListView.setOnlyShowNecessaryAccountNames(_prefs.onlyShowNecessaryAccountNames()); _entryListView.setOnlyShowNecessaryAccountNames(_prefs.onlyShowNecessaryAccountNames());
_entryListView.setHighlightEntry(_prefs.isEntryHighlightEnabled()); _entryListView.setHighlightEntry(_prefs.isEntryHighlightEnabled());
_entryListView.setPauseFocused(_prefs.isPauseFocusedEnabled()); _entryListView.setPauseFocused(_prefs.isPauseFocusedEnabled());

View file

@ -60,6 +60,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
private Preferences.CodeGrouping _codeGroupSize; private Preferences.CodeGrouping _codeGroupSize;
private AccountNamePosition _accountNamePosition; private AccountNamePosition _accountNamePosition;
private boolean _showIcon; private boolean _showIcon;
private boolean _showNextCode;
private boolean _showExpirationState; private boolean _showExpirationState;
private boolean _onlyShowNecessaryAccountNames; private boolean _onlyShowNecessaryAccountNames;
private boolean _highlightEntry; private boolean _highlightEntry;
@ -116,6 +117,10 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
_showIcon = showIcon; _showIcon = showIcon;
} }
public void setShowNextCode(boolean showNextCode) {
_showNextCode = showNextCode;
}
public void setShowExpirationState(boolean showExpirationState) { public void setShowExpirationState(boolean showExpirationState) {
_showExpirationState = showExpirationState; _showExpirationState = showExpirationState;
} }
@ -544,7 +549,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} }
AccountNamePosition accountNamePosition = showAccountName ? _accountNamePosition : AccountNamePosition.HIDDEN; 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.setFocused(_selectedEntries.contains(entry));
entryHolder.setShowDragHandle(isEntryDraggable(entry)); entryHolder.setShowDragHandle(isEntryDraggable(entry));

View file

@ -50,6 +50,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
private View _favoriteIndicator; private View _favoriteIndicator;
private TextView _profileName; private TextView _profileName;
private TextView _profileCode; private TextView _profileCode;
private TextView _nextProfileCode;
private TextView _profileIssuer; private TextView _profileIssuer;
private TextView _profileCopied; private TextView _profileCopied;
private ImageView _profileDrawable; private ImageView _profileDrawable;
@ -74,6 +75,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
private UiRefresher _refresher; private UiRefresher _refresher;
private Handler _animationHandler; private Handler _animationHandler;
private AnimatorSet _expirationAnimSet; private AnimatorSet _expirationAnimSet;
private boolean _showNextCode;
private boolean _showExpirationState; private boolean _showExpirationState;
private Animation _scaleIn; private Animation _scaleIn;
@ -85,6 +87,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_view = (MaterialCardView) view; _view = (MaterialCardView) 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);
_nextProfileCode = view.findViewById(R.id.next_profile_code);
_profileIssuer = view.findViewById(R.id.profile_issuer); _profileIssuer = view.findViewById(R.id.profile_issuer);
_profileCopied = view.findViewById(R.id.profile_copied); _profileCopied = view.findViewById(R.id.profile_copied);
_description = view.findViewById(R.id.description); _description = view.findViewById(R.id.description);
@ -115,7 +118,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
}); });
} }
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; _entry = entry;
_hidden = hidden; _hidden = hidden;
_paused = paused; _paused = paused;
@ -131,6 +134,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_selected.setVisibility(View.GONE); _selected.setVisibility(View.GONE);
_selectedHandler.removeCallbacksAndMessages(null); _selectedHandler.removeCallbacksAndMessages(null);
_animationHandler.removeCallbacksAndMessages(null); _animationHandler.removeCallbacksAndMessages(null);
_showNextCode = entry.getInfo() instanceof TotpInfo && showNextCode;
_showExpirationState = _entry.getInfo() instanceof TotpInfo && showExpirationState; _showExpirationState = _entry.getInfo() instanceof TotpInfo && showExpirationState;
_favoriteIndicator.setVisibility(_entry.isFavorite() ? View.VISIBLE : View.INVISIBLE); _favoriteIndicator.setVisibility(_entry.isFavorite() ? View.VISIBLE : View.INVISIBLE);
@ -140,6 +144,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
// 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);
_nextProfileCode.setVisibility(_showNextCode ? View.VISIBLE : View.GONE);
String profileIssuer = entry.getIssuer(); String profileIssuer = entry.getIssuer();
String profileName = entry.getName(); String profileName = entry.getName();
@ -284,16 +289,24 @@ public class EntryHolder extends RecyclerView.ViewHolder {
public void refreshCode() { public void refreshCode() {
if (!_hidden && !_paused) { if (!_hidden && !_paused) {
updateCode(); updateCodes();
startExpirationAnimation(); startExpirationAnimation();
} }
} }
private void updateCode() { private void updateCodes() {
_profileCode.setText(getOtp()); _profileCode.setText(getOtp());
if (_showNextCode) {
_nextProfileCode.setText(getOtp(1));
}
} }
private String getOtp() { private String getOtp() {
return getOtp(0);
}
private String getOtp(int offset) {
OtpInfo info = _entry.getInfo(); OtpInfo info = _entry.getInfo();
// In previous versions of Aegis, it was possible to import entries with an empty // In previous versions of Aegis, it was possible to import entries with an empty
@ -302,7 +315,12 @@ public class EntryHolder extends RecyclerView.ViewHolder {
// the OTP, instead of crashing. // the OTP, instead of crashing.
String otp; String otp;
try { 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)) { if (!(info instanceof SteamInfo || info instanceof YandexInfo)) {
otp = formatCode(otp); otp = formatCode(otp);
} }
@ -343,7 +361,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
} }
public void revealCode() { public void revealCode() {
updateCode(); updateCodes();
startExpirationAnimation(); startExpirationAnimation();
_hidden = false; _hidden = false;
} }
@ -352,6 +370,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
String code = getOtp(); String code = getOtp();
String hiddenText = code.replaceAll("\\S", Character.toString(HIDDEN_CHAR)); String hiddenText = code.replaceAll("\\S", Character.toString(HIDDEN_CHAR));
updateTextViewWithDots(_profileCode, hiddenText, code); updateTextViewWithDots(_profileCode, hiddenText, code);
updateTextViewWithDots(_nextProfileCode, hiddenText, code);
stopExpirationAnimation(); stopExpirationAnimation();
_hidden = true; _hidden = true;
} }
@ -480,7 +499,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
if (_paused) { if (_paused) {
stopExpirationAnimation(); stopExpirationAnimation();
} else if (!_hidden) { } else if (!_hidden) {
updateCode(); updateCodes();
startExpirationAnimation(); startExpirationAnimation();
} }
} }

View file

@ -363,6 +363,10 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
_adapter.setShowIcon(showIcon); _adapter.setShowIcon(showIcon);
} }
public void setShowNextCode(boolean showNextCode) {
_adapter.setShowNextCode(showNextCode);
}
public void setShowExpirationState(boolean showExpirationState) { public void setShowExpirationState(boolean showExpirationState) {
_showExpirationState = showExpirationState; _showExpirationState = showExpirationState;
_adapter.setShowExpirationState(showExpirationState); _adapter.setShowExpirationState(showExpirationState);

View file

@ -71,7 +71,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/description" android:id="@+id/description"
android:layout_alignStart="@+id/profile_code"> android:layout_alignStart="@+id/profile_codes_layout">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -109,23 +109,47 @@
android:textSize="16sp" android:textSize="16sp"
android:visibility="invisible" /> android:visibility="invisible" />
<TextView <LinearLayout
android:layout_width="wrap_content" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_marginBottom="0dp"
android:textAppearance="?android:attr/textAppearanceMedium" android:paddingBottom="0dp"
android:fontFamily="sans-serif-light" android:id="@+id/profile_codes_layout"
tools:text="012 345"
android:id="@+id/profile_code"
android:layoutDirection="ltr"
android:layout_below="@id/description" android:layout_below="@id/description"
android:includeFontPadding="false" android:layout_width="match_parent"
android:fallbackLineSpacing="false" android:layout_height="match_parent">
android:textSize="34sp"
android:textColor="?attr/colorCode" <TextView
android:layout_marginStart="6dp" android:layout_width="wrap_content"
android:layout_alignParentStart="true" android:layout_height="wrap_content"
android:layout_marginTop="0dp" android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="normal|bold"/> android:fontFamily="sans-serif-light"
tools:text="012 345"
android:id="@+id/profile_code"
android:layoutDirection="ltr"
android:textSize="34sp"
android:layout_below="@id/description"
android:textColor="?attr/colorCode"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
android:layout_alignParentStart="true"
android:layout_marginTop="0dp"
android:textStyle="normal|bold"/>
<TextView
android:id="@+id/next_profile_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:paddingTop="0dp"
android:paddingStart="2dp"
android:layout_alignParentStart="true"
android:textColor="?attr/colorOnSurfaceDim"
android:textSize="20sp"
android:textStyle="normal|bold"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
tools:text="412 643"/>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -85,8 +85,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/description" android:id="@+id/description"
android:layout_alignStart="@+id/profile_code"> android:layout_alignStart="@+id/profile_codes_layout">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -95,6 +94,7 @@
android:text="@string/issuer" android:text="@string/issuer"
android:textStyle="bold" android:textStyle="bold"
android:includeFontPadding="false" android:includeFontPadding="false"
android:fallbackLineSpacing="false"
android:textSize="13sp" android:textSize="13sp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1"/> android:maxLines="1"/>
@ -112,23 +112,50 @@
</RelativeLayout> </RelativeLayout>
<TextView <LinearLayout
android:layout_width="wrap_content" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_marginBottom="0dp"
android:textAppearance="?android:attr/textAppearanceMedium" android:paddingBottom="0dp"
android:fontFamily="sans-serif-light" android:id="@+id/profile_codes_layout"
tools:text="012 345"
android:id="@+id/profile_code"
android:layoutDirection="ltr"
android:layout_below="@id/description" android:layout_below="@id/description"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
android:textSize="26sp"
android:textColor="?attr/colorCode"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_alignParentStart="true" android:layout_width="match_parent"
android:layout_marginTop="0dp" android:layout_height="match_parent">
android:textStyle="normal|bold"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="sans-serif-light"
tools:text="012 345"
android:id="@+id/profile_code"
android:layoutDirection="ltr"
android:layout_below="@id/description"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
android:textSize="26sp"
android:textColor="?attr/colorCode"
android:layout_alignParentStart="true"
android:layout_marginTop="0dp"
android:textStyle="normal|bold"/>
<TextView
android:id="@+id/next_profile_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:letterSpacing="-0.01"
android:layout_marginTop="0dp"
android:paddingTop="0dp"
android:paddingStart="2dp"
android:textColor="?attr/colorOnSurfaceDim"
android:textSize="16sp"
android:textStyle="normal|bold"
android:fallbackLineSpacing="false"
android:includeFontPadding="false"
tools:text="412 643"/>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -85,7 +85,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/description" android:id="@+id/description"
android:layout_alignStart="@+id/profile_code"> android:layout_alignStart="@+id/profile_codes_layout">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -110,23 +110,48 @@
tools:text=" - AccountName" /> tools:text=" - AccountName" />
</RelativeLayout> </RelativeLayout>
<TextView <LinearLayout
android:layout_width="wrap_content" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_marginBottom="0dp"
android:textAppearance="?android:attr/textAppearanceMedium" android:paddingBottom="0dp"
android:fontFamily="sans-serif-light" android:id="@+id/profile_codes_layout"
tools:text="012 345"
android:id="@+id/profile_code"
android:layoutDirection="ltr"
android:layout_below="@id/description" android:layout_below="@id/description"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
android:textSize="26sp"
android:textColor="?attr/colorCode"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_alignParentStart="true" android:layout_width="match_parent"
android:layout_marginTop="0dp" android:layout_height="match_parent">
android:textStyle="normal|bold"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="sans-serif-light"
tools:text="012 345"
android:id="@+id/profile_code"
android:layoutDirection="ltr"
android:layout_below="@id/description"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
android:textSize="26sp"
android:textColor="?attr/colorCode"
android:layout_alignParentStart="true"
android:layout_marginTop="0dp"
android:textStyle="normal|bold"/>
<TextView
android:id="@+id/next_profile_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:letterSpacing="-0.01"
android:layout_marginTop="0dp"
android:paddingTop="0dp"
android:paddingStart="2dp"
android:textColor="?attr/colorOnSurfaceDim"
android:textSize="16sp"
android:textStyle="normal|bold"
android:fallbackLineSpacing="false"
android:includeFontPadding="false"
tools:text="412 643"/>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -104,22 +104,47 @@
tools:text=" - AccountName" /> tools:text=" - AccountName" />
</RelativeLayout> </RelativeLayout>
<TextView <LinearLayout
android:layout_width="wrap_content" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_marginBottom="0dp"
android:textAppearance="?android:attr/textAppearanceMedium" android:paddingBottom="0dp"
android:fontFamily="sans-serif-light" android:id="@+id/profile_codes_layout"
tools:text="012 345"
android:id="@+id/profile_code"
android:layoutDirection="ltr"
android:layout_below="@id/description" android:layout_below="@id/description"
android:includeFontPadding="false" android:layout_width="match_parent"
android:fallbackLineSpacing="false" android:layout_height="match_parent">
android:textSize="26sp"
android:textColor="?attr/colorCode" <TextView
android:layout_alignParentStart="true" android:layout_width="wrap_content"
android:layout_marginTop="10dp" android:layout_height="wrap_content"
android:textStyle="normal|bold"/> android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="sans-serif-light"
tools:text="012 345"
android:id="@+id/profile_code"
android:layoutDirection="ltr"
android:layout_below="@id/description"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
android:textSize="26sp"
android:textColor="?attr/colorCode"
android:layout_alignParentStart="true"
android:layout_marginTop="10dp"
android:textStyle="normal|bold"/>
<TextView
android:id="@+id/next_profile_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:letterSpacing="-0.01"
android:layout_marginTop="0dp"
android:paddingTop="0dp"
android:paddingStart="2dp"
android:textColor="?attr/colorOnSurfaceDim"
android:textSize="16sp"
android:textStyle="normal|bold"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
tools:text="412 643"/>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -48,6 +48,8 @@
<string name="pref_code_group_size_title">Code digit grouping</string> <string name="pref_code_group_size_title">Code digit grouping</string>
<string name="pref_code_group_size_summary">Select number of digits to group codes by</string> <string name="pref_code_group_size_summary">Select number of digits to group codes by</string>
<string name="pref_account_name_position_title">Show the account name</string> <string name="pref_account_name_position_title">Show the account name</string>
<string name="pref_show_next_code_title">Show next code</string>
<string name="pref_show_next_code_summary">Generate and show the next code ahead of time</string>
<string name="pref_expiration_state_title">Indicate when codes are about to expire</string> <string name="pref_expiration_state_title">Indicate when codes are about to expire</string>
<string name="pref_expiration_state_summary">Change the color of the codes and have them blink when they are about to expire</string> <string name="pref_expiration_state_summary">Change the color of the codes and have them blink when they are about to expire</string>
<string name="pref_expiration_state_fallback">Change the color of the codes when they are about to expire</string> <string name="pref_expiration_state_fallback">Change the color of the codes when they are about to expire</string>

View file

@ -41,6 +41,13 @@
android:summary="@string/pref_show_icons_summary" android:summary="@string/pref_show_icons_summary"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_show_next_code"
android:title="@string/pref_show_next_code_title"
android:summary="@string/pref_show_next_code_summary"
app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat <androidx.preference.SwitchPreferenceCompat
android:defaultValue="true" android:defaultValue="true"
android:key="pref_expiration_state" android:key="pref_expiration_state"