feat(mmp): 领取进度/过期状态,并展示红包与领取人ID
状态优先显示已全部领取或已领取N/M,未领完才附加已过期;列表与最高最低补全 activityId、receiverId。
This commit is contained in:
@@ -7,6 +7,7 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -157,42 +158,52 @@ public class MmpClaimActivity extends MiracleGardenActivity<ActivityMmpClaimBind
|
|||||||
holder.setText(R.id.tv_sum, formatMoney(packet.sumClaimed > 0
|
holder.setText(R.id.tv_sum, formatMoney(packet.sumClaimed > 0
|
||||||
? packet.sumClaimed : parseDouble(packet.total)));
|
? packet.sumClaimed : parseDouble(packet.total)));
|
||||||
|
|
||||||
|
TextView idView = holder.getView(R.id.tv_id);
|
||||||
|
if (!TextUtils.isEmpty(packet.packetId)) {
|
||||||
|
idView.setVisibility(View.VISIBLE);
|
||||||
|
idView.setText("ID " + packet.packetId);
|
||||||
|
} else {
|
||||||
|
idView.setVisibility(View.GONE);
|
||||||
|
idView.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
android.widget.TextView status = holder.getView(R.id.tv_status);
|
android.widget.TextView status = holder.getView(R.id.tv_status);
|
||||||
if (packet.finished) {
|
status.setText(packet.statusLabel());
|
||||||
status.setText("已领完");
|
if (packet.isFullyClaimed()) {
|
||||||
status.setTextColor(Color.parseColor("#047857"));
|
status.setTextColor(Color.parseColor("#047857"));
|
||||||
status.setBackgroundColor(Color.parseColor("#ECFDF5"));
|
status.setBackgroundColor(Color.parseColor("#ECFDF5"));
|
||||||
|
} else if (packet.isExpired()) {
|
||||||
|
status.setTextColor(Color.parseColor("#6B7280"));
|
||||||
|
status.setBackgroundColor(Color.parseColor("#F3F4F6"));
|
||||||
} else {
|
} else {
|
||||||
status.setText("领取中");
|
|
||||||
status.setTextColor(Color.parseColor("#B45309"));
|
status.setTextColor(Color.parseColor("#B45309"));
|
||||||
status.setBackgroundColor(Color.parseColor("#FFFBEB"));
|
status.setBackgroundColor(Color.parseColor("#FFFBEB"));
|
||||||
}
|
}
|
||||||
|
|
||||||
String meta = packet.claimantCount + " 人";
|
String meta;
|
||||||
|
if (packet.effectiveTotalCount() > 0) {
|
||||||
|
meta = packet.effectiveClaimedCount() + "/" + packet.effectiveTotalCount() + " 人";
|
||||||
|
} else {
|
||||||
|
meta = packet.claimantCount + " 人";
|
||||||
|
}
|
||||||
if (packet.issuedAt != null && !packet.issuedAt.isEmpty()) {
|
if (packet.issuedAt != null && !packet.issuedAt.isEmpty()) {
|
||||||
meta += " · 发放 " + packet.issuedAt;
|
meta += " · 发放 " + packet.issuedAt;
|
||||||
}
|
}
|
||||||
|
if (packet.expiresAt != null && !packet.expiresAt.isEmpty()) {
|
||||||
|
meta += " · 到期 " + packet.expiresAt;
|
||||||
|
}
|
||||||
holder.setText(R.id.tv_meta, meta);
|
holder.setText(R.id.tv_meta, meta);
|
||||||
|
|
||||||
String hi = packet.finished ? "最佳" : "最高";
|
String hi = packet.finished ? "最佳" : "最高";
|
||||||
String lo = packet.finished ? "最差" : "最低";
|
String lo = packet.finished ? "最差" : "最低";
|
||||||
com.miraclegarden.smsmessage.mmp.MmpClaim best = packet.bestClaim();
|
com.miraclegarden.smsmessage.mmp.MmpClaim best = packet.bestClaim();
|
||||||
com.miraclegarden.smsmessage.mmp.MmpClaim worst = packet.worstClaim();
|
com.miraclegarden.smsmessage.mmp.MmpClaim worst = packet.worstClaim();
|
||||||
if (best != null) {
|
holder.setText(R.id.tv_best, formatHiLoLine(hi, best));
|
||||||
String amt = best.amountText != null ? best.amountText : formatMoney(best.amount);
|
|
||||||
String nick = best.nickname != null ? best.nickname : "?";
|
|
||||||
holder.setText(R.id.tv_best, hi + " " + nick + " " + amt);
|
|
||||||
} else {
|
|
||||||
holder.setText(R.id.tv_best, hi + " -");
|
|
||||||
}
|
|
||||||
if (worst != null && packet.claimantCount > 1) {
|
if (worst != null && packet.claimantCount > 1) {
|
||||||
String amt = worst.amountText != null ? worst.amountText : formatMoney(worst.amount);
|
holder.setText(R.id.tv_worst, formatHiLoLine(lo, worst));
|
||||||
String nick = worst.nickname != null ? worst.nickname : "?";
|
|
||||||
holder.setText(R.id.tv_worst, lo + " " + nick + " " + amt);
|
|
||||||
} else {
|
} else {
|
||||||
holder.setText(R.id.tv_worst, lo + " -");
|
holder.setText(R.id.tv_worst, lo + " -");
|
||||||
}
|
}
|
||||||
holder.setText(R.id.tv_id, packet.shortId());
|
|
||||||
holder.itemView.setOnClickListener(v -> openDetail(packet.packetId));
|
holder.itemView.setOnClickListener(v -> openDetail(packet.packetId));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -538,6 +549,19 @@ public class MmpClaimActivity extends MiracleGardenActivity<ActivityMmpClaimBind
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String formatHiLoLine(String prefix,
|
||||||
|
com.miraclegarden.smsmessage.mmp.MmpClaim claim) {
|
||||||
|
if (claim == null) {
|
||||||
|
return prefix + " -";
|
||||||
|
}
|
||||||
|
String nick = claim.nickname != null ? claim.nickname : "?";
|
||||||
|
String amt = claim.amountText != null ? claim.amountText : formatMoney(claim.amount);
|
||||||
|
if (!TextUtils.isEmpty(claim.userId)) {
|
||||||
|
return prefix + " " + nick + " " + amt + "\nID " + claim.userId;
|
||||||
|
}
|
||||||
|
return prefix + " " + nick + " " + amt;
|
||||||
|
}
|
||||||
|
|
||||||
private void confirmClear() {
|
private void confirmClear() {
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle("清空红包台")
|
.setTitle("清空红包台")
|
||||||
|
|||||||
@@ -156,17 +156,16 @@ public class MmpDetailActivity extends MiracleGardenActivity<ActivityMmpDetailBi
|
|||||||
}
|
}
|
||||||
sub.append("发放 ").append(packet.issuedAt);
|
sub.append("发放 ").append(packet.issuedAt);
|
||||||
}
|
}
|
||||||
if (packet.finished) {
|
if (!TextUtils.isEmpty(packet.expiresAt)) {
|
||||||
if (sub.length() > 0) {
|
if (sub.length() > 0) {
|
||||||
sub.append(" · ");
|
sub.append(" · ");
|
||||||
}
|
}
|
||||||
sub.append("已领完");
|
sub.append("到期 ").append(packet.expiresAt);
|
||||||
} else {
|
}
|
||||||
if (sub.length() > 0) {
|
if (sub.length() > 0) {
|
||||||
sub.append(" · ");
|
sub.append(" · ");
|
||||||
}
|
}
|
||||||
sub.append("领取中");
|
sub.append(packet.statusLabel());
|
||||||
}
|
|
||||||
binding.tvSub.setText(sub.toString());
|
binding.tvSub.setText(sub.toString());
|
||||||
|
|
||||||
binding.tvStatPeople.setText(String.valueOf(packet.claimantCount));
|
binding.tvStatPeople.setText(String.valueOf(packet.claimantCount));
|
||||||
@@ -181,7 +180,7 @@ public class MmpDetailActivity extends MiracleGardenActivity<ActivityMmpDetailBi
|
|||||||
MmpClaim worst = packet.worstClaim();
|
MmpClaim worst = packet.worstClaim();
|
||||||
if (best != null) {
|
if (best != null) {
|
||||||
bestAmount = best.amount;
|
bestAmount = best.amount;
|
||||||
binding.tvBestName.setText(best.nickname != null ? best.nickname : "?");
|
binding.tvBestName.setText(formatClaimName(best));
|
||||||
binding.tvBestAmt.setText(best.amountText != null ? best.amountText
|
binding.tvBestAmt.setText(best.amountText != null ? best.amountText
|
||||||
: String.format(Locale.US, "%.2f", best.amount));
|
: String.format(Locale.US, "%.2f", best.amount));
|
||||||
} else {
|
} else {
|
||||||
@@ -191,7 +190,7 @@ public class MmpDetailActivity extends MiracleGardenActivity<ActivityMmpDetailBi
|
|||||||
}
|
}
|
||||||
if (worst != null && packet.claimantCount > 1) {
|
if (worst != null && packet.claimantCount > 1) {
|
||||||
worstAmount = worst.amount;
|
worstAmount = worst.amount;
|
||||||
binding.tvWorstName.setText(worst.nickname != null ? worst.nickname : "?");
|
binding.tvWorstName.setText(formatClaimName(worst));
|
||||||
binding.tvWorstAmt.setText(worst.amountText != null ? worst.amountText
|
binding.tvWorstAmt.setText(worst.amountText != null ? worst.amountText
|
||||||
: String.format(Locale.US, "%.2f", worst.amount));
|
: String.format(Locale.US, "%.2f", worst.amount));
|
||||||
} else {
|
} else {
|
||||||
@@ -213,6 +212,17 @@ public class MmpDetailActivity extends MiracleGardenActivity<ActivityMmpDetailBi
|
|||||||
handler.postDelayed(refreshRunnable, 2000);
|
handler.postDelayed(refreshRunnable, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String formatClaimName(MmpClaim claim) {
|
||||||
|
if (claim == null) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
String nick = claim.nickname != null ? claim.nickname : "?";
|
||||||
|
if (!TextUtils.isEmpty(claim.userId)) {
|
||||||
|
return nick + "\nID " + claim.userId;
|
||||||
|
}
|
||||||
|
return nick;
|
||||||
|
}
|
||||||
|
|
||||||
private static double parseDouble(String s) {
|
private static double parseDouble(String s) {
|
||||||
try {
|
try {
|
||||||
return s == null || s.isEmpty() ? 0 : Double.parseDouble(s);
|
return s == null || s.isEmpty() ? 0 : Double.parseDouble(s);
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ package com.miraclegarden.smsmessage.mmp;
|
|||||||
*/
|
*/
|
||||||
public final class MmpModuleInfo {
|
public final class MmpModuleInfo {
|
||||||
public static final String XPOSED_PACKAGE = "com.miraclegarden.smsmessage.xposed";
|
public static final String XPOSED_PACKAGE = "com.miraclegarden.smsmessage.xposed";
|
||||||
public static final int EXPECTED_VERSION_CODE = 3;
|
public static final int EXPECTED_VERSION_CODE = 5;
|
||||||
public static final String EXPECTED_VERSION_NAME = "1.2.0";
|
public static final String EXPECTED_VERSION_NAME = "1.2.2";
|
||||||
|
|
||||||
private MmpModuleInfo() {
|
private MmpModuleInfo() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,16 @@ public class MmpPacket {
|
|||||||
public String total;
|
public String total;
|
||||||
public String via;
|
public String via;
|
||||||
public String issuedAt;
|
public String issuedAt;
|
||||||
|
public String expiresAt;
|
||||||
|
/** TNG activityStatus:ACTIVE / FINISHED / ENDED / CLOSED / CANCELLED … */
|
||||||
|
public String status;
|
||||||
public String updatedAt;
|
public String updatedAt;
|
||||||
public boolean finished;
|
public boolean finished;
|
||||||
public int snapshots;
|
public int snapshots;
|
||||||
|
/** 已领人数(来自 TNG claimedCount,缺省用名单人数) */
|
||||||
|
public int claimedCount;
|
||||||
|
/** 红包总份数(TNG totalCount) */
|
||||||
|
public int totalCount;
|
||||||
public int claimantCount;
|
public int claimantCount;
|
||||||
public double sumClaimed;
|
public double sumClaimed;
|
||||||
public List<MmpClaim> leaderboard = new ArrayList<>();
|
public List<MmpClaim> leaderboard = new ArrayList<>();
|
||||||
@@ -74,10 +81,12 @@ public class MmpPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String luckSummaryLine() {
|
public String luckSummaryLine() {
|
||||||
|
refreshClosedState();
|
||||||
MmpClaim best = bestClaim();
|
MmpClaim best = bestClaim();
|
||||||
MmpClaim worst = worstClaim();
|
MmpClaim worst = worstClaim();
|
||||||
String hi = finished ? "手气最佳" : "目前最高";
|
boolean closed = finished;
|
||||||
String lo = finished ? "手气最差" : "目前最低";
|
String hi = closed ? "手气最佳" : "目前最高";
|
||||||
|
String lo = closed ? "手气最差" : "目前最低";
|
||||||
if (best == null) {
|
if (best == null) {
|
||||||
return hi + ":- " + lo + ":-";
|
return hi + ":- " + lo + ":-";
|
||||||
}
|
}
|
||||||
@@ -93,6 +102,87 @@ public class MmpPacket {
|
|||||||
return hi + ":" + bn + "(" + ba + ") " + lo + ":" + wn + "(" + wa + ")";
|
return hi + ":" + bn + "(" + ba + ") " + lo + ":" + wn + "(" + wa + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 根据 expire/status/人数 刷新 finished;过期未领完也会标结束 */
|
||||||
|
public void refreshClosedState() {
|
||||||
|
if (isFullyClaimed()) {
|
||||||
|
finished = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (finished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isTerminalStatus(status) || isExpirePassed()) {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
if (isExpirePassed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (status == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String s = status.toUpperCase(Locale.US);
|
||||||
|
return s.contains("EXPIRE") || s.contains("ENDED") || s.contains("CLOSED")
|
||||||
|
|| s.contains("CANCEL");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullyClaimed() {
|
||||||
|
int claimed = effectiveClaimedCount();
|
||||||
|
int total = effectiveTotalCount();
|
||||||
|
return total > 0 && claimed >= total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int effectiveClaimedCount() {
|
||||||
|
return Math.max(claimedCount, claimantCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int effectiveTotalCount() {
|
||||||
|
return Math.max(0, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先展示领取进度,过期放后面:
|
||||||
|
* 已全部领取 / 已领取 N/M 人 · 已过期
|
||||||
|
*/
|
||||||
|
public String statusLabel() {
|
||||||
|
refreshClosedState();
|
||||||
|
String progress;
|
||||||
|
if (isFullyClaimed()) {
|
||||||
|
progress = "已全部领取";
|
||||||
|
} else {
|
||||||
|
int claimed = effectiveClaimedCount();
|
||||||
|
int total = effectiveTotalCount();
|
||||||
|
if (total > 0) {
|
||||||
|
progress = "已领取 " + claimed + "/" + total + " 人";
|
||||||
|
} else if (claimed > 0) {
|
||||||
|
progress = "已领取 " + claimed + " 人";
|
||||||
|
} else {
|
||||||
|
progress = finished ? "未领取" : "领取中";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isExpired() && !isFullyClaimed()) {
|
||||||
|
return progress + " · 已过期";
|
||||||
|
}
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExpirePassed() {
|
||||||
|
long ms = parseIssueMs(expiresAt);
|
||||||
|
return ms > 0 && System.currentTimeMillis() >= ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isTerminalStatus(String status) {
|
||||||
|
if (status == null || status.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String s = status.toUpperCase(Locale.US);
|
||||||
|
return s.contains("FINISH") || s.contains("COMPLETE") || s.contains("EXPIRE")
|
||||||
|
|| s.contains("ENDED") || s.contains("CLOSED") || s.contains("CANCEL")
|
||||||
|
|| s.contains("DONE");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean matchesFilter(String keyword, int statusFilter) {
|
public boolean matchesFilter(String keyword, int statusFilter) {
|
||||||
return matchesFilter(keyword, statusFilter, 0, 0L, 0L);
|
return matchesFilter(keyword, statusFilter, 0, 0L, 0L);
|
||||||
}
|
}
|
||||||
@@ -103,6 +193,7 @@ public class MmpPacket {
|
|||||||
*/
|
*/
|
||||||
public boolean matchesFilter(String keyword, int statusFilter,
|
public boolean matchesFilter(String keyword, int statusFilter,
|
||||||
int timeMode, long customFromMs, long customToMs) {
|
int timeMode, long customFromMs, long customToMs) {
|
||||||
|
refreshClosedState();
|
||||||
if (statusFilter == 1 && finished) {
|
if (statusFilter == 1 && finished) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,14 +96,30 @@ public final class MmpPacketStore {
|
|||||||
incoming.total = nullToEmpty(parsed.meta.get("total"));
|
incoming.total = nullToEmpty(parsed.meta.get("total"));
|
||||||
incoming.via = nullToEmpty(parsed.meta.get("via"));
|
incoming.via = nullToEmpty(parsed.meta.get("via"));
|
||||||
incoming.issuedAt = firstNonEmpty(parsed.meta, "issued", "createTime", "issuedAt", "create");
|
incoming.issuedAt = firstNonEmpty(parsed.meta, "issued", "createTime", "issuedAt", "create");
|
||||||
|
incoming.expiresAt = firstNonEmpty(parsed.meta, "expire", "expireTime", "expiry", "expiresAt");
|
||||||
|
incoming.status = firstNonEmpty(parsed.meta, "status", "activityStatus");
|
||||||
|
parseCountsMeta(incoming, parsed.meta.get("counts"));
|
||||||
|
if (incoming.claimedCount <= 0) {
|
||||||
|
tryParseIntField(incoming, parsed.meta, "claimed", "claimedCount");
|
||||||
|
}
|
||||||
|
if (incoming.totalCount <= 0) {
|
||||||
|
tryParseIntField(incoming, parsed.meta, "slots", "totalCount", "quantity");
|
||||||
|
}
|
||||||
incoming.finished = "1".equals(parsed.meta.get("done"))
|
incoming.finished = "1".equals(parsed.meta.get("done"))
|
||||||
|| "true".equalsIgnoreCase(parsed.meta.get("done"))
|
|| "true".equalsIgnoreCase(parsed.meta.get("done"))
|
||||||
|| "FINISHED".equalsIgnoreCase(parsed.meta.get("status"));
|
|| isTerminalStatusMeta(incoming.status);
|
||||||
|
incoming.refreshClosedState();
|
||||||
incoming.updatedAt = nowText();
|
incoming.updatedAt = nowText();
|
||||||
incoming.snapshots = 1;
|
incoming.snapshots = 1;
|
||||||
List<MmpClaim> ranked = aggregateClaims(parsed.claims);
|
List<MmpClaim> ranked = aggregateClaims(parsed.claims);
|
||||||
incoming.leaderboard = ranked;
|
incoming.leaderboard = ranked;
|
||||||
incoming.claimantCount = ranked.size();
|
incoming.claimantCount = ranked.size();
|
||||||
|
if (incoming.claimedCount <= 0) {
|
||||||
|
incoming.claimedCount = ranked.size();
|
||||||
|
}
|
||||||
|
if (incoming.totalCount > 0 && incoming.claimedCount >= incoming.totalCount) {
|
||||||
|
incoming.finished = true;
|
||||||
|
}
|
||||||
double sum = 0;
|
double sum = 0;
|
||||||
for (MmpClaim c : ranked) {
|
for (MmpClaim c : ranked) {
|
||||||
sum += c.amount;
|
sum += c.amount;
|
||||||
@@ -127,9 +143,23 @@ public final class MmpPacketStore {
|
|||||||
if (TextUtils.isEmpty(incoming.issuedAt) && !TextUtils.isEmpty(existing.issuedAt)) {
|
if (TextUtils.isEmpty(incoming.issuedAt) && !TextUtils.isEmpty(existing.issuedAt)) {
|
||||||
incoming.issuedAt = existing.issuedAt;
|
incoming.issuedAt = existing.issuedAt;
|
||||||
}
|
}
|
||||||
|
if (TextUtils.isEmpty(incoming.expiresAt) && !TextUtils.isEmpty(existing.expiresAt)) {
|
||||||
|
incoming.expiresAt = existing.expiresAt;
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(incoming.status) && !TextUtils.isEmpty(existing.status)) {
|
||||||
|
incoming.status = existing.status;
|
||||||
|
}
|
||||||
|
if (incoming.totalCount <= 0 && existing.totalCount > 0) {
|
||||||
|
incoming.totalCount = existing.totalCount;
|
||||||
|
}
|
||||||
|
if (incoming.claimedCount < existing.claimedCount) {
|
||||||
|
// 保留更大的已领人数
|
||||||
|
incoming.claimedCount = existing.claimedCount;
|
||||||
|
}
|
||||||
if (!incoming.finished && existing.finished) {
|
if (!incoming.finished && existing.finished) {
|
||||||
incoming.finished = true;
|
incoming.finished = true;
|
||||||
}
|
}
|
||||||
|
incoming.refreshClosedState();
|
||||||
map.put(packetId, incoming);
|
map.put(packetId, incoming);
|
||||||
} else {
|
} else {
|
||||||
existing.snapshots = existing.snapshots + 1;
|
existing.snapshots = existing.snapshots + 1;
|
||||||
@@ -193,6 +223,11 @@ public final class MmpPacketStore {
|
|||||||
return nullToEmpty(b.packetId).compareTo(nullToEmpty(a.packetId));
|
return nullToEmpty(b.packetId).compareTo(nullToEmpty(a.packetId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
for (MmpPacket p : list) {
|
||||||
|
if (p != null) {
|
||||||
|
p.refreshClosedState();
|
||||||
|
}
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,7 +248,11 @@ public final class MmpPacketStore {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
synchronized (MmpPacketStore.class) {
|
synchronized (MmpPacketStore.class) {
|
||||||
return loadMap(context).get(packetId);
|
MmpPacket p = loadMap(context).get(packetId);
|
||||||
|
if (p != null) {
|
||||||
|
p.refreshClosedState();
|
||||||
|
}
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,9 +640,13 @@ public final class MmpPacketStore {
|
|||||||
o.put("total", nullToEmpty(p.total));
|
o.put("total", nullToEmpty(p.total));
|
||||||
o.put("via", nullToEmpty(p.via));
|
o.put("via", nullToEmpty(p.via));
|
||||||
o.put("issuedAt", nullToEmpty(p.issuedAt));
|
o.put("issuedAt", nullToEmpty(p.issuedAt));
|
||||||
|
o.put("expiresAt", nullToEmpty(p.expiresAt));
|
||||||
|
o.put("status", nullToEmpty(p.status));
|
||||||
o.put("updatedAt", nullToEmpty(p.updatedAt));
|
o.put("updatedAt", nullToEmpty(p.updatedAt));
|
||||||
o.put("finished", p.finished);
|
o.put("finished", p.finished);
|
||||||
o.put("snapshots", p.snapshots);
|
o.put("snapshots", p.snapshots);
|
||||||
|
o.put("claimedCount", p.claimedCount);
|
||||||
|
o.put("totalCount", p.totalCount);
|
||||||
o.put("claimantCount", p.claimantCount);
|
o.put("claimantCount", p.claimantCount);
|
||||||
o.put("sumClaimed", p.sumClaimed);
|
o.put("sumClaimed", p.sumClaimed);
|
||||||
JSONArray board = new JSONArray();
|
JSONArray board = new JSONArray();
|
||||||
@@ -633,9 +676,16 @@ public final class MmpPacketStore {
|
|||||||
p.total = o.optString("total", "");
|
p.total = o.optString("total", "");
|
||||||
p.via = o.optString("via", "");
|
p.via = o.optString("via", "");
|
||||||
p.issuedAt = o.optString("issuedAt", "");
|
p.issuedAt = o.optString("issuedAt", "");
|
||||||
|
p.expiresAt = o.optString("expiresAt", "");
|
||||||
|
p.status = o.optString("status", "");
|
||||||
p.updatedAt = o.optString("updatedAt", "");
|
p.updatedAt = o.optString("updatedAt", "");
|
||||||
p.finished = o.optBoolean("finished", false);
|
p.finished = o.optBoolean("finished", false);
|
||||||
p.snapshots = o.optInt("snapshots", 1);
|
p.snapshots = o.optInt("snapshots", 1);
|
||||||
|
p.claimedCount = o.optInt("claimedCount", 0);
|
||||||
|
p.totalCount = o.optInt("totalCount", 0);
|
||||||
|
p.claimantCount = o.optInt("claimantCount", 0);
|
||||||
|
p.refreshClosedState();
|
||||||
|
p.snapshots = o.optInt("snapshots", 1);
|
||||||
p.claimantCount = o.optInt("claimantCount", 0);
|
p.claimantCount = o.optInt("claimantCount", 0);
|
||||||
p.sumClaimed = o.optDouble("sumClaimed", 0);
|
p.sumClaimed = o.optDouble("sumClaimed", 0);
|
||||||
JSONArray board = o.optJSONArray("leaderboard");
|
JSONArray board = o.optJSONArray("leaderboard");
|
||||||
@@ -657,6 +707,10 @@ public final class MmpPacketStore {
|
|||||||
if (p.claimantCount <= 0) {
|
if (p.claimantCount <= 0) {
|
||||||
p.claimantCount = p.leaderboard.size();
|
p.claimantCount = p.leaderboard.size();
|
||||||
}
|
}
|
||||||
|
if (p.claimedCount <= 0) {
|
||||||
|
p.claimedCount = p.claimantCount;
|
||||||
|
}
|
||||||
|
p.refreshClosedState();
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,6 +738,66 @@ public final class MmpPacketStore {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** counts=1/5 */
|
||||||
|
private static void parseCountsMeta(MmpPacket packet, String counts) {
|
||||||
|
if (packet == null || TextUtils.isEmpty(counts)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String raw = counts.trim();
|
||||||
|
int slash = raw.indexOf('/');
|
||||||
|
try {
|
||||||
|
if (slash >= 0) {
|
||||||
|
String left = raw.substring(0, slash).trim();
|
||||||
|
String right = raw.substring(slash + 1).trim();
|
||||||
|
if (!left.isEmpty()) {
|
||||||
|
packet.claimedCount = Integer.parseInt(left);
|
||||||
|
}
|
||||||
|
if (!right.isEmpty()) {
|
||||||
|
packet.totalCount = Integer.parseInt(right);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
packet.claimedCount = Integer.parseInt(raw);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void tryParseIntField(MmpPacket packet, Map<String, String> meta,
|
||||||
|
String... keys) {
|
||||||
|
if (packet == null || meta == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String k : keys) {
|
||||||
|
String v = meta.get(k);
|
||||||
|
if (TextUtils.isEmpty(v)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int n = Integer.parseInt(v.trim());
|
||||||
|
if (n < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ("claimed".equals(k) || "claimedCount".equals(k)) {
|
||||||
|
packet.claimedCount = n;
|
||||||
|
} else {
|
||||||
|
packet.totalCount = n;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isTerminalStatusMeta(String status) {
|
||||||
|
if (TextUtils.isEmpty(status)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String s = status.toUpperCase(Locale.US);
|
||||||
|
return s.contains("FINISH") || s.contains("COMPLETE") || s.contains("EXPIRE")
|
||||||
|
|| s.contains("ENDED") || s.contains("CLOSED") || s.contains("CANCEL")
|
||||||
|
|| s.contains("DONE");
|
||||||
|
}
|
||||||
|
|
||||||
private static String nowText() {
|
private static String nowText() {
|
||||||
return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||||
.format(new java.util.Date());
|
.format(new java.util.Date());
|
||||||
|
|||||||
@@ -179,7 +179,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="2"
|
||||||
android:text="-"
|
android:text="-"
|
||||||
android:textColor="#047857"
|
android:textColor="#047857"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="2"
|
||||||
android:text="-"
|
android:text="-"
|
||||||
android:textColor="#B45309"
|
android:textColor="#B45309"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
|
|||||||
@@ -16,11 +16,16 @@
|
|||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tv_title"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="红包"
|
android:text="红包"
|
||||||
@@ -28,6 +33,18 @@
|
|||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_id"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text=""
|
||||||
|
android:textColor="#9CA3AF"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_sum"
|
android:id="@+id/tv_sum"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -71,17 +88,6 @@
|
|||||||
android:text="0 人"
|
android:text="0 人"
|
||||||
android:textColor="#6B7280"
|
android:textColor="#6B7280"
|
||||||
android:textSize="11sp" />
|
android:textSize="11sp" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_id"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:ellipsize="middle"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:text="id"
|
|
||||||
android:textColor="#9CA3AF"
|
|
||||||
android:textSize="10sp" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -97,7 +103,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="#ECFDF5"
|
android:background="#ECFDF5"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="2"
|
||||||
android:paddingStart="6dp"
|
android:paddingStart="6dp"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingEnd="6dp"
|
android:paddingEnd="6dp"
|
||||||
@@ -115,7 +121,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="#FFFBEB"
|
android:background="#FFFBEB"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="2"
|
||||||
android:paddingStart="6dp"
|
android:paddingStart="6dp"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingEnd="6dp"
|
android:paddingEnd="6dp"
|
||||||
|
|||||||
@@ -96,6 +96,7 @@
|
|||||||
.pkt.active { background: #fff7ed; box-shadow: inset 3px 0 0 #ea580c; }
|
.pkt.active { background: #fff7ed; box-shadow: inset 3px 0 0 #ea580c; }
|
||||||
.pkt-row { display: flex; justify-content: space-between; gap: 8px; align-items: center; }
|
.pkt-row { display: flex; justify-content: space-between; gap: 8px; align-items: center; }
|
||||||
.pkt-name { font-size: 13px; font-weight: 700; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.pkt-name { font-size: 13px; font-weight: 700; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.pkt-uid { font-size: 11px; color: #9ca3af; font-weight: 500; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: ui-monospace, Consolas, monospace; }
|
||||||
.pkt-money {
|
.pkt-money {
|
||||||
font-size: 15px; font-weight: 800; color: var(--money);
|
font-size: 15px; font-weight: 800; color: var(--money);
|
||||||
font-variant-numeric: tabular-nums; white-space: nowrap;
|
font-variant-numeric: tabular-nums; white-space: nowrap;
|
||||||
@@ -104,9 +105,11 @@
|
|||||||
.pkt-meta span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.pkt-meta span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.badge {
|
.badge {
|
||||||
display: inline-block; padding: 1px 5px; border-radius: 3px; font-size: 10px; font-weight: 700; flex-shrink: 0;
|
display: inline-block; padding: 1px 5px; border-radius: 3px; font-size: 10px; font-weight: 700; flex-shrink: 0;
|
||||||
|
max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.badge.done { background: var(--best-bg); color: var(--done); }
|
.badge.done { background: var(--best-bg); color: var(--done); }
|
||||||
.badge.open { background: var(--worst-bg); color: var(--open); }
|
.badge.open { background: var(--worst-bg); color: var(--open); }
|
||||||
|
.badge.expired { background: #f3f4f6; color: #6b7280; }
|
||||||
.pkt-luck {
|
.pkt-luck {
|
||||||
margin-top: 4px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
|
margin-top: 4px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
|
||||||
}
|
}
|
||||||
@@ -366,6 +369,14 @@
|
|||||||
}
|
}
|
||||||
function money(v){ const n=Number(v); return Number.isFinite(n)?n.toFixed(2):String(v??'-'); }
|
function money(v){ const n=Number(v); return Number.isFinite(n)?n.toFixed(2):String(v??'-'); }
|
||||||
function shortId(id){ const s=String(id||''); return (s.length>=8&&s.indexOf('-')>0)?s.slice(0,8):(s.slice(0,16)||'-'); }
|
function shortId(id){ const s=String(id||''); return (s.length>=8&&s.indexOf('-')>0)?s.slice(0,8):(s.slice(0,16)||'-'); }
|
||||||
|
function formatHiLo(prefix, row){
|
||||||
|
if(!row) return prefix + ' -';
|
||||||
|
const nick = row.nickname || row.bestNick || '-';
|
||||||
|
const amt = row.amountText || row.bestAmount || (row.amount!=null ? money(row.amount) : '');
|
||||||
|
const uid = row.userId || '';
|
||||||
|
if(uid) return `${prefix} ${esc(nick)} ${esc(amt)}<div class="pkt-uid">ID ${esc(uid)}</div>`;
|
||||||
|
return `${prefix} ${esc(nick)} ${esc(amt)}`.trim();
|
||||||
|
}
|
||||||
function hiLo(p){
|
function hiLo(p){
|
||||||
return {
|
return {
|
||||||
hi: p&&p.finished?'手气最佳':'目前最高',
|
hi: p&&p.finished?'手气最佳':'目前最高',
|
||||||
@@ -407,19 +418,35 @@
|
|||||||
const div=document.createElement('div');
|
const div=document.createElement('div');
|
||||||
div.className='pkt'+(p.packetId===activeId?' active':'');
|
div.className='pkt'+(p.packetId===activeId?' active':'');
|
||||||
div.onclick=()=>{ activeId=p.packetId; renderList(); };
|
div.onclick=()=>{ activeId=p.packetId; renderList(); };
|
||||||
const badge=p.finished?'<span class="badge done">已领完</span>':'<span class="badge open">领取中</span>';
|
const label = p.statusLabel || (p.finished ? '已领完' : '领取中');
|
||||||
const hiShort = p.finished ? '最佳' : '最高';
|
const fully = label.indexOf('已全部领取') >= 0;
|
||||||
const loShort = p.finished ? '最差' : '最低';
|
const expired = label.indexOf('已过期') >= 0;
|
||||||
const bestLine = `${hiShort} ${esc(p.bestNick||'-')} ${esc(p.bestAmount||'')}`.trim();
|
const badgeClass = fully ? 'done' : (expired ? 'expired' : 'open');
|
||||||
|
const badge=`<span class="badge ${badgeClass}">${esc(label)}</span>`;
|
||||||
|
const hiShort = p.finished || fully ? '最佳' : '最高';
|
||||||
|
const loShort = p.finished || fully ? '最差' : '最低';
|
||||||
|
const best = (p.leaderboard && p.leaderboard[0]) || null;
|
||||||
|
const worst = (p.leaderboard && p.leaderboard.length > 1)
|
||||||
|
? p.leaderboard[p.leaderboard.length - 1] : null;
|
||||||
|
const bestLine = formatHiLo(hiShort, best || {nickname:p.bestNick, amountText:p.bestAmount, userId:p.bestUserId});
|
||||||
const worstLine = (p.claimantCount||0)<=1
|
const worstLine = (p.claimantCount||0)<=1
|
||||||
? `${loShort} -`
|
? `${loShort} -`
|
||||||
: `${loShort} ${esc(p.worstNick||'-')} ${esc(p.worstAmount||'')}`.trim();
|
: formatHiLo(loShort, worst || {nickname:p.worstNick, amountText:p.worstAmount, userId:p.worstUserId});
|
||||||
|
const timeLine = p.expiresAt
|
||||||
|
? `${esc(p.issuedAt||'时间未知')} · 到期 ${esc(p.expiresAt)}`
|
||||||
|
: esc(p.issuedAt||'时间未知');
|
||||||
|
const countLine = (p.totalCount > 0)
|
||||||
|
? `${p.claimedCount||p.claimantCount||0}/${p.totalCount}人`
|
||||||
|
: `${p.claimantCount||0}人`;
|
||||||
div.innerHTML=`
|
div.innerHTML=`
|
||||||
<div class="pkt-row">
|
<div class="pkt-row">
|
||||||
|
<div style="min-width:0;flex:1">
|
||||||
<div class="pkt-name">${esc(p.sender||p.title||'红包')}</div>
|
<div class="pkt-name">${esc(p.sender||p.title||'红包')}</div>
|
||||||
|
${p.packetId ? `<div class="pkt-uid">ID ${esc(p.packetId)}</div>` : ''}
|
||||||
|
</div>
|
||||||
<div class="pkt-money">${money(p.sumClaimed??p.total??0)}</div>
|
<div class="pkt-money">${money(p.sumClaimed??p.total??0)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pkt-meta">${badge}<span>${p.claimantCount||0}人 · ${esc(p.issuedAt||'时间未知')}</span><span class="pkt-id">${esc(shortId(p.packetId))}</span></div>
|
<div class="pkt-meta">${badge}<span>${countLine} · ${timeLine}</span></div>
|
||||||
<div class="pkt-luck">
|
<div class="pkt-luck">
|
||||||
<div class="mini best">${bestLine}</div>
|
<div class="mini best">${bestLine}</div>
|
||||||
<div class="mini worst">${worstLine}</div>
|
<div class="mini worst">${worstLine}</div>
|
||||||
@@ -437,21 +464,42 @@
|
|||||||
const parts=[];
|
const parts=[];
|
||||||
if(p.packetId) parts.push('ID '+p.packetId);
|
if(p.packetId) parts.push('ID '+p.packetId);
|
||||||
if(p.issuedAt) parts.push('发放 '+p.issuedAt);
|
if(p.issuedAt) parts.push('发放 '+p.issuedAt);
|
||||||
|
if(p.expiresAt) parts.push('到期 '+p.expiresAt);
|
||||||
if(p.via) parts.push(p.via);
|
if(p.via) parts.push(p.via);
|
||||||
sub.textContent = parts.join(' · ');
|
sub.textContent = parts.join(' · ');
|
||||||
cPeople.textContent = p.claimantCount || 0;
|
cPeople.textContent = p.claimantCount || 0;
|
||||||
cSum.textContent = money(p.sumClaimed ?? 0);
|
cSum.textContent = money(p.sumClaimed ?? 0);
|
||||||
cTotal.textContent = p.total || '-';
|
cTotal.textContent = p.total || '-';
|
||||||
cStatus.innerHTML = p.finished ? '<span class="badge done">已领完</span>' : '<span class="badge open">领取中</span>';
|
cStatus.innerHTML = (()=>{
|
||||||
|
const label = p.statusLabel || (p.finished ? '已领完' : '领取中');
|
||||||
|
const fully = label.indexOf('已全部领取') >= 0;
|
||||||
|
const expired = label.indexOf('已过期') >= 0;
|
||||||
|
const cls = fully ? 'done' : (expired ? 'expired' : 'open');
|
||||||
|
return `<span class="badge ${cls}">${esc(label)}</span>`;
|
||||||
|
})();
|
||||||
luckBestLab.textContent = labels.hi;
|
luckBestLab.textContent = labels.hi;
|
||||||
luckWorstLab.textContent = labels.lo;
|
luckWorstLab.textContent = labels.lo;
|
||||||
|
const best = (p.leaderboard && p.leaderboard[0]) || null;
|
||||||
|
const worst = (p.leaderboard && p.leaderboard.length > 1)
|
||||||
|
? p.leaderboard[p.leaderboard.length - 1] : null;
|
||||||
|
if(best){
|
||||||
|
luckBestName.innerHTML = best.userId
|
||||||
|
? `${esc(best.nickname||'-')}<div class="pkt-uid">ID ${esc(best.userId)}</div>`
|
||||||
|
: esc(best.nickname||p.bestNick||'-');
|
||||||
|
luckBestAmt.textContent = best.amountText ? ('RM ' + best.amountText)
|
||||||
|
: (p.bestAmount ? ('RM ' + p.bestAmount) : '-');
|
||||||
|
} else {
|
||||||
luckBestName.textContent = p.bestNick || '-';
|
luckBestName.textContent = p.bestNick || '-';
|
||||||
luckBestAmt.textContent = p.bestAmount ? ('RM ' + p.bestAmount) : '-';
|
luckBestAmt.textContent = p.bestAmount ? ('RM ' + p.bestAmount) : '-';
|
||||||
if((p.claimantCount||0)<=1){
|
}
|
||||||
luckWorstName.textContent='-'; luckWorstAmt.textContent='-';
|
if(worst && (p.claimantCount||0)>1){
|
||||||
|
luckWorstName.innerHTML = worst.userId
|
||||||
|
? `${esc(worst.nickname||'-')}<div class="pkt-uid">ID ${esc(worst.userId)}</div>`
|
||||||
|
: esc(worst.nickname||p.worstNick||'-');
|
||||||
|
luckWorstAmt.textContent = worst.amountText ? ('RM ' + worst.amountText)
|
||||||
|
: (p.worstAmount ? ('RM ' + p.worstAmount) : '-');
|
||||||
} else {
|
} else {
|
||||||
luckWorstName.textContent = p.worstNick || '-';
|
luckWorstName.textContent='-'; luckWorstAmt.textContent='-';
|
||||||
luckWorstAmt.textContent = p.worstAmount ? ('RM ' + p.worstAmount) : '-';
|
|
||||||
}
|
}
|
||||||
const rows=p.leaderboard||[];
|
const rows=p.leaderboard||[];
|
||||||
board.innerHTML='';
|
board.innerHTML='';
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ PACKETS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mmp_pac
|
|||||||
HOOK_STATUS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mmp_hook_status.json")
|
HOOK_STATUS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mmp_hook_status.json")
|
||||||
|
|
||||||
# 与 xposed-module / App MmpModuleInfo 同步
|
# 与 xposed-module / App MmpModuleInfo 同步
|
||||||
EXPECTED_MODULE_CODE = 3
|
EXPECTED_MODULE_CODE = 5
|
||||||
EXPECTED_MODULE_NAME = "1.2.0"
|
EXPECTED_MODULE_NAME = "1.2.2"
|
||||||
|
|
||||||
_messages = deque(maxlen=MAX_MESSAGES)
|
_messages = deque(maxlen=MAX_MESSAGES)
|
||||||
_mmp_messages = deque(maxlen=MAX_MESSAGES)
|
_mmp_messages = deque(maxlen=MAX_MESSAGES)
|
||||||
@@ -629,6 +629,9 @@ def _normalize_synced_packet(raw):
|
|||||||
elif sum_claimed:
|
elif sum_claimed:
|
||||||
total = "{0:.2f}".format(sum_claimed)
|
total = "{0:.2f}".format(sum_claimed)
|
||||||
issued = raw.get("issuedAt") or raw.get("issued") or ""
|
issued = raw.get("issuedAt") or raw.get("issued") or ""
|
||||||
|
expires = raw.get("expiresAt") or raw.get("expire") or raw.get("expireTime") or ""
|
||||||
|
status = raw.get("status") or raw.get("activityStatus") or ""
|
||||||
|
finished = bool(raw.get("finished")) or _is_terminal_status(status) or _is_expire_passed(expires)
|
||||||
return {
|
return {
|
||||||
"packetId": pid,
|
"packetId": pid,
|
||||||
"title": raw.get("title") or "TNG 红包",
|
"title": raw.get("title") or "TNG 红包",
|
||||||
@@ -637,11 +640,15 @@ def _normalize_synced_packet(raw):
|
|||||||
"total": total,
|
"total": total,
|
||||||
"via": raw.get("via") or "phone-sync",
|
"via": raw.get("via") or "phone-sync",
|
||||||
"issuedAt": issued,
|
"issuedAt": issued,
|
||||||
|
"expiresAt": expires,
|
||||||
|
"status": status,
|
||||||
"updatedAt": raw.get("updatedAt") or "",
|
"updatedAt": raw.get("updatedAt") or "",
|
||||||
"fetchedAt": raw.get("updatedAt") or raw.get("fetchedAt") or _now_iso(),
|
"fetchedAt": raw.get("updatedAt") or raw.get("fetchedAt") or _now_iso(),
|
||||||
"latestAt": issued or raw.get("updatedAt") or "",
|
"latestAt": issued or raw.get("updatedAt") or "",
|
||||||
"finished": bool(raw.get("finished")),
|
"finished": finished,
|
||||||
"snapshots": int(raw.get("snapshots") or 1),
|
"snapshots": int(raw.get("snapshots") or 1),
|
||||||
|
"claimedCount": int(raw.get("claimedCount") or 0),
|
||||||
|
"totalCount": int(raw.get("totalCount") or 0),
|
||||||
"leaderboard": norm_board,
|
"leaderboard": norm_board,
|
||||||
"claimantCount": len(norm_board) if norm_board else int(raw.get("claimantCount") or 0),
|
"claimantCount": len(norm_board) if norm_board else int(raw.get("claimantCount") or 0),
|
||||||
"sumClaimed": sum_claimed,
|
"sumClaimed": sum_claimed,
|
||||||
@@ -741,7 +748,8 @@ def _mmp_packets():
|
|||||||
key = str(packet_id)
|
key = str(packet_id)
|
||||||
issued = meta.get("issued") or ""
|
issued = meta.get("issued") or ""
|
||||||
finished = str(meta.get("done") or "").lower() in ("1", "true") \
|
finished = str(meta.get("done") or "").lower() in ("1", "true") \
|
||||||
or str(meta.get("status") or "").upper() in ("FINISHED", "COMPLETE", "COMPLETED", "EXPIRED")
|
or _is_terminal_status(meta.get("status") or "") \
|
||||||
|
or _is_expire_passed(meta.get("expire") or meta.get("expireTime") or "")
|
||||||
item = {
|
item = {
|
||||||
"packetId": key,
|
"packetId": key,
|
||||||
"title": msg.get("title") or "TNG 红包",
|
"title": msg.get("title") or "TNG 红包",
|
||||||
@@ -750,12 +758,15 @@ def _mmp_packets():
|
|||||||
"total": meta.get("total") or "",
|
"total": meta.get("total") or "",
|
||||||
"via": meta.get("via") or "",
|
"via": meta.get("via") or "",
|
||||||
"issuedAt": issued,
|
"issuedAt": issued,
|
||||||
|
"expiresAt": meta.get("expire") or meta.get("expireTime") or "",
|
||||||
|
"status": meta.get("status") or "",
|
||||||
"fetchedAt": msg.get("receivedAt") or "",
|
"fetchedAt": msg.get("receivedAt") or "",
|
||||||
"latestAt": issued or (msg.get("receivedAt") or ""),
|
"latestAt": issued or (msg.get("receivedAt") or ""),
|
||||||
"finished": finished,
|
"finished": finished,
|
||||||
"snapshots": 1,
|
"snapshots": 1,
|
||||||
"claims": claims,
|
"claims": claims,
|
||||||
}
|
}
|
||||||
|
_apply_counts_from_meta(item, meta)
|
||||||
existing = packets.get(key)
|
existing = packets.get(key)
|
||||||
def _score(it):
|
def _score(it):
|
||||||
return (
|
return (
|
||||||
@@ -801,6 +812,10 @@ def _mmp_packets():
|
|||||||
p["bestAmount"] = (best or {}).get("amountText") or ""
|
p["bestAmount"] = (best or {}).get("amountText") or ""
|
||||||
p["worstNick"] = (worst or {}).get("nickname") or ""
|
p["worstNick"] = (worst or {}).get("nickname") or ""
|
||||||
p["worstAmount"] = (worst or {}).get("amountText") or ""
|
p["worstAmount"] = (worst or {}).get("amountText") or ""
|
||||||
|
if not p.get("claimedCount"):
|
||||||
|
p["claimedCount"] = len(ranked)
|
||||||
|
if p.get("totalCount") and int(p.get("claimedCount") or 0) >= int(p.get("totalCount") or 0):
|
||||||
|
p["finished"] = True
|
||||||
del p["claims"]
|
del p["claims"]
|
||||||
result.append(p)
|
result.append(p)
|
||||||
# 合并手机全量同步落盘数据(电脑重启后仍可看)
|
# 合并手机全量同步落盘数据(电脑重启后仍可看)
|
||||||
@@ -821,6 +836,9 @@ def _mmp_packets():
|
|||||||
merged["latestAt"] = merged["issuedAt"]
|
merged["latestAt"] = merged["issuedAt"]
|
||||||
by_id[key] = merged
|
by_id[key] = merged
|
||||||
result = list(by_id.values())
|
result = list(by_id.values())
|
||||||
|
for p in result:
|
||||||
|
_refresh_packet_closed(p)
|
||||||
|
p["statusLabel"] = _packet_status_label(p)
|
||||||
result.sort(key=lambda x: _mmp_time_sort_key(x.get("issuedAt") or x.get("latestAt") or ""), reverse=True)
|
result.sort(key=lambda x: _mmp_time_sort_key(x.get("issuedAt") or x.get("latestAt") or ""), reverse=True)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -834,6 +852,100 @@ def _mmp_time_sort_key(text):
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_mmp_time_ms(text):
|
||||||
|
s = (text or "").strip()
|
||||||
|
if not s:
|
||||||
|
return 0
|
||||||
|
m = re.match(r"^(\d{2})/(\d{2})/(\d{4})(?:\s+(\d{2}):(\d{2})(?::(\d{2}))?)?", s)
|
||||||
|
if m:
|
||||||
|
try:
|
||||||
|
dt = datetime(
|
||||||
|
int(m.group(3)), int(m.group(2)), int(m.group(1)),
|
||||||
|
int(m.group(4) or 23), int(m.group(5) or 59), int(m.group(6) or 59))
|
||||||
|
return int(dt.timestamp() * 1000)
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
|
m = re.match(r"^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?", s)
|
||||||
|
if m:
|
||||||
|
try:
|
||||||
|
dt = datetime(
|
||||||
|
int(m.group(1)), int(m.group(2)), int(m.group(3)),
|
||||||
|
int(m.group(4) or 0), int(m.group(5) or 0), int(m.group(6) or 0))
|
||||||
|
return int(dt.timestamp() * 1000)
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
|
if re.match(r"^\d{13}$", s):
|
||||||
|
return int(s)
|
||||||
|
if re.match(r"^\d{10}$", s):
|
||||||
|
return int(s) * 1000
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _is_terminal_status(status):
|
||||||
|
s = (status or "").upper()
|
||||||
|
if not s:
|
||||||
|
return False
|
||||||
|
return any(k in s for k in (
|
||||||
|
"FINISH", "COMPLETE", "EXPIRE", "ENDED", "CLOSED", "CANCEL", "DONE"))
|
||||||
|
|
||||||
|
|
||||||
|
def _is_expire_passed(expire_text):
|
||||||
|
ms = _parse_mmp_time_ms(expire_text)
|
||||||
|
return ms > 0 and int(time.time() * 1000) >= ms
|
||||||
|
|
||||||
|
|
||||||
|
def _refresh_packet_closed(p):
|
||||||
|
if not isinstance(p, dict):
|
||||||
|
return p
|
||||||
|
if p.get("finished"):
|
||||||
|
return p
|
||||||
|
if _is_terminal_status(p.get("status") or "") or _is_expire_passed(
|
||||||
|
p.get("expiresAt") or p.get("expire") or ""):
|
||||||
|
p["finished"] = True
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def _packet_status_label(p):
|
||||||
|
_refresh_packet_closed(p)
|
||||||
|
claimed = int(p.get("claimedCount") or 0)
|
||||||
|
total = int(p.get("totalCount") or 0)
|
||||||
|
board_n = len(p.get("leaderboard") or []) if isinstance(p.get("leaderboard"), list) else 0
|
||||||
|
claimed = max(claimed, board_n, int(p.get("claimantCount") or 0))
|
||||||
|
fully = total > 0 and claimed >= total
|
||||||
|
if fully:
|
||||||
|
progress = "已全部领取"
|
||||||
|
elif total > 0:
|
||||||
|
progress = "已领取 {0}/{1} 人".format(claimed, total)
|
||||||
|
elif claimed > 0:
|
||||||
|
progress = "已领取 {0} 人".format(claimed)
|
||||||
|
else:
|
||||||
|
progress = "未领取" if p.get("finished") else "领取中"
|
||||||
|
expired = _is_expire_passed(p.get("expiresAt") or "") or any(
|
||||||
|
k in (p.get("status") or "").upper() for k in ("EXPIRE", "ENDED", "CLOSED", "CANCEL"))
|
||||||
|
if expired and not fully:
|
||||||
|
return progress + " · 已过期"
|
||||||
|
return progress
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_counts_from_meta(item, meta):
|
||||||
|
counts = (meta.get("counts") or "").strip()
|
||||||
|
if counts and "/" in counts:
|
||||||
|
left, right = counts.split("/", 1)
|
||||||
|
try:
|
||||||
|
item["claimedCount"] = int(left.strip() or 0)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
item["totalCount"] = int(right.strip() or 0)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
board = item.get("claims") or item.get("leaderboard") or []
|
||||||
|
if not item.get("claimedCount") and isinstance(board, list):
|
||||||
|
item["claimedCount"] = len(board)
|
||||||
|
if item.get("totalCount") and item.get("claimedCount", 0) >= item["totalCount"]:
|
||||||
|
item["finished"] = True
|
||||||
|
|
||||||
|
|
||||||
def _mmp_html_page(filename="mmp_page.html"):
|
def _mmp_html_page(filename="mmp_page.html"):
|
||||||
page = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
|
page = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ android {
|
|||||||
applicationId "com.miraclegarden.smsmessage.xposed"
|
applicationId "com.miraclegarden.smsmessage.xposed"
|
||||||
minSdk 24
|
minSdk 24
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 3
|
versionCode 5
|
||||||
versionName "1.2.0"
|
versionName "1.2.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ public final class HookBridge {
|
|||||||
public static final String EXTRA_MODULE_VERSION_NAME = "moduleVersionName";
|
public static final String EXTRA_MODULE_VERSION_NAME = "moduleVersionName";
|
||||||
public static final String EXTRA_HOST_PACKAGE = "hostPackage";
|
public static final String EXTRA_HOST_PACKAGE = "hostPackage";
|
||||||
/** 与 xposed-module/build.gradle 保持同步 */
|
/** 与 xposed-module/build.gradle 保持同步 */
|
||||||
public static final int MODULE_VERSION_CODE = 3;
|
public static final int MODULE_VERSION_CODE = 5;
|
||||||
public static final String MODULE_VERSION_NAME = "1.2.0";
|
public static final String MODULE_VERSION_NAME = "1.2.2";
|
||||||
public static final String SOURCE_XPOSED_SQLITE = "xposed_sqlite";
|
public static final String SOURCE_XPOSED_SQLITE = "xposed_sqlite";
|
||||||
public static final String SOURCE_XPOSED_WECHAT = "xposed_wechat";
|
public static final String SOURCE_XPOSED_WECHAT = "xposed_wechat";
|
||||||
public static final String SOURCE_XPOSED_TELEGRAM = "xposed_telegram";
|
public static final String SOURCE_XPOSED_TELEGRAM = "xposed_telegram";
|
||||||
|
|||||||
@@ -938,7 +938,7 @@ public final class TngMoneyPacketHook {
|
|||||||
return s == null ? "" : s;
|
return s == null ? "" : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** remaining / claimed/total / status 任一表明已领完 */
|
/** remaining / claimed/total / status 任一表明已领完或已结束 */
|
||||||
private static boolean isFinishedFlags(String remain, String claimedSlashTotal, String status) {
|
private static boolean isFinishedFlags(String remain, String claimedSlashTotal, String status) {
|
||||||
if (!TextUtils.isEmpty(remain)) {
|
if (!TextUtils.isEmpty(remain)) {
|
||||||
try {
|
try {
|
||||||
@@ -961,7 +961,9 @@ public final class TngMoneyPacketHook {
|
|||||||
}
|
}
|
||||||
if (!TextUtils.isEmpty(status)) {
|
if (!TextUtils.isEmpty(status)) {
|
||||||
String s = status.toUpperCase(Locale.US);
|
String s = status.toUpperCase(Locale.US);
|
||||||
|
// TNG ActivityStatus: ACTIVE/INIT/FINISHED/ENDED/CLOSED/CANCELLED
|
||||||
if (s.contains("FINISH") || s.contains("COMPLETE") || s.contains("EXPIRE")
|
if (s.contains("FINISH") || s.contains("COMPLETE") || s.contains("EXPIRE")
|
||||||
|
|| s.contains("ENDED") || s.contains("CLOSED") || s.contains("CANCEL")
|
||||||
|| s.contains("DONE") || "TRUE".equals(s)) {
|
|| s.contains("DONE") || "TRUE".equals(s)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -978,6 +980,9 @@ public final class TngMoneyPacketHook {
|
|||||||
snapshot.activityStatus)) {
|
snapshot.activityStatus)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (isExpireTimePassed(snapshot.expireTime)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// 已领金额 ≈ 总额
|
// 已领金额 ≈ 总额
|
||||||
Double claimedAmt = parseMoneyDouble(snapshot.claimedAmountText);
|
Double claimedAmt = parseMoneyDouble(snapshot.claimedAmountText);
|
||||||
Double totalAmt = parseMoneyDouble(snapshot.totalAmount);
|
Double totalAmt = parseMoneyDouble(snapshot.totalAmount);
|
||||||
@@ -998,6 +1003,58 @@ public final class TngMoneyPacketHook {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** expireTime 形如 05/08/2026 14:06:56 或 ISO;已过则视为结束 */
|
||||||
|
private static boolean isExpireTimePassed(String expireTime) {
|
||||||
|
long ms = parseIssueTimeMs(expireTime);
|
||||||
|
return ms > 0 && System.currentTimeMillis() >= ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long parseIssueTimeMs(String text) {
|
||||||
|
if (TextUtils.isEmpty(text)) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
String s = text.trim();
|
||||||
|
try {
|
||||||
|
java.util.regex.Matcher m = java.util.regex.Pattern
|
||||||
|
.compile("^(\\d{2})/(\\d{2})/(\\d{4})(?:\\s+(\\d{2}):(\\d{2})(?::(\\d{2}))?)?")
|
||||||
|
.matcher(s);
|
||||||
|
if (m.find()) {
|
||||||
|
java.util.Calendar c = java.util.Calendar.getInstance();
|
||||||
|
c.set(Integer.parseInt(m.group(3)),
|
||||||
|
Integer.parseInt(m.group(2)) - 1,
|
||||||
|
Integer.parseInt(m.group(1)),
|
||||||
|
m.group(4) != null ? Integer.parseInt(m.group(4)) : 23,
|
||||||
|
m.group(5) != null ? Integer.parseInt(m.group(5)) : 59,
|
||||||
|
m.group(6) != null ? Integer.parseInt(m.group(6)) : 59);
|
||||||
|
c.set(java.util.Calendar.MILLISECOND, 999);
|
||||||
|
return c.getTimeInMillis();
|
||||||
|
}
|
||||||
|
if (s.matches("^\\d{13}$")) {
|
||||||
|
return Long.parseLong(s);
|
||||||
|
}
|
||||||
|
if (s.matches("^\\d{10}$")) {
|
||||||
|
return Long.parseLong(s) * 1000L;
|
||||||
|
}
|
||||||
|
// 2026-08-05T14:06:56 / 2026-08-05 14:06:56
|
||||||
|
java.util.regex.Matcher iso = java.util.regex.Pattern
|
||||||
|
.compile("^(\\d{4})-(\\d{2})-(\\d{2})[ T](\\d{2}):(\\d{2})(?::(\\d{2}))?")
|
||||||
|
.matcher(s);
|
||||||
|
if (iso.find()) {
|
||||||
|
java.util.Calendar c = java.util.Calendar.getInstance();
|
||||||
|
c.set(Integer.parseInt(iso.group(1)),
|
||||||
|
Integer.parseInt(iso.group(2)) - 1,
|
||||||
|
Integer.parseInt(iso.group(3)),
|
||||||
|
Integer.parseInt(iso.group(4)),
|
||||||
|
Integer.parseInt(iso.group(5)),
|
||||||
|
iso.group(6) != null ? Integer.parseInt(iso.group(6)) : 0);
|
||||||
|
c.set(java.util.Calendar.MILLISECOND, 0);
|
||||||
|
return c.getTimeInMillis();
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
private static String buildCountHint(String claimed, String total) {
|
private static String buildCountHint(String claimed, String total) {
|
||||||
if (TextUtils.isEmpty(claimed) && TextUtils.isEmpty(total)) {
|
if (TextUtils.isEmpty(claimed) && TextUtils.isEmpty(total)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -2034,6 +2091,11 @@ public final class TngMoneyPacketHook {
|
|||||||
stringField(result, "activityStatus", "getActivityStatus"),
|
stringField(result, "activityStatus", "getActivityStatus"),
|
||||||
boolFieldText(summary, "isFinished", "isFinished"),
|
boolFieldText(summary, "isFinished", "isFinished"),
|
||||||
boolFieldText(result, "isFinished", "isFinished"));
|
boolFieldText(result, "isFinished", "isFinished"));
|
||||||
|
snapshot.expireTime = firstNonEmpty(
|
||||||
|
stringField(summary, "expireTime", "getExpireTime"),
|
||||||
|
stringField(result, "expireTime", "getExpireTime"),
|
||||||
|
stringField(summary, "expiryTime", "getExpiryTime"),
|
||||||
|
stringField(result, "expiryTime", "getExpiryTime"));
|
||||||
if (TextUtils.isEmpty(snapshot.claimedAmountText)) {
|
if (TextUtils.isEmpty(snapshot.claimedAmountText)) {
|
||||||
try {
|
try {
|
||||||
Object money = XposedHelpers.callMethod(summary, "getClaimedAmount");
|
Object money = XposedHelpers.callMethod(summary, "getClaimedAmount");
|
||||||
@@ -2649,6 +2711,7 @@ public final class TngMoneyPacketHook {
|
|||||||
String claimedCount;
|
String claimedCount;
|
||||||
String totalCount;
|
String totalCount;
|
||||||
String activityStatus;
|
String activityStatus;
|
||||||
|
String expireTime;
|
||||||
String claimedAmountText;
|
String claimedAmountText;
|
||||||
boolean finished;
|
boolean finished;
|
||||||
final List<ClaimLine> claims = new ArrayList<>();
|
final List<ClaimLine> claims = new ArrayList<>();
|
||||||
@@ -2713,6 +2776,21 @@ public final class TngMoneyPacketHook {
|
|||||||
if (!TextUtils.isEmpty(issueTime)) {
|
if (!TextUtils.isEmpty(issueTime)) {
|
||||||
sb.append(" | issued=").append(sanitizeMeta(issueTime));
|
sb.append(" | issued=").append(sanitizeMeta(issueTime));
|
||||||
}
|
}
|
||||||
|
if (!TextUtils.isEmpty(expireTime)) {
|
||||||
|
sb.append(" | expire=").append(sanitizeMeta(expireTime));
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(activityStatus)) {
|
||||||
|
sb.append(" | status=").append(sanitizeMeta(activityStatus));
|
||||||
|
}
|
||||||
|
String claimed = claimedCount;
|
||||||
|
String totalCnt = totalCount;
|
||||||
|
if (TextUtils.isEmpty(claimed) && claims != null) {
|
||||||
|
claimed = String.valueOf(claims.size());
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(claimed) || !TextUtils.isEmpty(totalCnt)) {
|
||||||
|
sb.append(" | counts=").append(sanitizeMeta(nullToEmpty(claimed)))
|
||||||
|
.append('/').append(sanitizeMeta(nullToEmpty(totalCnt)));
|
||||||
|
}
|
||||||
if (finished) {
|
if (finished) {
|
||||||
sb.append(" | done=1");
|
sb.append(" | done=1");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user