diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java
index ad95d8a9..007ae4d6 100644
--- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java
+++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java
@@ -279,6 +279,7 @@ public class NotificationCenter {
public static final int updatedChatRanks = totalEvents++;
public static final int joinedGroup = totalEvents++;
public static final int loadedAiComposeTones = totalEvents++;
+ public static final int sponsorStatusUpdated = totalEvents++;
//global
public static final int pushMessagesUpdated = totalEvents++;
diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java
index 98781d56..54bae7bc 100644
--- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java
+++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java
@@ -341,6 +341,7 @@ import tw.nekomimi.nekogram.BackButtonMenuRecent;
import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.SimpleTextViewSwitcher;
import tw.nekomimi.nekogram.helpers.PopupHelper;
+import tw.nekomimi.nekogram.helpers.SponsorHelper;
import tw.nekomimi.nekogram.helpers.remote.ConfigHelper;
import tw.nekomimi.nekogram.settings.NekoSettingsActivity;
import tw.nekomimi.nekogram.translator.Translator;
@@ -375,6 +376,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
private final Drawable[] verifiedCheckDrawable = new Drawable[2];
private final CrossfadeDrawable[] verifiedCrossfadeDrawable = new CrossfadeDrawable[2];
private final CrossfadeDrawable[] premiumCrossfadeDrawable = new CrossfadeDrawable[2];
+ private final Drawable[] foxSponsorDrawable = new Drawable[2];
private ScamDrawable scamDrawable;
private UndoView undoView;
private OverlaysView overlaysView;
@@ -2261,6 +2263,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
getNotificationCenter().addObserver(this, NotificationCenter.profileMusicUpdated);
getNotificationCenter().addObserver(this, NotificationCenter.updatedChatRanks);
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.emojiLoaded);
+ NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.sponsorStatusUpdated);
+ if (userId == getUserConfig().getClientUserId() || myProfile) {
+ SponsorHelper.refresh(false);
+ }
updateRowsIds();
if (listAdapter != null) {
listAdapter.notifyDataSetChanged();
@@ -2402,6 +2408,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
getNotificationCenter().removeObserver(this, NotificationCenter.profileMusicUpdated);
getNotificationCenter().removeObserver(this, NotificationCenter.updatedChatRanks);
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.emojiLoaded);
+ NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.sponsorStatusUpdated);
if (avatarsViewPager != null) {
avatarsViewPager.onDestroy();
}
@@ -9185,6 +9192,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
public void didReceivedNotification(int id, int account, final Object... args) {
if (id == NotificationCenter.uploadStoryEnd || id == NotificationCenter.chatWasBoostedByUser) {
checkCanSendStoryForPosting();
+ } else if (id == NotificationCenter.sponsorStatusUpdated) {
+ if (userId != 0) {
+ updateProfileData(false);
+ }
} else if (id == NotificationCenter.updateInterfaces) {
int mask = (Integer) args[0];
boolean infoChanged = (mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0 || (mask & MessagesController.UPDATE_MASK_STATUS) != 0 || (mask & MessagesController.UPDATE_MASK_EMOJI_STATUS) != 0;
@@ -11167,6 +11178,19 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
return verifiedCrossfadeDrawable[a];
}
+ 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;
+ }
+ return foxSponsorDrawable[a];
+ }
+
private Drawable getPremiumCrossfadeDrawable(int a) {
if (premiumCrossfadeDrawable[a] == null) {
premiumStarDrawable[a] = ContextCompat.getDrawable(getParentActivity(), R.drawable.msg_premium_liststar).mutate();
@@ -11538,6 +11562,9 @@ 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;
@@ -11561,6 +11588,8 @@ 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);
}
diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SponsorHelper.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SponsorHelper.java
new file mode 100644
index 00000000..982fada4
--- /dev/null
+++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SponsorHelper.java
@@ -0,0 +1,145 @@
+package tw.nekomimi.nekogram.helpers;
+
+import android.content.SharedPreferences;
+
+import org.json.JSONObject;
+import org.telegram.messenger.AndroidUtilities;
+import org.telegram.messenger.ApplicationLoader;
+import org.telegram.messenger.FileLog;
+import org.telegram.messenger.NotificationCenter;
+import org.telegram.messenger.UserConfig;
+import org.telegram.messenger.Utilities;
+
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * Resolves the "sponsor" status of the current Telegram user against the
+ * FoxCloud backend and caches it locally.
+ *
+ * Identification is done purely by Telegram ID: the backend endpoint
+ * {@code GET /mobile/me} accepts an {@code X-Tg-Id} header and looks the user
+ * up by the Telegram account that was linked on the website / bot. No
+ * subscription URL or manual input is required.
+ *
+ * The result is cached per-account in shared preferences so the badge can be
+ * drawn synchronously while a fresh check runs in the background. When the
+ * cached value changes, {@link NotificationCenter#sponsorStatusUpdated} is
+ * posted on the global instance.
+ */
+public final class SponsorHelper {
+
+ private SponsorHelper() {}
+
+ private static final String TAG = "SponsorHelper";
+ private static final String BASE_URL = "https://vpnghost.space";
+ private static final String PREFS = "foxsponsor";
+ // Re-check at most once per hour while the value is fresh.
+ private static final long TTL_MS = 60 * 60 * 1000L;
+
+ private static volatile boolean checking = false;
+
+ private static SharedPreferences prefs() {
+ return ApplicationLoader.applicationContext.getSharedPreferences(PREFS, android.content.Context.MODE_PRIVATE);
+ }
+
+ /** Cached sponsor flag for the given Telegram user id. Defaults to false. */
+ public static boolean isSponsor(long telegramId) {
+ if (telegramId == 0) {
+ return false;
+ }
+ return prefs().getBoolean("sponsor_" + telegramId, false);
+ }
+
+ /** Convenience: sponsor flag for the currently selected account. */
+ public static boolean isCurrentUserSponsor() {
+ var user = UserConfig.getInstance(UserConfig.selectedAccount).getCurrentUser();
+ return user != null && isSponsor(user.id);
+ }
+
+ /**
+ * Kick off a background refresh of the sponsor status for the current
+ * account. Safe to call repeatedly: it self-throttles via {@link #TTL_MS}
+ * unless {@code force} is set.
+ */
+ public static void refresh(boolean force) {
+ var user = UserConfig.getInstance(UserConfig.selectedAccount).getCurrentUser();
+ if (user == null || user.id == 0) {
+ return;
+ }
+ final long telegramId = user.id;
+ if (checking) {
+ return;
+ }
+ if (!force) {
+ long last = prefs().getLong("checked_" + telegramId, 0);
+ if (Math.abs(System.currentTimeMillis() - last) < TTL_MS) {
+ return;
+ }
+ }
+ checking = true;
+ Utilities.globalQueue.postRunnable(() -> {
+ try {
+ doCheck(telegramId);
+ } finally {
+ checking = false;
+ }
+ });
+ }
+
+ // @WorkerThread
+ private static void doCheck(long telegramId) {
+ HttpURLConnection connection = null;
+ try {
+ URL url = new URL(BASE_URL + "/mobile/me");
+ connection = (HttpURLConnection) url.openConnection();
+ connection.setInstanceFollowRedirects(true);
+ connection.setConnectTimeout(15000);
+ connection.setReadTimeout(15000);
+ connection.setRequestProperty("X-Tg-Id", String.valueOf(telegramId));
+ connection.setRequestProperty("Accept", "application/json");
+
+ int code = connection.getResponseCode();
+ if (code != HttpURLConnection.HTTP_OK) {
+ FileLog.d(TAG + ": /mobile/me returned HTTP " + code);
+ // Don't clobber a previously known value on transient errors.
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ try (InputStream in = connection.getInputStream()) {
+ byte[] buffer = new byte[4096];
+ int read;
+ while ((read = in.read(buffer)) >= 0) {
+ sb.append(new String(buffer, 0, read, "UTF-8"));
+ }
+ }
+
+ JSONObject json = new JSONObject(sb.toString());
+ if (!json.optBoolean("success", false)) {
+ return;
+ }
+ boolean sponsor = json.optBoolean("is_sponsor", false);
+ store(telegramId, sponsor);
+ } catch (Exception e) {
+ FileLog.e(e);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ private static void store(long telegramId, boolean sponsor) {
+ boolean previous = prefs().getBoolean("sponsor_" + telegramId, false);
+ prefs().edit()
+ .putBoolean("sponsor_" + telegramId, sponsor)
+ .putLong("checked_" + telegramId, System.currentTimeMillis())
+ .apply();
+ if (previous != sponsor) {
+ AndroidUtilities.runOnUIThread(() ->
+ NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.sponsorStatusUpdated));
+ }
+ }
+}
diff --git a/TMessagesProj/src/main/res/drawable/foxsponsor_badge.xml b/TMessagesProj/src/main/res/drawable/foxsponsor_badge.xml
new file mode 100644
index 00000000..b2b37b9e
--- /dev/null
+++ b/TMessagesProj/src/main/res/drawable/foxsponsor_badge.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/TMessagesProj/src/main/res/values/strings_neko.xml b/TMessagesProj/src/main/res/values/strings_neko.xml
index 943996ca..87ba589d 100644
--- a/TMessagesProj/src/main/res/values/strings_neko.xml
+++ b/TMessagesProj/src/main/res/values/strings_neko.xml
@@ -149,6 +149,7 @@
Share FoxiGram...
FoxiGram %1$s\nBased on Telegram %2$s\nDesigned by %3$s
Installing update...
+ Sponsor
Downloading update...
A notification will be shown when the update completes.
The app will relaunch when the update completes.