Sponsor badges for everyone + new app icon
- Sponsor checkmark now shows on ANY user's profile, not just your own - SponsorHelper batches sponsor-status lookups by Telegram id (cached 6h) and posts sponsorStatusUpdated so the badge redraws when resolved - ProfileActivity requests the lookup when opening a profile - Replace the main launcher icon with foxigram4.png (all densities, legacy + adaptive foreground)
|
|
@ -11359,6 +11359,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
|
||||||
if (avatarContainer == null || nameTextView == null || getParentActivity() == null) {
|
if (avatarContainer == null || nameTextView == null || getParentActivity() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (userId != 0) {
|
||||||
|
SponsorHelper.requestSponsorStatus(userId);
|
||||||
|
}
|
||||||
String onlineTextOverride;
|
String onlineTextOverride;
|
||||||
int currentConnectionState = getConnectionsManager().getConnectionState();
|
int currentConnectionState = getConnectionsManager().getConnectionState();
|
||||||
if (currentConnectionState == ConnectionsManager.ConnectionStateWaitingForNetwork) {
|
if (currentConnectionState == ConnectionsManager.ConnectionStateWaitingForNetwork) {
|
||||||
|
|
@ -11568,7 +11571,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
|
||||||
} else if (getMessagesController().isDialogMuted(dialogId != 0 ? dialogId : userId, topicId)) {
|
} else if (getMessagesController().isDialogMuted(dialogId != 0 ? dialogId : userId, topicId)) {
|
||||||
nameTextView[a].setRightDrawable2(getThemedDrawable(Theme.key_drawable_muteIconDrawable));
|
nameTextView[a].setRightDrawable2(getThemedDrawable(Theme.key_drawable_muteIconDrawable));
|
||||||
nameTextViewRightDrawable2ContentDescription = LocaleController.getString(R.string.NotificationsMuted);
|
nameTextViewRightDrawable2ContentDescription = LocaleController.getString(R.string.NotificationsMuted);
|
||||||
} else if (user.self && SponsorHelper.isSponsor(user.id)) {
|
} else if (SponsorHelper.isKnownSponsor(user.id)) {
|
||||||
rightIconIsSponsor = true;
|
rightIconIsSponsor = true;
|
||||||
nameTextView[a].setRightDrawable2(getFoxSponsorDrawable(a));
|
nameTextView[a].setRightDrawable2(getFoxSponsorDrawable(a));
|
||||||
nameTextViewRightDrawable2ContentDescription = LocaleController.getString(R.string.FoxSponsorBadge);
|
nameTextViewRightDrawable2ContentDescription = LocaleController.getString(R.string.FoxSponsorBadge);
|
||||||
|
|
@ -11595,7 +11598,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter.
|
||||||
nameTextView[a].setRightDrawable2(getScamDrawable(user.scam ? 0 : 1));
|
nameTextView[a].setRightDrawable2(getScamDrawable(user.scam ? 0 : 1));
|
||||||
} else if (user.verified) {
|
} else if (user.verified) {
|
||||||
nameTextView[a].setRightDrawable2(getVerifiedCrossfadeDrawable(a));
|
nameTextView[a].setRightDrawable2(getVerifiedCrossfadeDrawable(a));
|
||||||
} else if (user.self && SponsorHelper.isSponsor(user.id)) {
|
} else if (SponsorHelper.isKnownSponsor(user.id)) {
|
||||||
rightIconIsSponsor = true;
|
rightIconIsSponsor = true;
|
||||||
nameTextView[a].setRightDrawable2(getFoxSponsorDrawable(a));
|
nameTextView[a].setRightDrawable2(getFoxSponsorDrawable(a));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,11 @@ import java.io.InputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the "sponsor" status of the current Telegram user against the
|
* Resolves the "sponsor" status of the current Telegram user against the
|
||||||
|
|
@ -43,6 +47,16 @@ public final class SponsorHelper {
|
||||||
|
|
||||||
private static volatile boolean checking = false;
|
private static volatile boolean checking = false;
|
||||||
|
|
||||||
|
// ─── Per-user sponsor cache (for showing badges of OTHER users) ───
|
||||||
|
// Cached sponsor flag per Telegram id, with a freshness timestamp.
|
||||||
|
private static final ConcurrentHashMap<Long, Boolean> otherSponsor = new ConcurrentHashMap<>();
|
||||||
|
private static final ConcurrentHashMap<Long, Long> otherChecked = new ConcurrentHashMap<>();
|
||||||
|
// Ids waiting to be resolved in the next batch request.
|
||||||
|
private static final Set<Long> pendingLookup = new LinkedHashSet<>();
|
||||||
|
private static volatile boolean lookupScheduled = false;
|
||||||
|
// Re-check another user's status at most once every 6 hours.
|
||||||
|
private static final long OTHER_TTL_MS = 6 * 60 * 60 * 1000L;
|
||||||
|
|
||||||
private static SharedPreferences prefs() {
|
private static SharedPreferences prefs() {
|
||||||
return ApplicationLoader.applicationContext.getSharedPreferences(PREFS, android.content.Context.MODE_PRIVATE);
|
return ApplicationLoader.applicationContext.getSharedPreferences(PREFS, android.content.Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +75,144 @@ public final class SponsorHelper {
|
||||||
return user != null && isSponsor(user.id);
|
return user != null && isSponsor(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sponsor flag for ANY Telegram user id (not just the current account).
|
||||||
|
* Combines the locally-stored value for the current account with the
|
||||||
|
* per-user cache populated by {@link #requestSponsorStatus(long)}. Returns
|
||||||
|
* the best known answer synchronously; callers should also invoke
|
||||||
|
* {@link #requestSponsorStatus(long)} to keep the cache warm and get a
|
||||||
|
* {@link NotificationCenter#sponsorStatusUpdated} when it changes.
|
||||||
|
*/
|
||||||
|
public static boolean isKnownSponsor(long telegramId) {
|
||||||
|
if (telegramId == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The current account's own status is authoritative from /mobile/me.
|
||||||
|
var self = UserConfig.getInstance(UserConfig.selectedAccount).getCurrentUser();
|
||||||
|
if (self != null && self.id == telegramId) {
|
||||||
|
return isSponsor(telegramId);
|
||||||
|
}
|
||||||
|
Boolean cached = otherSponsor.get(telegramId);
|
||||||
|
return cached != null && cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask the backend whether {@code telegramId} is a sponsor. Self-throttles
|
||||||
|
* per id and batches concurrent requests. Posts
|
||||||
|
* {@link NotificationCenter#sponsorStatusUpdated} when a value changes so
|
||||||
|
* the UI can redraw the badge.
|
||||||
|
*/
|
||||||
|
public static void requestSponsorStatus(long telegramId) {
|
||||||
|
if (telegramId == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var self = UserConfig.getInstance(UserConfig.selectedAccount).getCurrentUser();
|
||||||
|
if (self != null && self.id == telegramId) {
|
||||||
|
return; // covered by refresh()/mobile-me
|
||||||
|
}
|
||||||
|
Long last = otherChecked.get(telegramId);
|
||||||
|
if (last != null && Math.abs(System.currentTimeMillis() - last) < OTHER_TTL_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (pendingLookup) {
|
||||||
|
pendingLookup.add(telegramId);
|
||||||
|
if (lookupScheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lookupScheduled = true;
|
||||||
|
}
|
||||||
|
// Small delay so several visible rows coalesce into one request.
|
||||||
|
AndroidUtilities.runOnUIThread(() -> Utilities.globalQueue.postRunnable(SponsorHelper::flushLookup), 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @WorkerThread
|
||||||
|
private static void flushLookup() {
|
||||||
|
List<Long> batch;
|
||||||
|
synchronized (pendingLookup) {
|
||||||
|
lookupScheduled = false;
|
||||||
|
if (pendingLookup.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
batch = new ArrayList<>(pendingLookup);
|
||||||
|
pendingLookup.clear();
|
||||||
|
}
|
||||||
|
if (batch.size() > 200) {
|
||||||
|
batch = batch.subList(0, 200);
|
||||||
|
}
|
||||||
|
StringBuilder ids = new StringBuilder();
|
||||||
|
for (int i = 0; i < batch.size(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
ids.append(',');
|
||||||
|
}
|
||||||
|
ids.append(batch.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpURLConnection connection = null;
|
||||||
|
try {
|
||||||
|
URL url = new URL(BASE_URL + "/api/auth/mobile/sponsors/by-telegram?ids=" + ids);
|
||||||
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setInstanceFollowRedirects(true);
|
||||||
|
connection.setConnectTimeout(15000);
|
||||||
|
connection.setReadTimeout(15000);
|
||||||
|
connection.setRequestProperty("Accept", "application/json");
|
||||||
|
|
||||||
|
int code = connection.getResponseCode();
|
||||||
|
if (code != HttpURLConnection.HTTP_OK) {
|
||||||
|
FileLog.d(TAG + ": sponsors/by-telegram returned HTTP " + code);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
Set<Long> sponsors = new HashSet<>();
|
||||||
|
JSONArray arr = json.optJSONArray("sponsors");
|
||||||
|
if (arr != null) {
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
sponsors.add(arr.optLong(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean changed = false;
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
for (Long id : batch) {
|
||||||
|
boolean isSponsor = sponsors.contains(id);
|
||||||
|
Boolean prev = otherSponsor.put(id, isSponsor);
|
||||||
|
otherChecked.put(id, now);
|
||||||
|
if (prev == null || prev != isSponsor) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
AndroidUtilities.runOnUIThread(() ->
|
||||||
|
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.sponsorStatusUpdated));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
FileLog.e(e);
|
||||||
|
} finally {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
// If more ids queued up while we were busy, schedule another pass.
|
||||||
|
synchronized (pendingLookup) {
|
||||||
|
if (!pendingLookup.isEmpty() && !lookupScheduled) {
|
||||||
|
lookupScheduled = true;
|
||||||
|
Utilities.globalQueue.postRunnable(SponsorHelper::flushLookup, 250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the given Telegram id is linked to a FoxCloud/GhostCloud account.
|
* Whether the given Telegram id is linked to a FoxCloud/GhostCloud account.
|
||||||
* Only meaningful after at least one successful {@link #refresh(boolean)}.
|
* Only meaningful after at least one successful {@link #refresh(boolean)}.
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 295 KiB After Width: | Height: | Size: 211 KiB |