Sponsor badge: shimmering heart + tap-to-explain bulletin
- Replace static star with self-animating iridescent ShimmerHeartDrawable - Move badge to clickable rightDrawable slot; tap shows a Nekogram-style bulletin explaining why the badge was granted - Add en/ru strings; drop unused vector resource
This commit is contained in:
parent
e926e7afc6
commit
323c1ff10e
5 changed files with 159 additions and 27 deletions
|
|
@ -11180,17 +11180,23 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
|
|||
|
||||
private Drawable getFoxSponsorDrawable(int a) {
|
||||
if (foxSponsorDrawable[a] == null) {
|
||||
Drawable d = ContextCompat.getDrawable(getParentActivity(), R.drawable.foxsponsor_badge).mutate();
|
||||
int color = getThemedColor(Theme.key_profile_verifiedBackground);
|
||||
if (a == 1) {
|
||||
color = dontApplyPeerColor(color);
|
||||
}
|
||||
d.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
foxSponsorDrawable[a] = d;
|
||||
foxSponsorDrawable[a] = new tw.nekomimi.nekogram.helpers.ShimmerHeartDrawable(AndroidUtilities.dp(a == 1 ? 22 : 18));
|
||||
}
|
||||
return foxSponsorDrawable[a];
|
||||
}
|
||||
|
||||
private void showSponsorInfo() {
|
||||
if (getParentActivity() == null) {
|
||||
return;
|
||||
}
|
||||
BulletinFactory.of(ProfileActivity.this)
|
||||
.createSimpleBulletin(
|
||||
new tw.nekomimi.nekogram.helpers.ShimmerHeartDrawable(AndroidUtilities.dp(28)),
|
||||
LocaleController.getString(R.string.FoxSponsorBadge),
|
||||
LocaleController.getString(R.string.FoxSponsorBadgeInfo))
|
||||
.show();
|
||||
}
|
||||
|
||||
private Drawable getPremiumCrossfadeDrawable(int a) {
|
||||
if (premiumCrossfadeDrawable[a] == null) {
|
||||
premiumStarDrawable[a] = ContextCompat.getDrawable(getParentActivity(), R.drawable.msg_premium_liststar).mutate();
|
||||
|
|
@ -11550,7 +11556,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
|
|||
}, resourcesProvider);
|
||||
} : null);
|
||||
Drawable leftIcon = currentEncryptedChat != null ? getLockIconDrawable() : null;
|
||||
boolean rightIconIsPremium = false, rightIconIsStatus = false;
|
||||
boolean rightIconIsPremium = false, rightIconIsStatus = false, rightIconIsSponsor = false;
|
||||
nameTextView[a].setRightDrawableOutside(a == 0);
|
||||
if (a == 0 && !copyFromChatActivity) {
|
||||
if (user.scam || user.fake) {
|
||||
|
|
@ -11562,14 +11568,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
|
|||
} else if (getMessagesController().isDialogMuted(dialogId != 0 ? dialogId : userId, topicId)) {
|
||||
nameTextView[a].setRightDrawable2(getThemedDrawable(Theme.key_drawable_muteIconDrawable));
|
||||
nameTextViewRightDrawable2ContentDescription = LocaleController.getString(R.string.NotificationsMuted);
|
||||
} else if (user.self && SponsorHelper.isSponsor(user.id)) {
|
||||
nameTextView[a].setRightDrawable2(getFoxSponsorDrawable(a));
|
||||
nameTextViewRightDrawable2ContentDescription = LocaleController.getString(R.string.FoxSponsorBadge);
|
||||
} else {
|
||||
nameTextView[a].setRightDrawable2(null);
|
||||
nameTextViewRightDrawable2ContentDescription = null;
|
||||
}
|
||||
if (user != null/* && !getMessagesController().premiumFeaturesBlocked()*/ && !MessagesController.isSupportUser(user) && DialogObject.getEmojiStatusDocumentId(user.emoji_status) != 0) {
|
||||
if (user.self && SponsorHelper.isSponsor(user.id)) {
|
||||
rightIconIsSponsor = true;
|
||||
nameTextView[a].setRightDrawable(getFoxSponsorDrawable(a));
|
||||
nameTextViewRightDrawableContentDescription = LocaleController.getString(R.string.FoxSponsorBadge);
|
||||
} else if (user != null/* && !getMessagesController().premiumFeaturesBlocked()*/ && !MessagesController.isSupportUser(user) && DialogObject.getEmojiStatusDocumentId(user.emoji_status) != 0) {
|
||||
rightIconIsStatus = true;
|
||||
rightIconIsPremium = false;
|
||||
nameTextView[a].setRightDrawable(getEmojiStatusDrawable(user.emoji_status, false, false, a));
|
||||
|
|
@ -11588,12 +11595,13 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
|
|||
nameTextView[a].setRightDrawable2(getScamDrawable(user.scam ? 0 : 1));
|
||||
} else if (user.verified) {
|
||||
nameTextView[a].setRightDrawable2(getVerifiedCrossfadeDrawable(a));
|
||||
} else if (user.self && SponsorHelper.isSponsor(user.id)) {
|
||||
nameTextView[a].setRightDrawable2(getFoxSponsorDrawable(a));
|
||||
} else {
|
||||
nameTextView[a].setRightDrawable2(null);
|
||||
}
|
||||
if (/*!getMessagesController().premiumFeaturesBlocked() && */user != null && !MessagesController.isSupportUser(user) && DialogObject.getEmojiStatusDocumentId(user.emoji_status) != 0) {
|
||||
if (user.self && SponsorHelper.isSponsor(user.id)) {
|
||||
rightIconIsSponsor = true;
|
||||
nameTextView[a].setRightDrawable(getFoxSponsorDrawable(a));
|
||||
} else if (/*!getMessagesController().premiumFeaturesBlocked() && */user != null && !MessagesController.isSupportUser(user) && DialogObject.getEmojiStatusDocumentId(user.emoji_status) != 0) {
|
||||
rightIconIsStatus = true;
|
||||
rightIconIsPremium = false;
|
||||
nameTextView[a].setRightDrawable(getEmojiStatusDrawable(user.emoji_status, true, true, a));
|
||||
|
|
@ -11612,10 +11620,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
|
|||
nameTextView[a].setLeftDrawableOutside(false);
|
||||
}
|
||||
nameTextView[a].setLeftDrawable(leftIcon);
|
||||
if (a == 1 && (rightIconIsStatus || rightIconIsPremium)) {
|
||||
if (a == 1 && (rightIconIsStatus || rightIconIsPremium || rightIconIsSponsor)) {
|
||||
nameTextView[a].setRightDrawableOutside(true);
|
||||
}
|
||||
if (user.self && getMessagesController().isPremiumUser(user)) {
|
||||
if (rightIconIsSponsor) {
|
||||
nameTextView[a].setRightDrawableOnClick(v -> showSponsorInfo());
|
||||
} else if (user.self && getMessagesController().isPremiumUser(user)) {
|
||||
nameTextView[a].setRightDrawableOnClick(v -> {
|
||||
showStatusSelect();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
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.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
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).
|
||||
*/
|
||||
public class ShimmerHeartDrawable extends Drawable {
|
||||
|
||||
private final Paint paint = 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;
|
||||
|
||||
// Iridescent palette swept across the heart.
|
||||
private static final int[] COLORS = new int[] {
|
||||
0xFFFF4D6D, // pink-red
|
||||
0xFFFF8FA3, // light pink
|
||||
0xFFFFC2D1, // pale
|
||||
0xFFFF5C8A, // magenta
|
||||
0xFFFF1E56, // deep red
|
||||
0xFFFF4D6D, // back to start (seamless loop)
|
||||
};
|
||||
|
||||
private static final long CYCLE_MS = 2200L;
|
||||
private final int size;
|
||||
|
||||
public ShimmerHeartDrawable() {
|
||||
this(AndroidUtilities.dp(18));
|
||||
}
|
||||
|
||||
public ShimmerHeartDrawable(int sizePx) {
|
||||
this.size = sizePx;
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
private void buildGradient(int width) {
|
||||
gradientWidth = width * 2;
|
||||
gradient = new LinearGradient(0, 0, gradientWidth, 0, COLORS, null, Shader.TileMode.MIRROR);
|
||||
paint.setShader(gradient);
|
||||
lastWidth = width;
|
||||
}
|
||||
|
||||
private void buildHeart(Rect b) {
|
||||
heart.reset();
|
||||
float w = b.width();
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
buildHeart(bounds);
|
||||
buildGradient(bounds.width());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
Rect b = getBounds();
|
||||
if (b.width() == 0 || b.height() == 0) {
|
||||
return;
|
||||
}
|
||||
if (gradient == null || lastWidth != b.width()) {
|
||||
buildGradient(b.width());
|
||||
buildHeart(b);
|
||||
}
|
||||
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);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
paint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
paint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2L14.6,7.27L20.42,8.12L16.21,12.22L17.2,18.02L12,15.28L6.8,18.02L7.79,12.22L3.58,8.12L9.4,7.27L12,2Z" />
|
||||
</vector>
|
||||
|
|
@ -322,5 +322,7 @@
|
|||
<string name="TabsPositionBottom">Снизу</string>
|
||||
<string name="StrokeOnViews">Эффекты бликов</string>
|
||||
<!-- Accessibility -->
|
||||
<string name="AccessibilitySettings">Настройки доступности</string>
|
||||
<string name="AccessibilitySettings">Настройки специальных возможностей</string>
|
||||
<string name="FoxSponsorBadge">Спонсор</string>
|
||||
<string name="FoxSponsorBadgeInfo">Эта отметка выдана за поддержку проекта. Спасибо!</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@
|
|||
<string name="NekogramVersion">FoxiGram %1$s\nBased on Telegram %2$s\nDesigned by %3$s</string>
|
||||
<string name="UpdateInstalling">Installing update...</string>
|
||||
<string name="FoxSponsorBadge">Sponsor</string>
|
||||
<string name="FoxSponsorBadgeInfo">You received this badge for supporting the project. Thank you!</string>
|
||||
<string name="UpdateDownloading">Downloading update...</string>
|
||||
<string name="UpdateInstallingNotification">A notification will be shown when the update completes.</string>
|
||||
<string name="UpdateInstallingRelaunch">The app will relaunch when the update completes.</string>
|
||||
|
|
|
|||
Loading…
Reference in a new issue