Skip to content

Commit

Permalink
Show when codes are about to expire
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Bakker <ab@alexbakker.me>
  • Loading branch information
michaelschattgen and alexbakker committed Sep 21, 2024
1 parent 17f106f commit c8a37e7
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 11 deletions.
4 changes: 4 additions & 0 deletions app/src/main/java/com/beemdevelopment/aegis/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ public boolean isIconVisible() {
return _prefs.getBoolean("pref_show_icons", true);
}

public boolean getShowExpirationState() {
return _prefs.getBoolean("pref_expiration_state", true);
}

public CodeGrouping getCodeGroupSize() {
String value = _prefs.getString("pref_code_group_size_string", "GROUPING_THREES");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ protected void onCreate(Bundle savedInstanceState) {
_entryListView.setCodeGroupSize(_prefs.getCodeGroupSize());
_entryListView.setAccountNamePosition(_prefs.getAccountNamePosition());
_entryListView.setShowIcon(_prefs.isIconVisible());
_entryListView.setShowExpirationState(_prefs.getShowExpirationState());
_entryListView.setOnlyShowNecessaryAccountNames(_prefs.onlyShowNecessaryAccountNames());
_entryListView.setHighlightEntry(_prefs.isEntryHighlightEnabled());
_entryListView.setPauseFocused(_prefs.isPauseFocusedEnabled());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
public class AppearancePreferencesFragment extends PreferencesFragment {
private Preference _groupsPreference;
private Preference _resetUsageCountPreference;
private Preference _showExpirationStatePreference;
private Preference _currentAccountNamePositionPreference;

@Override
Expand Down Expand Up @@ -125,6 +126,11 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
return true;
});

Preference _showExpirationStatePreference = requirePreference("pref_expiration_state");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
_showExpirationStatePreference.setSummary(getString(R.string.pref_expiration_state_fallback));
}

String[] codeGroupings = getResources().getStringArray(R.array.pref_code_groupings_values);
String[] codeGroupingNames = getResources().getStringArray(R.array.pref_code_groupings);
Preference codeDigitGroupingPreference = requirePreference("pref_code_group_size_string");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class EntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
private Preferences.CodeGrouping _codeGroupSize;
private AccountNamePosition _accountNamePosition;
private boolean _showIcon;
private boolean _showExpirationState;
private boolean _onlyShowNecessaryAccountNames;
private boolean _highlightEntry;
private boolean _tempHighlightEntry;
Expand Down Expand Up @@ -115,6 +116,10 @@ public void setShowIcon(boolean showIcon) {
_showIcon = showIcon;
}

public void setShowExpirationState(boolean showExpirationState) {
_showExpirationState = showExpirationState;
}

public void setTapToReveal(boolean tapToReveal) {
_tapToReveal = tapToReveal;
}
Expand Down Expand Up @@ -539,7 +544,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);
entryHolder.setData(entry, _codeGroupSize, _viewMode, accountNamePosition, _showIcon, showProgress, hidden, paused, dimmed, _showExpirationState);
entryHolder.setFocused(_selectedEntries.contains(entry));
entryHolder.setShowDragHandle(isEntryDraggable(entry));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.beemdevelopment.aegis.ui.views;

import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.text.Spannable;
import android.text.SpannableString;
Expand Down Expand Up @@ -35,6 +40,7 @@
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.bumptech.glide.Glide;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.color.MaterialColors;

