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);
}
public boolean getShowNextCode() {
return _prefs.getBoolean("pref_show_next_code", false);
}
public boolean getShowExpirationState() {
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 {
OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod(), time);
return otp.toString();

View file

@ -208,6 +208,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_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());

View file

@ -60,6 +60,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
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 class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
_showIcon = showIcon;
}
public void setShowNextCode(boolean showNextCode) {
_showNextCode = showNextCode;
}
public void setShowExpirationState(boolean showExpirationState) {
_showExpirationState = showExpirationState;
}
@ -544,7 +549,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
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));

View file

@ -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 class EntryHolder extends RecyclerView.ViewHolder {
_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 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;
_hidden = hidden;
_paused = paused;
@ -131,6 +134,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_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 class EntryHolder extends RecyclerView.ViewHolder {
// 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 class EntryHolder extends RecyclerView.ViewHolder {
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 @@ public class EntryHolder extends RecyclerView.ViewHolder {
// the OTP, instead of crashing.
String otp;
try {
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 @@ public class EntryHolder extends RecyclerView.ViewHolder {
}
public void revealCode() {
updateCode();
updateCodes();
startExpirationAnimation();
_hidden = false;
}
@ -352,6 +370,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
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 class EntryHolder extends RecyclerView.ViewHolder {
if (_paused) {
stopExpirationAnimation();
} else if (!_hidden) {
updateCode();
updateCodes();
startExpirationAnimation();
}
}

View file

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

View file

@ -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">
<TextView
android:layout_width="wrap_content"
@ -109,6 +109,15 @@
android:textSize="16sp"
android:visibility="invisible" />
<LinearLayout
android:orientation="vertical"
android:layout_marginBottom="0dp"
android:paddingBottom="0dp"
android:id="@+id/profile_codes_layout"
android:layout_below="@id/description"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -117,16 +126,31 @@
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:textSize="34sp"
android:textColor="?attr/colorCode"
android:layout_marginStart="6dp"
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>
<LinearLayout

View file

@ -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">
<TextView
android:layout_width="wrap_content"
@ -95,6 +94,7 @@
android:text="@string/issuer"
android:textStyle="bold"
android:includeFontPadding="false"
android:fallbackLineSpacing="false"
android:textSize="13sp"
android:ellipsize="end"
android:maxLines="1"/>
@ -112,6 +112,16 @@
</RelativeLayout>
<LinearLayout
android:orientation="vertical"
android:layout_marginBottom="0dp"
android:paddingBottom="0dp"
android:id="@+id/profile_codes_layout"
android:layout_below="@id/description"
android:layout_marginStart="6dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -125,11 +135,28 @@
android:fallbackLineSpacing="false"
android:textSize="26sp"
android:textColor="?attr/colorCode"
android:layout_marginStart="6dp"
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>
<LinearLayout

View file

@ -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">
<TextView
android:layout_width="wrap_content"
@ -110,6 +110,16 @@
tools:text=" - AccountName" />
</RelativeLayout>
<LinearLayout
android:orientation="vertical"
android:layout_marginBottom="0dp"
android:paddingBottom="0dp"
android:id="@+id/profile_codes_layout"
android:layout_below="@id/description"
android:layout_marginStart="6dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -123,11 +133,26 @@
android:fallbackLineSpacing="false"
android:textSize="26sp"
android:textColor="?attr/colorCode"
android:layout_marginStart="6dp"
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>
<LinearLayout

View file

@ -104,6 +104,15 @@
tools:text=" - AccountName" />
</RelativeLayout>
<LinearLayout
android:orientation="vertical"
android:layout_marginBottom="0dp"
android:paddingBottom="0dp"
android:id="@+id/profile_codes_layout"
android:layout_below="@id/description"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -121,6 +130,22 @@
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>
<LinearLayout

View file

@ -48,6 +48,8 @@
<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_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_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>

View file

@ -41,6 +41,13 @@
android:summary="@string/pref_show_icons_summary"
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
android:defaultValue="true"
android:key="pref_expiration_state"