Chats list: show balance & subscription for Telegram-linked accounts
- SponsorHelper now caches balance, expire_at, plan and telegram_linked from /mobile/me (keyed by Telegram id) - New FoxAccountInfoCell: balance on the left, subscription days left on the right, shown as the first row above 'Archived chats' - Only visible when the Telegram account is linked to GhostCloud/FoxCloud - DialogsActivity refreshes on resume and updates the row on change
This commit is contained in:
parent
2fc29ea46d
commit
b4c3d9fbd6
6 changed files with 233 additions and 8 deletions
|
|
@ -56,6 +56,7 @@ import org.telegram.ui.Cells.DialogMeUrlCell;
|
|||
import org.telegram.ui.Cells.DialogsEmptyCell;
|
||||
import org.telegram.ui.Cells.DialogsHintCell;
|
||||
import org.telegram.ui.Cells.DialogsRequestedEmptyCell;
|
||||
import org.telegram.ui.Cells.FoxAccountInfoCell;
|
||||
import org.telegram.ui.Cells.GraySectionCell;
|
||||
import org.telegram.ui.Cells.HeaderCell;
|
||||
import org.telegram.ui.Cells.ProfileSearchCell;
|
||||
|
|
@ -82,6 +83,8 @@ import java.util.Collections;
|
|||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
import tw.nekomimi.nekogram.helpers.SponsorHelper;
|
||||
|
||||
public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements DialogCell.DialogCellDelegate {
|
||||
public final static int VIEW_TYPE_DIALOG = 0,
|
||||
VIEW_TYPE_FLICKER = 1,
|
||||
|
|
@ -104,7 +107,8 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements
|
|||
VIEW_TYPE_STORIES = 18,
|
||||
VIEW_TYPE_ARCHIVE_FULLSCREEN = 19,
|
||||
VIEW_TYPE_GRAY_SECTION = 20,
|
||||
VIEW_TYPE_FORWARD_TO_STORIES_CELL = 21;
|
||||
VIEW_TYPE_FORWARD_TO_STORIES_CELL = 21,
|
||||
VIEW_TYPE_FOX_ACCOUNT_INFO = 22;
|
||||
|
||||
private Context mContext;
|
||||
private ArchiveHintCell archiveHintCell;
|
||||
|
|
@ -172,6 +176,18 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements
|
|||
isReordering = reordering;
|
||||
}
|
||||
|
||||
private boolean shouldShowFoxAccountInfo() {
|
||||
return dialogsType == DialogsActivity.DIALOGS_TYPE_DEFAULT
|
||||
&& folderId == 0
|
||||
&& !isOnlySelect
|
||||
&& !isTransitionSupport
|
||||
&& !collapsedView
|
||||
&& requestPeerType == null
|
||||
&& (parentFragment == null || !parentFragment.isReplyTo)
|
||||
&& getCurrentFilter() == null
|
||||
&& SponsorHelper.isCurrentUserLinked();
|
||||
}
|
||||
|
||||
public int fixPosition(int position) {
|
||||
if (hasChatlistHint) {
|
||||
position--;
|
||||
|
|
@ -328,6 +344,8 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements
|
|||
this.emptyType = viewTypeEmpty;
|
||||
if (viewTypeEmpty == VIEW_TYPE_LAST_EMPTY) {
|
||||
stableId = 1;
|
||||
} else if (viewTypeEmpty == VIEW_TYPE_FOX_ACCOUNT_INFO) {
|
||||
stableId = 7;
|
||||
} else {
|
||||
if (viewType == VIEW_TYPE_ARCHIVE_FULLSCREEN) {
|
||||
stableId = 5;
|
||||
|
|
@ -574,7 +592,7 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements
|
|||
return viewType != VIEW_TYPE_FLICKER && viewType != VIEW_TYPE_EMPTY && viewType != VIEW_TYPE_DIVIDER &&
|
||||
viewType != VIEW_TYPE_SHADOW && viewType != VIEW_TYPE_HEADER &&
|
||||
viewType != VIEW_TYPE_LAST_EMPTY && viewType != VIEW_TYPE_NEW_CHAT_HINT && viewType != VIEW_TYPE_CONTACTS_FLICKER &&
|
||||
viewType != VIEW_TYPE_REQUIREMENTS && viewType != VIEW_TYPE_REQUIRED_EMPTY && viewType != VIEW_TYPE_STORIES && viewType != VIEW_TYPE_ARCHIVE_FULLSCREEN && viewType != VIEW_TYPE_GRAY_SECTION;
|
||||
viewType != VIEW_TYPE_REQUIREMENTS && viewType != VIEW_TYPE_REQUIRED_EMPTY && viewType != VIEW_TYPE_STORIES && viewType != VIEW_TYPE_ARCHIVE_FULLSCREEN && viewType != VIEW_TYPE_GRAY_SECTION && viewType != VIEW_TYPE_FOX_ACCOUNT_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -765,6 +783,9 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements
|
|||
case VIEW_TYPE_FOLDER_UPDATE_HINT:
|
||||
view = new DialogsHintCell(mContext);
|
||||
break;
|
||||
case VIEW_TYPE_FOX_ACCOUNT_INFO:
|
||||
view = new FoxAccountInfoCell(mContext);
|
||||
break;
|
||||
case VIEW_TYPE_STORIES: {
|
||||
view = new View(mContext) {
|
||||
@Override
|
||||
|
|
@ -1083,6 +1104,10 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements
|
|||
}
|
||||
break;
|
||||
}
|
||||
case VIEW_TYPE_FOX_ACCOUNT_INFO: {
|
||||
((FoxAccountInfoCell) holder.itemView).update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= dialogsCount + 1) {
|
||||
holder.itemView.setAlpha(1f);
|
||||
|
|
@ -1475,6 +1500,10 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements
|
|||
return;
|
||||
}
|
||||
|
||||
if (shouldShowFoxAccountInfo()) {
|
||||
itemInternals.add(new ItemInternal(VIEW_TYPE_FOX_ACCOUNT_INFO));
|
||||
}
|
||||
|
||||
if (!hasHints && dialogsType == 0 && folderId == 0 && messagesController.isDialogsEndReached(folderId) && !forceUpdatingContacts) {
|
||||
if (messagesController.getAllFoldersDialogsCount() <= 10 && ContactsController.getInstance(currentAccount).doneLoadingContacts && !ContactsController.getInstance(currentAccount).contacts.isEmpty()) {
|
||||
onlineContacts = new ArrayList<>(ContactsController.getInstance(currentAccount).contacts);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
package org.telegram.ui.Cells;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.UserConfig;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.Components.LayoutHelper;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import tw.nekomimi.nekogram.helpers.SponsorHelper;
|
||||
|
||||
/**
|
||||
* A slim, non-clickable info row shown at the very top of the chats list for
|
||||
* users who linked their Telegram account to GhostCloud/FoxCloud. Shows the
|
||||
* account balance on the left and the subscription expiry on the right.
|
||||
*/
|
||||
public class FoxAccountInfoCell extends FrameLayout {
|
||||
|
||||
private final TextView balanceTextView;
|
||||
private final TextView subTextView;
|
||||
|
||||
public FoxAccountInfoCell(Context context) {
|
||||
super(context);
|
||||
|
||||
balanceTextView = new TextView(context);
|
||||
balanceTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
|
||||
balanceTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2));
|
||||
balanceTextView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
|
||||
balanceTextView.setSingleLine();
|
||||
addView(balanceTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT,
|
||||
Gravity.LEFT | Gravity.CENTER_VERTICAL, 16, 0, 8, 0));
|
||||
|
||||
subTextView = new TextView(context);
|
||||
subTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
|
||||
subTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2));
|
||||
subTextView.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
|
||||
subTextView.setSingleLine();
|
||||
addView(subTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT,
|
||||
Gravity.RIGHT | Gravity.CENTER_VERTICAL, 8, 0, 16, 0));
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(30), MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
public void update() {
|
||||
var user = UserConfig.getInstance(UserConfig.selectedAccount).getCurrentUser();
|
||||
long tgId = user != null ? user.id : 0;
|
||||
|
||||
long kopecks = SponsorHelper.getBalanceKopecks(tgId);
|
||||
double rubles = kopecks / 100.0;
|
||||
String balanceStr;
|
||||
if (rubles == Math.floor(rubles)) {
|
||||
balanceStr = String.format(Locale.getDefault(), "%,d \u20bd", (long) rubles);
|
||||
} else {
|
||||
balanceStr = String.format(Locale.getDefault(), "%,.2f \u20bd", rubles);
|
||||
}
|
||||
balanceTextView.setText(LocaleController.formatString(R.string.FoxAccountBalance, balanceStr));
|
||||
|
||||
String expireAt = SponsorHelper.getExpireAt(tgId);
|
||||
long expireMs = SponsorHelper.parseExpireMillis(expireAt);
|
||||
if (expireMs <= 0) {
|
||||
subTextView.setText(LocaleController.getString(R.string.FoxAccountSubNone));
|
||||
} else {
|
||||
long now = System.currentTimeMillis();
|
||||
if (expireMs <= now) {
|
||||
subTextView.setText(LocaleController.getString(R.string.FoxAccountSubExpired));
|
||||
} else {
|
||||
long daysLeft = (long) Math.ceil((expireMs - now) / (24.0 * 60 * 60 * 1000));
|
||||
subTextView.setText(LocaleController.formatString(R.string.FoxAccountDaysLeft, (int) daysLeft));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2865,6 +2865,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter.
|
|||
if (!onlySelect) {
|
||||
globalObserversGroup.add(NotificationCenter.closeSearchByActiveAction);
|
||||
globalObserversGroup.add(NotificationCenter.proxySettingsChanged);
|
||||
globalObserversGroup.add(NotificationCenter.sponsorStatusUpdated);
|
||||
observersGroup.add(NotificationCenter.filterSettingsUpdated);
|
||||
observersGroup.add(NotificationCenter.dialogsUnreadCounterChanged);
|
||||
}
|
||||
|
|
@ -6992,6 +6993,9 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter.
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (!onlySelect && folderId == 0) {
|
||||
tw.nekomimi.nekogram.helpers.SponsorHelper.refresh(false);
|
||||
}
|
||||
if (dialogStoriesCell != null) {
|
||||
dialogStoriesCell.onResume();
|
||||
}
|
||||
|
|
@ -10380,6 +10384,16 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter.
|
|||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void didReceivedNotification(int id, int account, Object... args) {
|
||||
if (id == NotificationCenter.sponsorStatusUpdated) {
|
||||
if (viewPages != null) {
|
||||
for (int a = 0; a < viewPages.length; a++) {
|
||||
if (viewPages[a] != null && viewPages[a].dialogsAdapter != null) {
|
||||
viewPages[a].dialogsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (id == NotificationCenter.dialogsNeedReload) {
|
||||
if (viewPages == null || dialogsListFrozen) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,73 @@ public final class SponsorHelper {
|
|||
return user != null && isSponsor(user.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given Telegram id is linked to a FoxCloud/GhostCloud account.
|
||||
* Only meaningful after at least one successful {@link #refresh(boolean)}.
|
||||
*/
|
||||
public static boolean isLinked(long telegramId) {
|
||||
if (telegramId == 0) {
|
||||
return false;
|
||||
}
|
||||
return prefs().getBoolean("linked_" + telegramId, false);
|
||||
}
|
||||
|
||||
public static boolean isCurrentUserLinked() {
|
||||
var user = UserConfig.getInstance(UserConfig.selectedAccount).getCurrentUser();
|
||||
return user != null && isLinked(user.id);
|
||||
}
|
||||
|
||||
/** Cached account balance in kopecks (1/100 of a ruble). */
|
||||
public static long getBalanceKopecks(long telegramId) {
|
||||
if (telegramId == 0) {
|
||||
return 0;
|
||||
}
|
||||
return prefs().getLong("balance_" + telegramId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached subscription expiry, as the raw string returned by the backend
|
||||
* ({@code "yyyy-MM-dd HH:mm:ss"}), or empty when there is no subscription.
|
||||
*/
|
||||
public static String getExpireAt(long telegramId) {
|
||||
if (telegramId == 0) {
|
||||
return "";
|
||||
}
|
||||
return prefs().getString("expire_" + telegramId, "");
|
||||
}
|
||||
|
||||
/** Cached plan name, or empty. */
|
||||
public static String getPlan(long telegramId) {
|
||||
if (telegramId == 0) {
|
||||
return "";
|
||||
}
|
||||
return prefs().getString("plan_" + telegramId, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the backend expiry string ({@code "yyyy-MM-dd HH:mm:ss"}, treated as
|
||||
* UTC) into epoch millis, or 0 if absent/unparseable.
|
||||
*/
|
||||
public static long parseExpireMillis(String expireAt) {
|
||||
if (expireAt == null || expireAt.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
String s = expireAt.trim();
|
||||
String[] patterns = {"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd"};
|
||||
for (String pattern : patterns) {
|
||||
try {
|
||||
java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(pattern, java.util.Locale.US);
|
||||
fmt.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
|
||||
java.util.Date d = fmt.parse(s);
|
||||
if (d != null) {
|
||||
return d.getTime();
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kick off a background refresh of the sponsor status for the current
|
||||
* account. Safe to call repeatedly: it self-throttles via {@link #TTL_MS}
|
||||
|
|
@ -124,7 +191,12 @@ public final class SponsorHelper {
|
|||
return;
|
||||
}
|
||||
boolean sponsor = json.optBoolean("is_sponsor", false);
|
||||
store(telegramId, sponsor);
|
||||
boolean linked = json.optBoolean("telegram_linked", false);
|
||||
// balance is sent in kopecks; the client divides by 100 for display.
|
||||
long balance = json.optLong("balance", 0);
|
||||
String expireAt = json.isNull("expire_at") ? "" : json.optString("expire_at", "");
|
||||
String plan = json.isNull("plan") ? "" : json.optString("plan", "");
|
||||
store(telegramId, sponsor, linked, balance, expireAt, plan);
|
||||
} catch (Exception e) {
|
||||
FileLog.e(e);
|
||||
} finally {
|
||||
|
|
@ -134,13 +206,29 @@ public final class SponsorHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private static void store(long telegramId, boolean sponsor) {
|
||||
boolean previous = prefs().getBoolean("sponsor_" + telegramId, false);
|
||||
prefs().edit()
|
||||
private static void store(long telegramId, boolean sponsor, boolean linked, long balance, String expireAt, String plan) {
|
||||
SharedPreferences p = prefs();
|
||||
boolean previousSponsor = p.getBoolean("sponsor_" + telegramId, false);
|
||||
boolean previousLinked = p.getBoolean("linked_" + telegramId, false);
|
||||
long previousBalance = p.getLong("balance_" + telegramId, 0);
|
||||
String previousExpire = p.getString("expire_" + telegramId, "");
|
||||
String previousPlan = p.getString("plan_" + telegramId, "");
|
||||
|
||||
p.edit()
|
||||
.putBoolean("sponsor_" + telegramId, sponsor)
|
||||
.putBoolean("linked_" + telegramId, linked)
|
||||
.putLong("balance_" + telegramId, balance)
|
||||
.putString("expire_" + telegramId, expireAt != null ? expireAt : "")
|
||||
.putString("plan_" + telegramId, plan != null ? plan : "")
|
||||
.putLong("checked_" + telegramId, System.currentTimeMillis())
|
||||
.apply();
|
||||
if (previous != sponsor) {
|
||||
|
||||
boolean changed = previousSponsor != sponsor
|
||||
|| previousLinked != linked
|
||||
|| previousBalance != balance
|
||||
|| !previousExpire.equals(expireAt != null ? expireAt : "")
|
||||
|| !previousPlan.equals(plan != null ? plan : "");
|
||||
if (changed) {
|
||||
AndroidUtilities.runOnUIThread(() ->
|
||||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.sponsorStatusUpdated));
|
||||
}
|
||||
|
|
@ -149,7 +237,6 @@ public final class SponsorHelper {
|
|||
// ─────────────────────────────────────────────────────────────
|
||||
// Top sponsors leaderboard
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
public static class Sponsor {
|
||||
public int rank;
|
||||
public String name;
|
||||
|
|
|
|||
|
|
@ -331,4 +331,9 @@
|
|||
<string name="FoxTopSponsorsAbout">Те, кто поддерживает проект</string>
|
||||
<string name="FoxTopSponsorsEmpty">Пока нет спонсоров</string>
|
||||
<string name="FoxTopSponsorsError">Не удалось загрузить список</string>
|
||||
<string name="FoxAccountBalance">Баланс: %1$s</string>
|
||||
<string name="FoxAccountSubActive">Подписка: %1$s</string>
|
||||
<string name="FoxAccountSubExpired">Подписка истекла</string>
|
||||
<string name="FoxAccountSubNone">Нет подписки</string>
|
||||
<string name="FoxAccountDaysLeft">осталось %1$d дн.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -157,6 +157,11 @@
|
|||
<string name="FoxTopSponsorsAbout">People who support the project</string>
|
||||
<string name="FoxTopSponsorsEmpty">No sponsors yet</string>
|
||||
<string name="FoxTopSponsorsError">Failed to load sponsors</string>
|
||||
<string name="FoxAccountBalance">Balance: %1$s</string>
|
||||
<string name="FoxAccountSubActive">Subscription: %1$s</string>
|
||||
<string name="FoxAccountSubExpired">Subscription expired</string>
|
||||
<string name="FoxAccountSubNone">No subscription</string>
|
||||
<string name="FoxAccountDaysLeft">%1$d d. left</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