public class EntryHolder extends RecyclerView.ViewHolder {
private static final float DEFAULT_ALPHA = 1.0f;
Expand All @@ -52,7 +58,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
private RelativeLayout _description;
private ImageView _dragHandle;
private ViewMode _viewMode;

private final ImageView _selected;
private final Handler _selectedHandler;

Expand All @@ -67,6 +73,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {

private UiRefresher _refresher;
private Handler _animationHandler;
private AnimatorSet _expirationAnimSet;
private boolean _showExpirationState;

private Animation _scaleIn;
private Animation _scaleOut;
Expand Down Expand Up @@ -97,9 +105,7 @@ public EntryHolder(final View view) {
_refresher = new UiRefresher(new UiRefresher.Listener() {
@Override
public void onRefresh() {
if (!_hidden && !_paused) {
refreshCode();
}
refreshCode();
}

@Override
Expand All @@ -109,12 +115,13 @@ 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) {
public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMode viewMode, AccountNamePosition accountNamePosition, boolean showIcon, boolean showProgress, boolean hidden, boolean paused, boolean dimmed, boolean showExpirationState) {
_entry = entry;
_hidden = hidden;
_paused = paused;
_codeGrouping = groupSize;
_viewMode = viewMode;

_accountNamePosition = accountNamePosition;
if (viewMode.equals(ViewMode.TILES) && _accountNamePosition == AccountNamePosition.END) {
_accountNamePosition = AccountNamePosition.BELOW;
Expand All @@ -124,6 +131,7 @@ public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMo
_selected.setVisibility(View.GONE);
_selectedHandler.removeCallbacksAndMessages(null);
_animationHandler.removeCallbacksAndMessages(null);
_showExpirationState = _entry.getInfo() instanceof TotpInfo && showExpirationState;

_favoriteIndicator.setVisibility(_entry.isFavorite() ? View.VISIBLE : View.INVISIBLE);

Expand All @@ -149,7 +157,6 @@ public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMo
}

showIcon(showIcon);

itemView.setAlpha(dimmed ? DIMMED_ALPHA : DEFAULT_ALPHA);
}

Expand Down Expand Up @@ -278,6 +285,7 @@ public void refresh() {
public void refreshCode() {
if (!_hidden && !_paused) {
updateCode();
startExpirationAnimation();
}
}

Expand Down Expand Up @@ -336,14 +344,15 @@ private String formatCode(String code) {

public void revealCode() {
updateCode();
startExpirationAnimation();
_hidden = false;
}

public void hideCode() {
String code = getOtp();
String hiddenText = code.replaceAll("\\S", Character.toString(HIDDEN_CHAR));
updateTextViewWithDots(_profileCode, hiddenText, code);

stopExpirationAnimation();
_hidden = true;
}

Expand Down Expand Up @@ -387,6 +396,73 @@ private void updateTextViewWithDots(TextView textView, String hiddenCode, String
textView.setText(dotsString);
}

public void startExpirationAnimation() {
stopExpirationAnimation();
if (!_showExpirationState) {
return;
}

TotpInfo info = (TotpInfo) _entry.getInfo();
final int totalStateDuration = 7000;
final int colorShiftDuration = 300;

if (info.getPeriod() * 1000 < totalStateDuration) {
_profileCode.setTextColor(MaterialColors.getColor(_profileCode, com.google.android.material.R.attr.colorError));
return;
}

float durationScale = AnimationsHelper.Scale.ANIMATOR.getValue(itemView.getContext());
long delayAnimDuration = info.getPeriod() * 1000L - totalStateDuration - colorShiftDuration;
ValueAnimator delayAnim = ValueAnimator.ofFloat(0f, 0f);
delayAnim.setDuration((long) (delayAnimDuration / durationScale));

// Workaround for when animations are disabled or Android version being too old
if (durationScale == 0.0 || Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
if (info.getMillisTillNextRotation() < totalStateDuration) {
_profileCode.setTextColor(MaterialColors.getColor(_profileCode, com.google.android.material.R.attr.colorError));
} else {
_animationHandler.postDelayed(() -> {
_profileCode.setTextColor(MaterialColors.getColor(_profileCode, com.google.android.material.R.attr.colorError));
}, info.getMillisTillNextRotation() - totalStateDuration);
}

return;
}

int colorFrom = _profileCode.getCurrentTextColor();
int colorTo = MaterialColors.getColor(_profileCode, com.google.android.material.R.attr.colorError);
ValueAnimator colorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnim.setDuration((long) (colorShiftDuration / durationScale));
colorAnim.addUpdateListener(a -> _profileCode.setTextColor((int) a.getAnimatedValue()));

final int blinkDuration = 3000;
ValueAnimator delayAnim2 = ValueAnimator.ofFloat(0f, 0f);
delayAnim2.setDuration((long) ((totalStateDuration - blinkDuration) / durationScale));

float alphaTo = durationScale > 0 ? .5f : 1f;
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(_profileCode, "alpha", 1f, alphaTo);
alphaAnim.setDuration((long) (500 / durationScale));
alphaAnim.setRepeatCount(blinkDuration / 500 - 1);
alphaAnim.setRepeatMode(ValueAnimator.REVERSE);

_expirationAnimSet = new AnimatorSet();
_expirationAnimSet.playSequentially(delayAnim, colorAnim, delayAnim2, alphaAnim);
_expirationAnimSet.start();
long currentPlayTime = (info.getPeriod() * 1000L) - info.getMillisTillNextRotation();
_expirationAnimSet.setCurrentPlayTime((long) (currentPlayTime / durationScale));
}

private void stopExpirationAnimation() {
if (_expirationAnimSet != null) {
_expirationAnimSet.cancel();
_expirationAnimSet = null;
}

int colorTo = MaterialColors.getColor(_profileCode, R.attr.colorCode);
_profileCode.setTextColor(colorTo);
_profileCode.setAlpha(1f);
}

public void showIcon(boolean show) {
if (show) {
_profileDrawable.setVisibility(View.VISIBLE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.ui.glide.GlideHelper;
import com.beemdevelopment.aegis.ui.models.ErrorCardInfo;
import com.beemdevelopment.aegis.util.UUIDMap;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultGroup;
import com.bumptech.glide.Glide;
Expand All @@ -53,12 +52,10 @@

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

public class EntryListView extends Fragment implements EntryAdapter.Listener {
private EntryAdapter _adapter;
Expand All @@ -71,6 +68,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
private ViewPreloadSizeProvider<VaultEntry> _preloadSizeProvider;
private TotpProgressBar _progressBar;
private boolean _showProgress;
private boolean _showExpirationState;
private ViewMode _viewMode;
private LinearLayout _emptyStateView;

Expand Down Expand Up @@ -365,6 +363,11 @@ public void setShowIcon(boolean showIcon) {
_adapter.setShowIcon(showIcon);
}

public void setShowExpirationState(boolean showExpirationState) {
_showExpirationState = showExpirationState;
_adapter.setShowExpirationState(showExpirationState);
}

public void setHighlightEntry(boolean highlightEntry) {
_adapter.setHighlightEntry(highlightEntry);
}
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
<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_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</string>
<string name="pref_shared_issuer_account_name_title">Only show account name when necessary</string>
<string name="pref_shared_issuer_account_name_summary">Only show account names whenever they share the same issuer. Other account names will be hidden.</string>
<string name="pref_account_name_position_summary_override">This setting is overridden by the tiles view mode. Account name will be shown below the issuer.</string>
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/xml/preferences_appearance.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@
android:summary="@string/pref_show_icons_summary"
app:iconSpaceReserved="false"/>

<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_expiration_state"
android:title="@string/pref_expiration_state_title"
android:summary="@string/pref_expiration_state_summary"
app:iconSpaceReserved="false"/>

<Preference
android:key="pref_code_group_size_string"
android:title="@string/pref_code_group_size_title"
Expand Down

0 comments on commit c8a37e7

Please sign in to comment.