Merge pull request #1458 from michaelschattgen/fix/hidden-dots-size

Fix sizing inconsistency of the dots in hidden view
This commit is contained in:
Alexander Bakker 2024-08-23 23:08:35 +02:00 committed by GitHub
commit 9eae773efb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 85 additions and 4 deletions

View file

@ -0,0 +1,30 @@
package com.beemdevelopment.aegis.helpers;
import android.graphics.Rect;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
import androidx.annotation.NonNull;
public class CenterVerticalSpan extends MetricAffectingSpan {
Rect _substringBounds;
public CenterVerticalSpan(Rect substringBounds) {
_substringBounds = substringBounds;
}
@Override
public void updateMeasureState(@NonNull TextPaint textPaint) {
applyBaselineShift(textPaint);
}
@Override
public void updateDrawState(@NonNull TextPaint textPaint) {
applyBaselineShift(textPaint);
}
private void applyBaselineShift(TextPaint textPaint) {
float topDifference = textPaint.getFontMetrics().top - _substringBounds.top;
textPaint.baselineShift -= (topDifference / 2f);
}
}

View file

@ -1,6 +1,11 @@
package com.beemdevelopment.aegis.ui.views; package com.beemdevelopment.aegis.ui.views;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler; import android.os.Handler;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -17,6 +22,7 @@ import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.ViewMode; import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.helpers.AnimationsHelper; import com.beemdevelopment.aegis.helpers.AnimationsHelper;
import com.beemdevelopment.aegis.helpers.CenterVerticalSpan;
import com.beemdevelopment.aegis.helpers.SimpleAnimationEndListener; import com.beemdevelopment.aegis.helpers.SimpleAnimationEndListener;
import com.beemdevelopment.aegis.helpers.UiRefresher; import com.beemdevelopment.aegis.helpers.UiRefresher;
import com.beemdevelopment.aegis.otp.HotpInfo; import com.beemdevelopment.aegis.otp.HotpInfo;
@ -276,6 +282,10 @@ public class EntryHolder extends RecyclerView.ViewHolder {
} }
private void updateCode() { private void updateCode() {
_profileCode.setText(getOtp());
}
private String getOtp() {
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
@ -292,7 +302,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
otp = _view.getResources().getString(R.string.error_all_caps); otp = _view.getResources().getString(R.string.error_all_caps);
} }
_profileCode.setText(otp); return otp;
} }
private String formatCode(String code) { private String formatCode(String code) {
@ -330,12 +340,53 @@ public class EntryHolder extends RecyclerView.ViewHolder {
} }
public void hideCode() { public void hideCode() {
String hiddenText = new String(new char[_entry.getInfo().getDigits()]).replace("\0", Character.toString(HIDDEN_CHAR)); String code = getOtp();
hiddenText = formatCode(hiddenText); String hiddenText = code.replaceAll("\\S", Character.toString(HIDDEN_CHAR));
_profileCode.setText(hiddenText); updateTextViewWithDots(_profileCode, hiddenText, code);
_hidden = true; _hidden = true;
} }
private void updateTextViewWithDots(TextView textView, String hiddenCode, String code) {
Paint paint = new Paint();
paint.setTextSize(_profileCode.getTextSize());
// Calculate the difference between the actual code width and the dots width
float codeWidth = paint.measureText(code);
float dotsWidth = paint.measureText(hiddenCode);
float scaleFactor = codeWidth / dotsWidth;
scaleFactor = (float)(Math.round(scaleFactor * 10.0) / 10.0);
// If scale is higher or equal to 0.8, do nothing and proceed with the normal text rendering
if (scaleFactor >= 0.8) {
textView.setText(hiddenCode);
return;
}
// We need to use an invisible character in order to get the height of the profileCode textview consistent
// Tokens without a space (ie Steam TOTP) will get misaligned without this
SpannableString dotsString = new SpannableString("\u200B" + hiddenCode);
// Only scale the digits/characters, skip the spaces
int start = 1;
for (int i = 0; i <= dotsString.length(); i++) {
if (i == dotsString.length() || dotsString.charAt(i) == ' ') {
if (i > start) {
dotsString.setSpan(new RelativeSizeSpan(scaleFactor), start, i, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
start = i + 1;
}
}
Rect dotsRectBounds = new Rect();
paint.getTextBounds(hiddenCode, 1, hiddenCode.length(), dotsRectBounds);
// Use custom CenterVerticalSpan to make sure the dots are vertically aligned
dotsString.setSpan(new CenterVerticalSpan(dotsRectBounds), 1, dotsString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(dotsString);
}
public void showIcon(boolean show) { public void showIcon(boolean show) {
if (show) { if (show) {
_profileDrawable.setVisibility(View.VISIBLE); _profileDrawable.setVisibility(View.VISIBLE);