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.