diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ShimmerHeartDrawable.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ShimmerHeartDrawable.java index b70cbc69..16723676 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ShimmerHeartDrawable.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ShimmerHeartDrawable.java @@ -1,24 +1,31 @@ package tw.nekomimi.nekogram.helpers; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.LinearGradient; +import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.Path; import android.graphics.PixelFormat; -import android.graphics.RadialGradient; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.R; /** - * A soft, airbrushed 3D heart badge matching the GhostCloud "sponsor.png": - * violet in the upper-left, blue through the centre, warm orange/coral on the - * right and bottom, all blended smoothly with a gentle glossy highlight. The - * colour blobs drift slightly so the badge subtly shimmers. + * Sponsor badge that renders the GhostCloud "sponsor.png" heart and overlays a + * moving glossy highlight ("shimmer") that is clipped to the artwork's shape + * via its alpha channel, so it looks like light sweeping across the heart. * * It self-invalidates each frame, so when attached to a view via * {@code setRightDrawable(...)} / {@code setRightDrawable2(...)} the host keeps @@ -27,15 +34,18 @@ import org.telegram.messenger.AndroidUtilities; */ public class ShimmerHeartDrawable extends Drawable { - private final Paint basePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Paint blobPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Paint highlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Path heart = new Path(); + private static Bitmap sharedBitmap; - private int lastWidth = -1; - private int lastHeight = -1; + private final Paint bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final Paint shinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final Matrix shineMatrix = new Matrix(); - private static final long CYCLE_MS = 5200L; + private final Bitmap bitmap; + private LinearGradient shineGradient; + private int shineWidth; + + private static final long CYCLE_MS = 2600L; private final int size; public ShimmerHeartDrawable() { @@ -44,98 +54,70 @@ public class ShimmerHeartDrawable extends Drawable { public ShimmerHeartDrawable(int sizePx) { this.size = sizePx; - basePaint.setStyle(Paint.Style.FILL); - blobPaint.setStyle(Paint.Style.FILL); - highlightPaint.setStyle(Paint.Style.FILL); + bitmap = loadBitmap(); + // The moving highlight is drawn only where the artwork is opaque. + maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); } - private void buildHeart(Rect b) { - heart.reset(); - float w = b.width(); - float h = b.height(); - float l = b.left; - float t = b.top; - float cx = l + w / 2f; - heart.moveTo(cx, t + h * 0.30f); - heart.cubicTo(l + w * 0.40f, t + h * 0.05f, l + w * 0.02f, t + h * 0.18f, l + w * 0.10f, t + h * 0.45f); - heart.cubicTo(l + w * 0.17f, t + h * 0.66f, l + w * 0.40f, t + h * 0.80f, cx, t + h * 0.97f); - heart.cubicTo(l + w * 0.60f, t + h * 0.80f, l + w * 0.83f, t + h * 0.66f, l + w * 0.90f, t + h * 0.45f); - heart.cubicTo(l + w * 0.98f, t + h * 0.18f, l + w * 0.60f, t + h * 0.05f, cx, t + h * 0.30f); - heart.close(); + private static Bitmap loadBitmap() { + if (sharedBitmap == null || sharedBitmap.isRecycled()) { + try { + Context ctx = ApplicationLoader.applicationContext; + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inScaled = false; + sharedBitmap = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.foxsponsor_heart, opts); + } catch (Throwable ignore) { + } + } + return sharedBitmap; } - /** A soft circular blob of one colour fading to transparent. */ - private void drawBlob(Canvas canvas, float cx, float cy, float r, int color) { - blobPaint.setShader(new RadialGradient( - cx, cy, Math.max(1f, r), - new int[]{color, color & 0x00FFFFFF}, - new float[]{0f, 1f}, - Shader.TileMode.CLAMP)); - canvas.drawRect(getBounds(), blobPaint); - } - - @Override - protected void onBoundsChange(@NonNull Rect bounds) { - super.onBoundsChange(bounds); - buildHeart(bounds); - lastWidth = bounds.width(); - lastHeight = bounds.height(); + private void buildShine(int w) { + // A bright diagonal band, narrow relative to the badge width. + shineWidth = Math.max(1, (int) (w * 0.55f)); + shineGradient = new LinearGradient( + 0, 0, shineWidth, 0, + new int[]{0x00FFFFFF, 0x00FFFFFF, 0x99FFFFFF, 0x00FFFFFF, 0x00FFFFFF}, + new float[]{0f, 0.35f, 0.5f, 0.65f, 1f}, + Shader.TileMode.CLAMP); + shinePaint.setShader(shineGradient); } @Override public void draw(@NonNull Canvas canvas) { Rect b = getBounds(); - if (b.width() == 0 || b.height() == 0) { + if (b.width() == 0 || b.height() == 0 || bitmap == null) { return; } - if (lastWidth != b.width() || lastHeight != b.height()) { - buildHeart(b); - lastWidth = b.width(); - lastHeight = b.height(); + if (shineGradient == null || shineWidth != (int) (b.width() * 0.55f)) { + buildShine(b.width()); } - float w = b.width(); - float h = b.height(); - float l = b.left; - float t = b.top; + RectF dst = new RectF(b); - // Subtle drift so the colours softly shimmer. + // Layer so the shimmer can be masked against the artwork alpha. + int sc = canvas.saveLayer(dst, null); + + // 1) the heart artwork + canvas.drawBitmap(bitmap, null, dst, bitmapPaint); + + // 2) moving highlight band, swept diagonally float phase = (System.currentTimeMillis() % CYCLE_MS) / (float) CYCLE_MS; - double a = phase * 2 * Math.PI; - float dx = (float) Math.cos(a) * w * 0.05f; - float dy = (float) Math.sin(a) * h * 0.05f; + float travel = b.width() + shineWidth; + float x = b.left - shineWidth + phase * travel; + shineMatrix.reset(); + // slight diagonal slant + shineMatrix.postRotate(20f, 0, 0); + shineMatrix.postTranslate(x, b.top); + shineGradient.setLocalMatrix(shineMatrix); - int save = canvas.save(); - canvas.clipPath(heart); + int ssc = canvas.saveLayer(dst, null); + canvas.drawRect(dst, shinePaint); + // keep the highlight only where the artwork is opaque + canvas.drawBitmap(bitmap, null, dst, maskPaint); + canvas.restoreToCount(ssc); - // Light lavender base so blends stay airy. - basePaint.setShader(null); - basePaint.setColor(0xFFB9A8F0); - canvas.drawRect(b, basePaint); - - // Soft overlapping colour blobs (airbrushed look). - // Violet — upper-left. - drawBlob(canvas, l + w * (0.30f) + dx, t + h * (0.28f) + dy, w * 0.62f, 0xFF7A2FE0); - // Blue — centre-left. - drawBlob(canvas, l + w * (0.40f) - dx, t + h * (0.55f) + dy, w * 0.55f, 0xFF4F6BFF); - // Cyan/blue glow — centre. - drawBlob(canvas, l + w * (0.52f) + dy, t + h * (0.45f) - dx, w * 0.42f, 0xCC59B7FF); - // Warm orange — right. - drawBlob(canvas, l + w * (0.82f) - dx, t + h * (0.40f) - dy, w * 0.60f, 0xFFFF9A3D); - // Coral/pink — bottom-right. - drawBlob(canvas, l + w * (0.70f) + dx, t + h * (0.80f) + dy, w * 0.55f, 0xFFFF7E5A); - // Deep violet — bottom-left corner for contrast. - drawBlob(canvas, l + w * (0.22f) + dx, t + h * (0.82f) - dy, w * 0.45f, 0xCC6A2BC8); - - // Gentle glossy highlight, upper-centre. - highlightPaint.setShader(new RadialGradient( - l + w * 0.45f + dx, t + h * 0.30f + dy, w * 0.40f, - new int[]{0x80FFFFFF, 0x00FFFFFF}, - new float[]{0f, 1f}, - Shader.TileMode.CLAMP)); - canvas.drawRect(b, highlightPaint); - - canvas.restoreToCount(save); + canvas.restoreToCount(sc); invalidateSelf(); } @@ -152,12 +134,12 @@ public class ShimmerHeartDrawable extends Drawable { @Override public void setAlpha(int alpha) { - basePaint.setAlpha(alpha); + bitmapPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter colorFilter) { - basePaint.setColorFilter(colorFilter); + bitmapPaint.setColorFilter(colorFilter); } @Override diff --git a/TMessagesProj/src/main/res/drawable-nodpi/foxsponsor_heart.png b/TMessagesProj/src/main/res/drawable-nodpi/foxsponsor_heart.png new file mode 100644 index 00000000..4ac70c76 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-nodpi/foxsponsor_heart.png differ