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 b2c2cc48..63c6fa58 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ShimmerHeartDrawable.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/ShimmerHeartDrawable.java @@ -3,10 +3,10 @@ package tw.nekomimi.nekogram.helpers; 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.Rect; import android.graphics.Shader; import android.graphics.drawable.Drawable; @@ -16,36 +16,27 @@ import androidx.annotation.NonNull; import org.telegram.messenger.AndroidUtilities; /** - * A heart-shaped badge filled with a continuously moving iridescent gradient - * ("shimmer"). It self-invalidates each frame, so when attached to a view via - * {@code setRightDrawable(...)} the host keeps repainting automatically (the - * SimpleTextView drawable callback chain handles invalidation). + * A glossy 3D-looking heart badge with a soft multicolor gradient (purple/blue + * to orange) and a moving glossy highlight ("blik") sweeping across it, like an + * iridescent emoji sticker. + * + * It self-invalidates each frame, so when attached to a view via + * {@code setRightDrawable(...)} / {@code setRightDrawable2(...)} the host keeps + * repainting automatically (the SimpleTextView drawable callback chain handles + * invalidation). */ public class ShimmerHeartDrawable extends Drawable { - private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint basePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint tintPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint highlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint shadePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Path heart = new Path(); - private final Matrix gradientMatrix = new Matrix(); - private LinearGradient gradient; private int lastWidth = -1; - private int gradientWidth; + private int lastHeight = -1; - // Full rainbow spectrum swept across the heart (seamless loop). - private static final int[] COLORS = new int[] { - 0xFFFF3B30, // red - 0xFFFF9500, // orange - 0xFFFFCC00, // yellow - 0xFF34C759, // green - 0xFF00C7BE, // teal - 0xFF007AFF, // blue - 0xFF5856D6, // indigo - 0xFFAF52DE, // violet - 0xFFFF2D55, // pink - 0xFFFF3B30, // back to red (seamless loop) - }; - - private static final long CYCLE_MS = 2200L; + private static final long CYCLE_MS = 3200L; private final int size; public ShimmerHeartDrawable() { @@ -54,16 +45,10 @@ public class ShimmerHeartDrawable extends Drawable { public ShimmerHeartDrawable(int sizePx) { this.size = sizePx; - paint.setStyle(Paint.Style.FILL); - } - - private void buildGradient(int width) { - // Span the whole rainbow across roughly the heart width so multiple - // colors are visible at once, then sweep it for the shimmer. - gradientWidth = Math.max(1, width); - gradient = new LinearGradient(0, 0, gradientWidth, 0, COLORS, null, Shader.TileMode.MIRROR); - paint.setShader(gradient); - lastWidth = width; + basePaint.setStyle(Paint.Style.FILL); + tintPaint.setStyle(Paint.Style.FILL); + highlightPaint.setStyle(Paint.Style.FILL); + shadePaint.setStyle(Paint.Style.FILL); } private void buildHeart(Rect b) { @@ -72,7 +57,6 @@ public class ShimmerHeartDrawable extends Drawable { float h = b.height(); float l = b.left; float t = b.top; - // Heart path normalized to the bounds. 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); @@ -82,11 +66,40 @@ public class ShimmerHeartDrawable extends Drawable { heart.close(); } + private void buildShaders(Rect b) { + float w = b.width(); + float h = b.height(); + float l = b.left; + float t = b.top; + + // Base diagonal gradient: deep purple (top-left) -> blue -> warm orange (bottom-right). + basePaint.setShader(new LinearGradient( + l, t, l + w, t + h, + new int[]{0xFF7A3CE0, 0xFF5B6BFF, 0xFF4FA8FF, 0xFFFF9A4D, 0xFFFF7A3C}, + new float[]{0f, 0.32f, 0.55f, 0.82f, 1f}, + Shader.TileMode.CLAMP)); + + // A soft purple glow blob on the upper-left lobe for depth. + tintPaint.setShader(new RadialGradient( + l + w * 0.30f, t + h * 0.30f, w * 0.55f, + new int[]{0xCC8A4DFF, 0x00000000}, + null, Shader.TileMode.CLAMP)); + + // Soft inner shade at the bottom tip for a rounded 3D feel. + shadePaint.setShader(new RadialGradient( + l + w * 0.5f, t + h * 0.95f, w * 0.6f, + new int[]{0x66351A6B, 0x00000000}, + null, Shader.TileMode.CLAMP)); + + lastWidth = b.width(); + lastHeight = b.height(); + } + @Override protected void onBoundsChange(@NonNull Rect bounds) { super.onBoundsChange(bounds); buildHeart(bounds); - buildGradient(bounds.width()); + buildShaders(bounds); } @Override @@ -95,15 +108,44 @@ public class ShimmerHeartDrawable extends Drawable { if (b.width() == 0 || b.height() == 0) { return; } - if (gradient == null || lastWidth != b.width()) { - buildGradient(b.width()); + if (lastWidth != b.width() || lastHeight != b.height()) { buildHeart(b); + buildShaders(b); } + + float w = b.width(); + float h = b.height(); float phase = (System.currentTimeMillis() % CYCLE_MS) / (float) CYCLE_MS; - gradientMatrix.reset(); - gradientMatrix.setTranslate(b.left - phase * gradientWidth, 0); - gradient.setLocalMatrix(gradientMatrix); - canvas.drawPath(heart, paint); + double ang = phase * 2 * Math.PI; + + int save = canvas.save(); + canvas.clipPath(heart); + + // Base colors + purple glow + bottom shade. + canvas.drawPath(heart, basePaint); + canvas.drawPath(heart, tintPaint); + canvas.drawPath(heart, shadePaint); + + // Moving glossy highlight: a soft bright blob orbiting inside the heart. + float hx = b.left + w * (0.5f + 0.28f * (float) Math.cos(ang)); + float hy = b.top + h * (0.42f + 0.24f * (float) Math.sin(ang)); + float hr = w * 0.45f; + highlightPaint.setShader(new RadialGradient( + hx, hy, hr, + new int[]{0xCCFFFFFF, 0x33FFFFFF, 0x00FFFFFF}, + new float[]{0f, 0.4f, 1f}, + Shader.TileMode.CLAMP)); + canvas.drawPath(heart, highlightPaint); + + // A small fixed top-left specular dot for a glossy sticker look. + highlightPaint.setShader(new RadialGradient( + b.left + w * 0.34f, b.top + h * 0.28f, w * 0.18f, + new int[]{0xE6FFFFFF, 0x00FFFFFF}, + null, Shader.TileMode.CLAMP)); + canvas.drawPath(heart, highlightPaint); + + canvas.restoreToCount(save); + invalidateSelf(); } @@ -119,12 +161,12 @@ public class ShimmerHeartDrawable extends Drawable { @Override public void setAlpha(int alpha) { - paint.setAlpha(alpha); + basePaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter colorFilter) { - paint.setColorFilter(colorFilter); + basePaint.setColorFilter(colorFilter); } @Override