package org.telegram.ui.Components.Premium; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.CornerPathEffect; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.RectF; import android.view.View; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.SvgHelper; import org.telegram.messenger.Utilities; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.GLIconSettingsView; import org.telegram.ui.PremiumPreviewFragment; import java.util.ArrayList; public class StarParticlesView extends View { public Drawable drawable; int size; public final static int TYPE_APP_ICON_REACT = 1001; public static final int TYPE_APP_ICON_STAR_PREMIUM = 1002; public StarParticlesView(Context context) { super(context); int particlesCount = 50; if (SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_HIGH) { particlesCount = 200; } else if (SharedConfig.getDevicePerformanceClass() == SharedConfig.PERFORMANCE_CLASS_AVERAGE) { particlesCount = 100; } drawable = new Drawable(particlesCount); drawable.type = 100; drawable.roundEffect = true; drawable.useRotate = true; drawable.useBlur = true; drawable.checkBounds = true; drawable.size1 = 4; drawable.k1 = drawable.k2 = drawable.k3 = 0.98f; drawable.init(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int sizeInternal = getMeasuredWidth() << 16 + getMeasuredHeight(); drawable.rect.set(0, 0, AndroidUtilities.dp(140), AndroidUtilities.dp(140)); drawable.rect.offset((getMeasuredWidth() - drawable.rect.width()) / 2, (getMeasuredHeight() - drawable.rect.height()) / 2); drawable.rect2.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); if (size != sizeInternal) { size = sizeInternal; drawable.resetPositions(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawable.onDraw(canvas); if (!drawable.paused) { invalidate(); } } public void flingParticles(float sum) { float maxSpeed = 15f; if (sum < 60) { maxSpeed = 5f; } else if (sum < 180) { maxSpeed = 9f; } AnimatorSet animatorSet = new AnimatorSet(); ValueAnimator.AnimatorUpdateListener updateListener = animation -> drawable.speedScale = (float) animation.getAnimatedValue(); ValueAnimator a1 = ValueAnimator.ofFloat(1f, maxSpeed); a1.addUpdateListener(updateListener); a1.setDuration(600); ValueAnimator a2 = ValueAnimator.ofFloat(maxSpeed, 1f); a2.addUpdateListener(updateListener); a2.setDuration(2000); animatorSet.playTogether(a1, a2); animatorSet.start(); } public static class Drawable { public RectF rect = new RectF(); public RectF rect2 = new RectF(); public RectF excludeRect = new RectF(); private final Bitmap[] stars = new Bitmap[3]; public boolean paused; public boolean startFromCenter; private Paint paint = new Paint(); ArrayList particles = new ArrayList<>(); public float speedScale = 1f; public final int count; public boolean useGradient; public int size1 = 14, size2 = 12, size3 = 10; public float k1 = 0.85f, k2 = 0.85f, k3 = 0.9f; public long minLifeTime = 2000; public int randLifeTime = 1000; private int lastColor; private final float dt = 1000 / AndroidUtilities.screenRefreshRate; private boolean distributionAlgorithm; Matrix matrix = new Matrix(); Matrix matrix2 = new Matrix(); Matrix matrix3 = new Matrix(); float[] points1; float[] points2; float[] points3; int pointsCount1, pointsCount2, pointsCount3; public boolean useRotate; public boolean checkBounds = false; public boolean checkTime = true; public boolean isCircle = true; public boolean useBlur = false; public boolean roundEffect = true; public int type = -1; public String colorKey = Theme.key_premiumStartSmallStarsColor; public boolean svg; public final static int TYPE_SETTINGS = 101; public Drawable(int count) { this.count = count; distributionAlgorithm = count < 50; } public void init() { if (useRotate) { points1 = new float[count * 2]; points2 = new float[count * 2]; points3 = new float[count * 2]; } generateBitmaps(); if (particles.isEmpty()) { for (int i = 0; i < count; i++) { particles.add(new Particle()); } } } public void updateColors() { int c = Theme.getColor(colorKey); if (lastColor != c) { lastColor = c; generateBitmaps(); } } private void generateBitmaps() { for (int i = 0; i < 3; i++) { int size; float k = k1; Bitmap bitmap; if (i == 0) { size = AndroidUtilities.dp(size1); } else if (i == 1) { k = k2; size = AndroidUtilities.dp(size2); } else { k = k3; size = AndroidUtilities.dp(size3); } if (type == PremiumPreviewFragment.PREMIUM_FEATURE_ADVANCED_CHAT_MANAGEMENT) { int res; if (i == 0) { res = R.raw.premium_object_folder; } else if (i == 1) { res = R.raw.premium_object_bubble; } else { res = R.raw.premium_object_settings; } stars[i] = SvgHelper.getBitmap(res, size, size, ColorUtils.setAlphaComponent(Theme.getColor(colorKey), 30)); svg = true; continue; }else if (type == PremiumPreviewFragment.PREMIUM_FEATURE_ADS) { int res; if (i == 0) { res = R.raw.premium_object_adsbubble; } else if (i == 1) { res = R.raw.premium_object_like; } else { res = R.raw.premium_object_noads; } stars[i] = SvgHelper.getBitmap(res, size, size, ColorUtils.setAlphaComponent(Theme.getColor(colorKey), 30)); svg = true; continue; } else if (type == PremiumPreviewFragment.PREMIUM_FEATURE_ANIMATED_AVATARS) { int res; if (i == 0) { res = R.raw.premium_object_video2; } else if (i == 1) { res = R.raw.premium_object_video; } else { res = R.raw.premium_object_user; } stars[i] = SvgHelper.getBitmap(res, size, size, ColorUtils.setAlphaComponent(Theme.getColor(colorKey), 30)); svg = true; continue; } else if (type == TYPE_APP_ICON_REACT) { stars[i] = SvgHelper.getBitmap(R.raw.premium_object_fire, size, size, ColorUtils.setAlphaComponent(Theme.getColor(colorKey), 30)); svg = true; continue; } else if (type == TYPE_APP_ICON_STAR_PREMIUM) { stars[i] = SvgHelper.getBitmap(R.raw.premium_object_star2, size, size, ColorUtils.setAlphaComponent(Theme.getColor(colorKey), 30)); svg = true; continue; } bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); stars[i] = bitmap; Canvas canvas = new Canvas(bitmap); if (type == PremiumPreviewFragment.PREMIUM_FEATURE_PROFILE_BADGE && (i == 1 || i == 2)) { android.graphics.drawable.Drawable drawable = ContextCompat.getDrawable(ApplicationLoader.applicationContext, R.drawable.msg_premium_liststar); drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(colorKey), PorterDuff.Mode.MULTIPLY)); drawable.setBounds(0, 0, size, size); drawable.draw(canvas); continue; } Path path = new Path(); int sizeHalf = size >> 1; int mid = (int) (sizeHalf * k); path.moveTo(0, sizeHalf); path.lineTo(mid, mid); path.lineTo(sizeHalf, 0); path.lineTo(size - mid, mid); path.lineTo(size, sizeHalf); path.lineTo(size - mid, size - mid); path.lineTo(sizeHalf, size); path.lineTo(mid, size - mid); path.lineTo(0, sizeHalf); path.close(); Paint paint = new Paint(); if (useGradient) { if (size >= AndroidUtilities.dp(10)) { PremiumGradient.getInstance().updateMainGradientMatrix(0, 0, size, size, -2 * size, 0); } else { PremiumGradient.getInstance().updateMainGradientMatrix(0, 0, size, size, -4 * size, 0); } Paint paint1 = PremiumGradient.getInstance().getMainGradientPaint(); if (roundEffect) { paint1.setPathEffect(new CornerPathEffect(AndroidUtilities.dpf2(size1 / 5f))); } if (useBlur) { paint1.setAlpha(60); } else { paint1.setAlpha(120); } canvas.drawPath(path, paint1); paint1.setPathEffect(null); paint1.setAlpha(255); } else { if (type == 100) { paint.setColor(ColorUtils.setAlphaComponent(Theme.getColor(colorKey), 200)); } else { paint.setColor(Theme.getColor(colorKey)); } if (roundEffect) { paint.setPathEffect(new CornerPathEffect(AndroidUtilities.dpf2(size1 / 5f))); } canvas.drawPath(path, paint); } if (useBlur) { Utilities.stackBlurBitmap(bitmap, 2); } } } public void resetPositions() { long time = System.currentTimeMillis(); for (int i = 0; i < particles.size(); i++) { particles.get(i).genPosition(time); } } float a; float a1; float a2; public void onDraw(Canvas canvas) { onDraw(canvas, 1f); } public void onDraw(Canvas canvas, float alpha) { long time = System.currentTimeMillis(); if (useRotate) { matrix.reset(); a += 360f * (16f / 40000f); a1 += 360f * (16f / 50000f); a2 += 360f * (16f / 60000f); matrix.setRotate(a, rect.centerX(), rect.centerY()); matrix2.setRotate(a1, rect.centerX(), rect.centerY()); matrix3.setRotate(a2, rect.centerX(), rect.centerY()); pointsCount1 = 0; pointsCount2 = 0; pointsCount3 = 0; for (int i = 0; i < particles.size(); i++) { Particle particle = particles.get(i); particle.updatePoint(); } matrix.mapPoints(points1, 0, points1, 0, pointsCount1); matrix2.mapPoints(points2, 0, points2, 0, pointsCount2); matrix3.mapPoints(points3, 0, points3, 0, pointsCount3); pointsCount1 = 0; pointsCount2 = 0; pointsCount3 = 0; } for (int i = 0; i < particles.size(); i++) { Particle particle = particles.get(i); if (paused) { particle.draw(canvas, pausedTime, alpha); } else { particle.draw(canvas, time,alpha); } if (checkTime) { if (time > particle.lifeTime) { particle.genPosition(time); } } if (checkBounds) { if (!rect2.contains(particle.drawingX, particle.drawingY)) { particle.genPosition(time); } } } } long pausedTime; private class Particle { private float x, y; private float x2, y2; private float drawingX, drawingY; private float vecX, vecY; private int starIndex; private long lifeTime; private int alpha; private float randomRotate; float inProgress; public void updatePoint() { if (starIndex == 0) { points1[2 * pointsCount1] = x; points1[2 * pointsCount1 + 1] = y; pointsCount1++; } else if (starIndex == 1) { points2[2 * pointsCount2] = x; points2[2 * pointsCount2 + 1] = y; pointsCount2++; } else if (starIndex == 2) { points3[2 * pointsCount3] = x; points3[2 * pointsCount3 + 1] = y; pointsCount3++; } } public void draw(Canvas canvas, long time, float alpha) { if (useRotate) { if (starIndex == 0) { drawingX = points1[2 * pointsCount1]; drawingY = points1[2 * pointsCount1 + 1]; pointsCount1++; } else if (starIndex == 1) { drawingX = points2[2 * pointsCount2]; drawingY = points2[2 * pointsCount2 + 1]; pointsCount2++; } else if (starIndex == 2) { drawingX = points3[2 * pointsCount3]; drawingY = points3[2 * pointsCount3 + 1]; pointsCount3++; } } else { drawingX = x; drawingY = y; } boolean skipDraw = false; if (!excludeRect.isEmpty() && excludeRect.contains(drawingX, drawingY)) { skipDraw = true; } if (!skipDraw) { canvas.save(); canvas.translate(drawingX, drawingY); if (randomRotate != 0) { canvas.rotate(randomRotate, stars[starIndex].getWidth() / 2f, stars[starIndex].getHeight() / 2f); } if (inProgress < 1f || GLIconSettingsView.smallStarsSize != 1f) { float s = AndroidUtilities.overshootInterpolator.getInterpolation(inProgress) * GLIconSettingsView.smallStarsSize; canvas.scale(s, s, 0, 0); } float outProgress = 0f; if (checkTime && lifeTime - time < 200) { outProgress = 1f - (lifeTime - time) / 150f; outProgress = Utilities.clamp(outProgress, 1f, 0f); } paint.setAlpha((int) (this.alpha * (1f - outProgress) * alpha)); canvas.drawBitmap(stars[starIndex], -(stars[starIndex].getWidth() >> 1), -(stars[starIndex].getHeight() >> 1), paint); canvas.restore(); } if (!paused) { float speed = AndroidUtilities.dp(4) * (dt / 660f) * speedScale; x += vecX * speed; y += vecY * speed; if (inProgress != 1f) { inProgress += dt / 200; if (inProgress > 1f) { inProgress = 1f; } } } } public void genPosition(long time) { starIndex = Math.abs(Utilities.fastRandom.nextInt() % stars.length); lifeTime = time + minLifeTime + Utilities.fastRandom.nextInt(randLifeTime); randomRotate = 0; if (distributionAlgorithm) { float bestDistance = 0; float bestX = rect.left + Math.abs(Utilities.fastRandom.nextInt() % rect.width()); float bestY = rect.top + Math.abs(Utilities.fastRandom.nextInt() % rect.height()); for (int k = 0; k < 10; k++) { float randX = rect.left + Math.abs(Utilities.fastRandom.nextInt() % rect.width()); float randY = rect.top + Math.abs(Utilities.fastRandom.nextInt() % rect.height()); float minDistance = Integer.MAX_VALUE; for (int j = 0; j < particles.size(); j++) { float rx; float ry; if (startFromCenter) { rx = particles.get(j).x2 - randX; ry = particles.get(j).y2 - randY; } else { rx = particles.get(j).x - randX; ry = particles.get(j).y - randY; } float distance = rx * rx + ry * ry; if (distance < minDistance) { minDistance = distance; } } if (minDistance > bestDistance) { bestDistance = minDistance; bestX = randX; bestY = randY; } } x = bestX; y = bestY; } else { if (isCircle) { float r = (Math.abs(Utilities.fastRandom.nextInt() % 1000) / 1000f) * rect.width(); float a = Math.abs(Utilities.fastRandom.nextInt() % 360); x = rect.centerX() + (float) (r * Math.sin(Math.toRadians(a))); y = rect.centerY() + (float) (r * Math.cos(Math.toRadians(a))); } else { x = rect.left + Math.abs(Utilities.fastRandom.nextInt() % rect.width()); y = rect.top + Math.abs(Utilities.fastRandom.nextInt() % rect.height()); } } double a = Math.atan2(x - rect.centerX(), y - rect.centerY()); vecX = (float) Math.sin(a); vecY = (float) Math.cos(a); if (svg) { alpha = (int) (120 * ((50 + Utilities.fastRandom.nextInt(50)) / 100f)); } else { alpha = (int) (255 * ((50 + Utilities.fastRandom.nextInt(50)) / 100f)); } if ((type == PremiumPreviewFragment.PREMIUM_FEATURE_PROFILE_BADGE && (starIndex == 1 || starIndex == 2)) || type == PremiumPreviewFragment.PREMIUM_FEATURE_ADVANCED_CHAT_MANAGEMENT || type == PremiumPreviewFragment.PREMIUM_FEATURE_ADS || type == PremiumPreviewFragment.PREMIUM_FEATURE_ANIMATED_AVATARS) { randomRotate = (int) (45 * ((Utilities.fastRandom.nextInt() % 100) / 100f)); } if (type != TYPE_SETTINGS) { inProgress = 0; } if (startFromCenter) { x2 = x; y2 = y; x = rect.centerX();// + (x - rect.centerX()) * 0.3f; y = rect.centerY();// + (y - rect.centerY()) * 0.3f; } } } } public void setPaused(boolean paused) { if (paused == drawable.paused) { return; } drawable.paused = paused; if (paused) { drawable.pausedTime = System.currentTimeMillis(); } else { for (int i = 0; i < drawable.particles.size(); i++) { drawable.particles.get(i).lifeTime += System.currentTimeMillis() - drawable.pausedTime; } invalidate(); } } }