Use a real gaussian blur for the masked phone number

- Add BlurredTextSpan: draws the real glyphs through a BlurMaskFilter
  into a cached offscreen bitmap, so the blur works on both software and
  hardware-accelerated views
- formatOwnPhone() now returns this span in Blur mode instead of
  replacing digits with bullets
- SettingsActivity header builds the subtitle with a SpannableStringBuilder
  so the span is preserved
This commit is contained in:
instant992 2026-06-10 03:03:03 +04:00
parent edd7c4a8b4
commit 87b8ea4949
3 changed files with 116 additions and 11 deletions

View file

@ -529,7 +529,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter
avatarDrawable.setInfo(user);
avatarView.setForUserOrChat(user, avatarDrawable);
titleView.setText(UserObject.getUserName(user));
final StringBuilder sb = new StringBuilder();
final android.text.SpannableStringBuilder sb = new android.text.SpannableStringBuilder();
if (user != null) {
sb.append(tw.nekomimi.nekogram.NekoConfig.formatOwnPhone(PhoneFormat.getInstance().format("+" + user.phone)));
}

View file

@ -701,16 +701,12 @@ public class NekoConfig {
if (hidePhoneNumber == PHONE_HIDE) {
return org.telegram.messenger.LocaleController.getString(org.telegram.messenger.R.string.MobileHidden);
}
StringBuilder sb = new StringBuilder(formatted.length());
for (int i = 0; i < formatted.length(); i++) {
char c = formatted.charAt(i);
if (Character.isDigit(c)) {
sb.append('•');
} else {
sb.append(c);
}
}
return sb.toString();
// PHONE_BLUR: render the real number, but genuinely blurred.
String text = formatted.toString();
android.text.SpannableString span = new android.text.SpannableString(text);
span.setSpan(new tw.nekomimi.nekogram.helpers.BlurredTextSpan(text), 0, text.length(),
android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return span;
}
public static void setNameOrder(int order) {

View file

@ -0,0 +1,109 @@
package tw.nekomimi.nekogram.helpers;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.style.ReplacementSpan;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.telegram.messenger.AndroidUtilities;
/**
* Renders a piece of text genuinely blurred (Gaussian-style), instead of just
* masking the characters. The real glyphs are drawn into an offscreen software
* bitmap with a {@link BlurMaskFilter} applied and then blitted onto the target
* canvas, so it works the same on both software and hardware-accelerated views.
*/
public class BlurredTextSpan extends ReplacementSpan {
private final String text;
private final float blurRadius;
private Bitmap cache;
private int cacheWidth;
private int cacheHeight;
private float cacheTextSize;
private int cacheColor;
private float cacheBaseline;
private int cachePad;
public BlurredTextSpan(String text) {
this(text, AndroidUtilities.dp(3.5f));
}
public BlurredTextSpan(String text, float blurRadius) {
this.text = text == null ? "" : text;
this.blurRadius = Math.max(0.5f, blurRadius);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence charSequence, int start, int end, @Nullable Paint.FontMetricsInt fm) {
if (fm != null) {
Paint.FontMetricsInt pfm = paint.getFontMetricsInt();
fm.ascent = pfm.ascent;
fm.descent = pfm.descent;
fm.top = pfm.top;
fm.bottom = pfm.bottom;
fm.leading = pfm.leading;
}
return (int) Math.ceil(paint.measureText(text));
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence charSequence, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
ensureCache(paint);
if (cache == null) {
return;
}
canvas.drawBitmap(cache, x - cachePad, y - cacheBaseline, null);
}
private void ensureCache(Paint paint) {
final float textSize = paint.getTextSize();
final int color = paint.getColor();
final int width = (int) Math.ceil(paint.measureText(text));
if (width <= 0) {
cache = null;
return;
}
// padding so the blur isn't clipped at the edges
final int pad = (int) Math.ceil(blurRadius * 2.5f);
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
final int textHeight = fm.descent - fm.ascent;
final int w = width + pad * 2;
final int h = textHeight + pad * 2;
if (cache != null && cacheWidth == w && cacheHeight == h
&& cacheTextSize == textSize && cacheColor == color) {
return;
}
if (cache != null) {
cache.recycle();
}
try {
cache = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
} catch (Throwable e) {
cache = null;
return;
}
cacheWidth = w;
cacheHeight = h;
cacheTextSize = textSize;
cacheColor = color;
cachePad = pad;
// baseline offset inside the cache bitmap, relative to the text top
cacheBaseline = pad - fm.ascent;
Canvas c = new Canvas(cache);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setTextSize(textSize);
p.setColor(color);
p.setTypeface(paint.getTypeface());
p.setMaskFilter(new BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL));
c.drawText(text, pad, cacheBaseline, p);
}
}