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) {
|
||||
return;
|
||||
}
|
||||
if (userId != 0) {
|
||||
SponsorHelper.requestSponsorStatus(userId);
|
||||
}
|
||||
String onlineTextOverride;
|
||||
int currentConnectionState = getConnectionsManager().getConnectionState();
|
||||
if (currentConnectionState == ConnectionsManager.ConnectionStateWaitingForNetwork) {
|
||||
|
|
@ -11568,7 +11571,7 @@ 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)) {
|
||||
} else if (SponsorHelper.isKnownSponsor(user.id)) {
|
||||
rightIconIsSponsor = true;
|
||||
nameTextView[a].setRightDrawable2(getFoxSponsorDrawable(a));
|
||||
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));
|
||||
} else if (user.verified) {
|
||||
nameTextView[a].setRightDrawable2(getVerifiedCrossfadeDrawable(a));
|
||||
} else if (user.self && SponsorHelper.isSponsor(user.id)) {
|
||||
} else if (SponsorHelper.isKnownSponsor(user.id)) {
|
||||
rightIconIsSponsor = true;
|
||||
nameTextView[a].setRightDrawable2(getFoxSponsorDrawable(a));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ import java.io.InputStream;
|
|||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// ─── 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() {
|
||||
return ApplicationLoader.applicationContext.getSharedPreferences(PREFS, android.content.Context.MODE_PRIVATE);
|
||||
}
|
||||
|
|
@ -61,6 +75,144 @@ public final class SponsorHelper {
|
|||
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.
|
||||
* 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 |