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.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
@@ -157,42 +158,52 @@ public class MmpClaimActivity extends MiracleGardenActivity<ActivityMmpClaimBind
|
||||
holder.setText(R.id.tv_sum, formatMoney(packet.sumClaimed > 0
|
||||
? 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);
|
||||
if (packet.finished) {
|
||||
status.setText("已领完");
|
||||
status.setText(packet.statusLabel());
|
||||
if (packet.isFullyClaimed()) {
|
||||
status.setTextColor(Color.parseColor("#047857"));
|
||||
status.setBackgroundColor(Color.parseColor("#ECFDF5"));
|
||||
} else if (packet.isExpired()) {
|
||||
status.setTextColor(Color.parseColor("#6B7280"));
|
||||
status.setBackgroundColor(Color.parseColor("#F3F4F6"));
|
||||
} else {
|
||||
status.setText("领取中");
|
||||
status.setTextColor(Color.parseColor("#B45309"));
|
||||
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()) {
|
||||
meta += " · 发放 " + packet.issuedAt;
|
||||
}
|
||||
if (packet.expiresAt != null && !packet.expiresAt.isEmpty()) {
|
||||
meta += " · 到期 " + packet.expiresAt;
|
||||
}
|
||||
holder.setText(R.id.tv_meta, meta);
|
||||
|
||||
String hi = packet.finished ? "最佳" : "最高";
|
||||
String lo = packet.finished ? "最差" : "最低";
|
||||
com.miraclegarden.smsmessage.mmp.MmpClaim best = packet.bestClaim();
|
||||
com.miraclegarden.smsmessage.mmp.MmpClaim worst = packet.worstClaim();
|
||||
if (best != null) {
|
||||
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 + " -");
|
||||
}
|
||||
holder.setText(R.id.tv_best, formatHiLoLine(hi, best));
|
||||
if (worst != null && packet.claimantCount > 1) {
|
||||
String amt = worst.amountText != null ? worst.amountText : formatMoney(worst.amount);
|
||||
String nick = worst.nickname != null ? worst.nickname : "?";
|
||||
holder.setText(R.id.tv_worst, lo + " " + nick + " " + amt);
|
||||
holder.setText(R.id.tv_worst, formatHiLoLine(lo, worst));
|
||||
} else {
|
||||
holder.setText(R.id.tv_worst, lo + " -");
|
||||
}
|
||||
holder.setText(R.id.tv_id, packet.shortId());
|
||||
holder.itemView.setOnClickListener(v -> openDetail(packet.packetId));
|
||||
}
|
||||
};
|
||||
@@ -538,6 +549,19 @@ public class MmpClaimActivity extends MiracleGardenActivity<ActivityMmpClaimBind
|
||||
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() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("清空红包台")
|
||||
|
||||
@@ -156,17 +156,16 @@ public class MmpDetailActivity extends MiracleGardenActivity<ActivityMmpDetailBi
|
||||
}
|
||||
sub.append("发放 ").append(packet.issuedAt);
|
||||
}
|
||||
if (packet.finished) {
|
||||
if (!TextUtils.isEmpty(packet.expiresAt)) {
|
||||
if (sub.length() > 0) {
|
||||
sub.append(" · ");
|
||||
}
|
||||
sub.append("已领完");
|
||||
} else {
|
||||
if (sub.length() > 0) {
|
||||
sub.append(" · ");
|
||||
}
|
||||
sub.append("领取中");
|
||||
sub.append("到期 ").append(packet.expiresAt);
|
||||
}
|
||||
if (sub.length() > 0) {
|
||||
sub.append(" · ");
|
||||
}
|
||||
sub.append(packet.statusLabel());
|
||||
binding.tvSub.setText(sub.toString());
|
||||
|
||||
binding.tvStatPeople.setText(String.valueOf(packet.claimantCount));
|
||||
@@ -181,7 +180,7 @@ public class MmpDetailActivity extends MiracleGardenActivity<ActivityMmpDetailBi
|
||||
MmpClaim worst = packet.worstClaim();
|
||||
if (best != null) {
|
||||
bestAmount = best.amount;
|
||||
binding.tvBestName.setText(best.nickname != null ? best.nickname : "?");
|
||||
binding.tvBestName.setText(formatClaimName(best));
|
||||
binding.tvBestAmt.setText(best.amountText != null ? best.amountText
|
||||
: String.format(Locale.US, "%.2f", best.amount));
|
||||
} else {
|
||||
@@ -191,7 +190,7 @@ public class MmpDetailActivity extends MiracleGardenActivity<ActivityMmpDetailBi
|
||||
}
|
||||
if (worst != null && packet.claimantCount > 1) {
|
||||
worstAmount = worst.amount;
|
||||
binding.tvWorstName.setText(worst.nickname != null ? worst.nickname : "?");
|
||||
binding.tvWorstName.setText(formatClaimName(worst));
|
||||
binding.tvWorstAmt.setText(worst.amountText != null ? worst.amountText
|
||||
: String.format(Locale.US, "%.2f", worst.amount));
|
||||
} else {
|
||||
@@ -213,6 +212,17 @@ public class MmpDetailActivity extends MiracleGardenActivity<ActivityMmpDetailBi
|
||||
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) {
|
||||
try {
|
||||
return s == null || s.isEmpty() ? 0 : Double.parseDouble(s);
|
||||
|
||||
@@ -5,8 +5,8 @@ package com.miraclegarden.smsmessage.mmp;
|
||||
*/
|
||||
public final class MmpModuleInfo {
|
||||
public static final String XPOSED_PACKAGE = "com.miraclegarden.smsmessage.xposed";
|
||||
public static final int EXPECTED_VERSION_CODE = 3;
|
||||
public static final String EXPECTED_VERSION_NAME = "1.2.0";
|
||||
public static final int EXPECTED_VERSION_CODE = 5;
|
||||
public static final String EXPECTED_VERSION_NAME = "1.2.2";
|
||||
|
||||
private MmpModuleInfo() {
|
||||
}
|
||||
|
||||
@@ -15,9 +15,16 @@ public class MmpPacket {
|
||||
public String total;
|
||||
public String via;
|
||||
public String issuedAt;
|
||||
public String expiresAt;
|
||||
/** TNG activityStatus:ACTIVE / FINISHED / ENDED / CLOSED / CANCELLED … */
|
||||
public String status;
|
||||
public String updatedAt;
|
||||
public boolean finished;
|
||||
public int snapshots;
|
||||
/** 已领人数(来自 TNG claimedCount,缺省用名单人数) */
|
||||
public int claimedCount;
|
||||
/** 红包总份数(TNG totalCount) */
|
||||
public int totalCount;
|
||||
public int claimantCount;
|
||||
public double sumClaimed;
|
||||
public List<MmpClaim> leaderboard = new ArrayList<>();
|
||||
@@ -74,10 +81,12 @@ public class MmpPacket {
|
||||
}
|
||||
|
||||
public String luckSummaryLine() {
|
||||
refreshClosedState();
|
||||
MmpClaim best = bestClaim();
|
||||
MmpClaim worst = worstClaim();
|
||||
String hi = finished ? "手气最佳" : "目前最高";
|
||||
String lo = finished ? "手气最差" : "目前最低";
|
||||
boolean closed = finished;
|
||||
String hi = closed ? "手气最佳" : "目前最高";
|
||||
String lo = closed ? "手气最差" : "目前最低";
|
||||
if (best == null) {
|
||||
return hi + ":- " + lo + ":-";
|
||||
}
|
||||
@@ -93,6 +102,87 @@ public class MmpPacket {
|
||||
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) {
|
||||
return matchesFilter(keyword, statusFilter, 0, 0L, 0L);
|
||||
}
|
||||
@@ -103,6 +193,7 @@ public class MmpPacket {
|
||||
*/
|
||||
public boolean matchesFilter(String keyword, int statusFilter,
|
||||
int timeMode, long customFromMs, long customToMs) {
|
||||
refreshClosedState();
|
||||
if (statusFilter == 1 && finished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -96,14 +96,30 @@ public final class MmpPacketStore {
|
||||
incoming.total = nullToEmpty(parsed.meta.get("total"));
|
||||
incoming.via = nullToEmpty(parsed.meta.get("via"));
|
||||
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"))
|
||||
|| "true".equalsIgnoreCase(parsed.meta.get("done"))
|
||||
|| "FINISHED".equalsIgnoreCase(parsed.meta.get("status"));
|
||||
|| isTerminalStatusMeta(incoming.status);
|
||||
incoming.refreshClosedState();
|
||||
incoming.updatedAt = nowText();
|
||||
incoming.snapshots = 1;
|
||||
List<MmpClaim> ranked = aggregateClaims(parsed.claims);
|
||||
incoming.leaderboard = ranked;
|
||||
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;
|
||||
for (MmpClaim c : ranked) {
|
||||
sum += c.amount;
|
||||
@@ -127,9 +143,23 @@ public final class MmpPacketStore {
|
||||
if (TextUtils.isEmpty(incoming.issuedAt) && !TextUtils.isEmpty(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) {
|
||||
incoming.finished = true;
|
||||
}
|
||||
incoming.refreshClosedState();
|
||||
map.put(packetId, incoming);
|
||||
} else {
|
||||
existing.snapshots = existing.snapshots + 1;
|
||||
@@ -193,6 +223,11 @@ public final class MmpPacketStore {
|
||||
return nullToEmpty(b.packetId).compareTo(nullToEmpty(a.packetId));
|
||||
}
|
||||
});
|
||||
for (MmpPacket p : list) {
|
||||
if (p != null) {
|
||||
p.refreshClosedState();
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -213,7 +248,11 @@ public final class MmpPacketStore {
|
||||
return null;
|
||||
}
|
||||
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("via", nullToEmpty(p.via));
|
||||
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("finished", p.finished);
|
||||
o.put("snapshots", p.snapshots);
|
||||
o.put("claimedCount", p.claimedCount);
|
||||
o.put("totalCount", p.totalCount);
|
||||
o.put("claimantCount", p.claimantCount);
|
||||
o.put("sumClaimed", p.sumClaimed);
|
||||
JSONArray board = new JSONArray();
|
||||
@@ -633,9 +676,16 @@ public final class MmpPacketStore {
|
||||
p.total = o.optString("total", "");
|
||||
p.via = o.optString("via", "");
|
||||
p.issuedAt = o.optString("issuedAt", "");
|
||||
p.expiresAt = o.optString("expiresAt", "");
|
||||
p.status = o.optString("status", "");
|
||||
p.updatedAt = o.optString("updatedAt", "");
|
||||
p.finished = o.optBoolean("finished", false);
|
||||
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.sumClaimed = o.optDouble("sumClaimed", 0);
|
||||
JSONArray board = o.optJSONArray("leaderboard");
|
||||
@@ -657,6 +707,10 @@ public final class MmpPacketStore {
|
||||
if (p.claimantCount <= 0) {
|
||||
p.claimantCount = p.leaderboard.size();
|
||||
}
|
||||
if (p.claimedCount <= 0) {
|
||||
p.claimedCount = p.claimantCount;
|
||||
}
|
||||
p.refreshClosedState();
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -684,6 +738,66 @@ public final class MmpPacketStore {
|
||||
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() {
|
||||
return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||
.format(new java.util.Date());
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:maxLines="2"
|
||||
android:text="-"
|
||||
android:textColor="#047857"
|
||||
android:textSize="16sp"
|
||||
@@ -219,7 +219,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:maxLines="2"
|
||||
android:text="-"
|
||||
android:textColor="#B45309"
|
||||
android:textSize="16sp"
|
||||
|
||||
@@ -16,17 +16,34 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="红包"
|
||||
android:textColor="#111827"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="红包"
|
||||
android:textColor="#111827"
|
||||
android:textSize="14sp"
|
||||
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
|
||||
android:id="@+id/tv_sum"
|
||||
@@ -71,17 +88,6 @@
|
||||
android:text="0 人"
|
||||
android:textColor="#6B7280"
|
||||
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
|
||||
@@ -97,7 +103,7 @@
|
||||
android:layout_weight="1"
|
||||
android:background="#ECFDF5"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="6dp"
|
||||
@@ -115,7 +121,7 @@
|
||||
android:layout_weight="1"
|
||||
android:background="#FFFBEB"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="6dp"
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
.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-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 {
|
||||
font-size: 15px; font-weight: 800; color: var(--money);
|
||||
font-variant-numeric: tabular-nums; white-space: nowrap;
|
||||
@@ -104,9 +105,11 @@
|
||||
.pkt-meta span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.badge {
|
||||
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.open { background: var(--worst-bg); color: var(--open); }
|
||||
.badge.expired { background: #f3f4f6; color: #6b7280; }
|
||||
.pkt-luck {
|
||||
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 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){
|
||||
return {
|
||||
hi: p&&p.finished?'手气最佳':'目前最高',
|
||||
@@ -407,19 +418,35 @@
|
||||
const div=document.createElement('div');
|
||||
div.className='pkt'+(p.packetId===activeId?' active':'');
|
||||
div.onclick=()=>{ activeId=p.packetId; renderList(); };
|
||||
const badge=p.finished?'<span class="badge done">已领完</span>':'<span class="badge open">领取中</span>';
|
||||
const hiShort = p.finished ? '最佳' : '最高';
|
||||
const loShort = p.finished ? '最差' : '最低';
|
||||
const bestLine = `${hiShort} ${esc(p.bestNick||'-')} ${esc(p.bestAmount||'')}`.trim();
|
||||
const label = p.statusLabel || (p.finished ? '已领完' : '领取中');
|
||||
const fully = label.indexOf('已全部领取') >= 0;
|
||||
const expired = label.indexOf('已过期') >= 0;
|
||||
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
|
||||
? `${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 class="pkt-row">
|
||||
<div class="pkt-name">${esc(p.sender||p.title||'红包')}</div>
|
||||
<div style="min-width:0;flex:1">
|
||||
<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>
|
||||
<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="mini best">${bestLine}</div>
|
||||
<div class="mini worst">${worstLine}</div>
|
||||
@@ -437,21 +464,42 @@
|
||||
const parts=[];
|
||||
if(p.packetId) parts.push('ID '+p.packetId);
|
||||
if(p.issuedAt) parts.push('发放 '+p.issuedAt);
|
||||
if(p.expiresAt) parts.push('到期 '+p.expiresAt);
|
||||
if(p.via) parts.push(p.via);
|
||||
sub.textContent = parts.join(' · ');
|
||||
cPeople.textContent = p.claimantCount || 0;
|
||||
cSum.textContent = money(p.sumClaimed ?? 0);
|
||||
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;
|
||||
luckWorstLab.textContent = labels.lo;
|
||||
luckBestName.textContent = p.bestNick || '-';
|
||||
luckBestAmt.textContent = p.bestAmount ? ('RM ' + p.bestAmount) : '-';
|
||||
if((p.claimantCount||0)<=1){
|
||||
luckWorstName.textContent='-'; luckWorstAmt.textContent='-';
|
||||
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 {
|
||||
luckWorstName.textContent = p.worstNick || '-';
|
||||
luckWorstAmt.textContent = p.worstAmount ? ('RM ' + p.worstAmount) : '-';
|
||||
luckBestName.textContent = p.bestNick || '-';
|
||||
luckBestAmt.textContent = p.bestAmount ? ('RM ' + p.bestAmount) : '-';
|
||||
}
|
||||
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 {
|
||||
luckWorstName.textContent='-'; luckWorstAmt.textContent='-';
|
||||
}
|
||||
const rows=p.leaderboard||[];
|
||||
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")
|
||||
|
||||
# 与 xposed-module / App MmpModuleInfo 同步
|
||||
EXPECTED_MODULE_CODE = 3
|
||||
EXPECTED_MODULE_NAME = "1.2.0"
|
||||
EXPECTED_MODULE_CODE = 5
|
||||
EXPECTED_MODULE_NAME = "1.2.2"
|
||||
|
||||
_messages = deque(maxlen=MAX_MESSAGES)
|
||||
_mmp_messages = deque(maxlen=MAX_MESSAGES)
|
||||
@@ -629,6 +629,9 @@ def _normalize_synced_packet(raw):
|
||||
elif sum_claimed:
|
||||
total = "{0:.2f}".format(sum_claimed)
|
||||
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 {
|
||||
"packetId": pid,
|
||||
"title": raw.get("title") or "TNG 红包",
|
||||
@@ -637,11 +640,15 @@ def _normalize_synced_packet(raw):
|
||||
"total": total,
|
||||
"via": raw.get("via") or "phone-sync",
|
||||
"issuedAt": issued,
|
||||
"expiresAt": expires,
|
||||
"status": status,
|
||||
"updatedAt": raw.get("updatedAt") or "",
|
||||
"fetchedAt": raw.get("updatedAt") or raw.get("fetchedAt") or _now_iso(),
|
||||
"latestAt": issued or raw.get("updatedAt") or "",
|
||||
"finished": bool(raw.get("finished")),
|
||||
"finished": finished,
|
||||
"snapshots": int(raw.get("snapshots") or 1),
|
||||
"claimedCount": int(raw.get("claimedCount") or 0),
|
||||
"totalCount": int(raw.get("totalCount") or 0),
|
||||
"leaderboard": norm_board,
|
||||
"claimantCount": len(norm_board) if norm_board else int(raw.get("claimantCount") or 0),
|
||||
"sumClaimed": sum_claimed,
|
||||
@@ -741,7 +748,8 @@ def _mmp_packets():
|
||||
key = str(packet_id)
|
||||
issued = meta.get("issued") or ""
|
||||
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 = {
|
||||
"packetId": key,
|
||||
"title": msg.get("title") or "TNG 红包",
|
||||
@@ -750,12 +758,15 @@ def _mmp_packets():
|
||||
"total": meta.get("total") or "",
|
||||
"via": meta.get("via") or "",
|
||||
"issuedAt": issued,
|
||||
"expiresAt": meta.get("expire") or meta.get("expireTime") or "",
|
||||
"status": meta.get("status") or "",
|
||||
"fetchedAt": msg.get("receivedAt") or "",
|
||||
"latestAt": issued or (msg.get("receivedAt") or ""),
|
||||
"finished": finished,
|
||||
"snapshots": 1,
|
||||
"claims": claims,
|
||||
}
|
||||
_apply_counts_from_meta(item, meta)
|
||||
existing = packets.get(key)
|
||||
def _score(it):
|
||||
return (
|
||||
@@ -801,6 +812,10 @@ def _mmp_packets():
|
||||
p["bestAmount"] = (best or {}).get("amountText") or ""
|
||||
p["worstNick"] = (worst or {}).get("nickname") 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"]
|
||||
result.append(p)
|
||||
# 合并手机全量同步落盘数据(电脑重启后仍可看)
|
||||
@@ -821,6 +836,9 @@ def _mmp_packets():
|
||||
merged["latestAt"] = merged["issuedAt"]
|
||||
by_id[key] = merged
|
||||
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)
|
||||
return result
|
||||
|
||||
@@ -834,6 +852,100 @@ def _mmp_time_sort_key(text):
|
||||
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"):
|
||||
page = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
|
||||
try:
|
||||
|
||||
@@ -10,8 +10,8 @@ android {
|
||||
applicationId "com.miraclegarden.smsmessage.xposed"
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionCode 3
|
||||
versionName "1.2.0"
|
||||
versionCode 5
|
||||
versionName "1.2.2"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -14,8 +14,8 @@ public final class HookBridge {
|
||||
public static final String EXTRA_MODULE_VERSION_NAME = "moduleVersionName";
|
||||
public static final String EXTRA_HOST_PACKAGE = "hostPackage";
|
||||
/** 与 xposed-module/build.gradle 保持同步 */
|
||||
public static final int MODULE_VERSION_CODE = 3;
|
||||
public static final String MODULE_VERSION_NAME = "1.2.0";
|
||||
public static final int MODULE_VERSION_CODE = 5;
|
||||
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_WECHAT = "xposed_wechat";
|
||||
public static final String SOURCE_XPOSED_TELEGRAM = "xposed_telegram";
|
||||
|
||||
@@ -938,7 +938,7 @@ public final class TngMoneyPacketHook {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
/** remaining / claimed/total / status 任一表明已领完 */
|
||||
/** remaining / claimed/total / status 任一表明已领完或已结束 */
|
||||
private static boolean isFinishedFlags(String remain, String claimedSlashTotal, String status) {
|
||||
if (!TextUtils.isEmpty(remain)) {
|
||||
try {
|
||||
@@ -961,7 +961,9 @@ public final class TngMoneyPacketHook {
|
||||
}
|
||||
if (!TextUtils.isEmpty(status)) {
|
||||
String s = status.toUpperCase(Locale.US);
|
||||
// TNG ActivityStatus: ACTIVE/INIT/FINISHED/ENDED/CLOSED/CANCELLED
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
@@ -978,6 +980,9 @@ public final class TngMoneyPacketHook {
|
||||
snapshot.activityStatus)) {
|
||||
return true;
|
||||
}
|
||||
if (isExpireTimePassed(snapshot.expireTime)) {
|
||||
return true;
|
||||
}
|
||||
// 已领金额 ≈ 总额
|
||||
Double claimedAmt = parseMoneyDouble(snapshot.claimedAmountText);
|
||||
Double totalAmt = parseMoneyDouble(snapshot.totalAmount);
|
||||
@@ -998,6 +1003,58 @@ public final class TngMoneyPacketHook {
|
||||
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) {
|
||||
if (TextUtils.isEmpty(claimed) && TextUtils.isEmpty(total)) {
|
||||
return null;
|
||||
@@ -2034,6 +2091,11 @@ public final class TngMoneyPacketHook {
|
||||
stringField(result, "activityStatus", "getActivityStatus"),
|
||||
boolFieldText(summary, "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)) {
|
||||
try {
|
||||
Object money = XposedHelpers.callMethod(summary, "getClaimedAmount");
|
||||
@@ -2649,6 +2711,7 @@ public final class TngMoneyPacketHook {
|
||||
String claimedCount;
|
||||
String totalCount;
|
||||
String activityStatus;
|
||||
String expireTime;
|
||||
String claimedAmountText;
|
||||
boolean finished;
|
||||
final List<ClaimLine> claims = new ArrayList<>();
|
||||
@@ -2713,6 +2776,21 @@ public final class TngMoneyPacketHook {
|
||||
if (!TextUtils.isEmpty(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) {
|
||||
sb.append(" | done=1");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user