Wallets: expected-days drain, copy/paste, three-column selectable balances, rename and destroy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Opus 5.5 committed 2026-11-03 23:05:13 +08:00
1 parent 783f73989d
commit 2b781acd82
10 files changed
+178 -22

No files matched your search

+61 -9
View File
@@ -68,8 +68,9 @@ namespace Decentralized
public static string NameOf(Wallet w)
{
if (!string.IsNullOrWhiteSpace(w.Label)) return w.Label;
int i = Session.Data.Wallets.IndexOf(w);
return i == 0 ? L.T("hw.wallet.main") : L.T("hw.wallet.n", i + 1);
return i == 0 ? L.T("hw.wallet.main") : L.T("hw.wallet.n", w.Name ?? (i + 1).ToString());
}
private static Device Dev(Wallet w)
@@ -80,6 +81,14 @@ namespace Decentralized
private static bool IsOpen(Device dev) => CustomUIManager.Instance != null && CustomUIManager.Instance.IsOpen(dev.Id);
/// <summary>钱包被销毁:关掉它的窗口,丢掉设备状态。</summary>
public static void Close(Wallet w)
{
if (w == null || !Devices.TryGetValue(w.Address, out var dev)) return;
if (IsOpen(dev)) CustomUIManager.Instance.CloseWindow(dev.Id);
Devices.Remove(w.Address);
}
/// <summary>读档 / 新游戏后清掉上一局的设备状态并关窗。</summary>
public static void Reset()
{
@@ -115,6 +124,7 @@ namespace Decentralized
WalletDriver.Ensure();
var b = CustomUIManager.Window(dev.Id, "PLEDGER");
if (b == null) return;
_rows.Clear(); _clear.Clear();
int idx = Session.Data.Wallets.IndexOf(dev.Wallet);
b.SetSize(WinW, WinH).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle();
b.SetPosition(new Vector2(600f + 28f * idx, -24f * idx));
@@ -181,6 +191,21 @@ namespace Decentralized
if (img != null) img.color = Key;
foreach (var t in btn.GetComponentsInChildren<TextMeshProUGUI>(true)) t.color = KeyText;
}
foreach (var img in _clear) if (img != null) img.color = new Color(0, 0, 0, 0);
// 余额行:整行可点,选中的一行反色高亮
foreach (var (root, img, asset) in _rows)
{
if (root == null || img == null) continue;
bool on = asset == dev.Asset;
img.sprite = MachineryStyle.White; img.type = Image.Type.Simple; img.raycastTarget = true;
img.color = on ? Ink : new Color(0, 0, 0, 0.04f);
foreach (var t in root.GetComponentsInChildren<TextMeshProUGUI>(true)) t.color = on ? KeyText : Ink;
var btn = root.GetComponent<Button>() ?? root.AddComponent<Button>();
btn.transition = Selectable.Transition.None;
btn.onClick.RemoveAllListeners();
string a = asset;
btn.onClick.AddListener(() => { dev.Asset = a; dev.Note = null; Open(dev); });
}
// 签名要按住 ✓:在按键上挂一个按住计时
if (ok != null && dev.Screen == Screen.Review && ok.GetComponent<HoldToSign>() == null) ok.AddComponent<HoldToSign>().Device = dev.Address;
}
@@ -203,6 +228,9 @@ namespace Decentralized
}
private static readonly Dictionary<string, Sprite> Sliced = new Dictionary<string, Sprite>();
private const float RowW = ScreenW - 28f;
private static readonly List<(GameObject root, Image image, string asset)> _rows = new List<(GameObject, Image, string)>();
private static readonly List<Image> _clear = new List<Image>();
public static string Amt(double v) => v.ToString(v >= 100 ? "0.##" : "0.####", CultureInfo.InvariantCulture);
@@ -214,16 +242,34 @@ namespace Decentralized
private static void HomeScreen(CustomUIBuilder b, Device dev)
{
var w = dev.Wallet;
b.AddHeader(NameOf(w));
b.AddLabel("<size=22><b>" + NameOf(w) + "</b></size>");
b.BeginRow(8f, TextAnchor.MiddleLeft);
b.AddLabel(w.Address, "addr_dim");
b.AddButton(L.T("hw.copy"), () => { Clipboard.Copy(w.Address); dev.Note = L.T("hw.copied"); Open(dev); });
b.End();
b.AddSeparator();
bool any = false;
foreach (var kv in w.Balances.OrderBy(k => k.Key))
// 余额:每个币一行,左边币种、右边数量与估值,全部显示(含 0);点一行选中,发送时默认用它
foreach (var a in SendAssets)
{
any = true;
b.AddLabel(L.T("hw.balance", kv.Key, Amt(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key))));
double q = w.Get(a);
b.BeginPanel(RowW, 32f, TextAnchor.MiddleLeft, 6f, 0f);
_rows.Add((b.Last.root, b.Last.image, a));
b.BeginRow(0f, TextAnchor.MiddleLeft);
b.BeginPanel(90f, 26f, TextAnchor.MiddleLeft, 0f, 0f);
_clear.Add(b.Last.image);
b.AddLabel(a);
b.End();
b.BeginPanel(130f, 26f, TextAnchor.MiddleRight, 0f, 0f);
_clear.Add(b.Last.image);
b.AddLabel(Amt(q) + (w.Frozen.TryGetValue(a, out var fz) && fz > 0 ? L.T("hw.balance.frozen", Amt(fz)) : ""));
b.End();
b.BeginPanel(RowW - 12f - 90f - 130f, 26f, TextAnchor.MiddleRight, 0f, 0f);
_clear.Add(b.Last.image);
b.AddLabel(L.T("hw.balance.value", Fmt.Credits(q * Session.Price(a))));
b.End();
b.End();
b.End();
}
if (!any) b.AddLabel(L.T("wallet.empty"));
b.AddSeparator();
b.AddLabel(L.T("hw.total", Fmt.Credits(w.Value(Session.Price))));
if (w.Taint > 0.5) b.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
@@ -240,7 +286,13 @@ namespace Decentralized
var w = dev.Wallet;
b.AddHeader(L.T("hw.send"));
b.AddLabel(L.T("hw.to"));
b.BeginRow(6f, TextAnchor.MiddleLeft);
b.BeginPanel(RowW - 70f, -1f, TextAnchor.MiddleLeft, 0f, 0f);
_clear.Add(b.Last.image);
b.AddInput("0x", dev.To, s => dev.To = s, "to", s => dev.To = s);
b.End();
b.AddButton(L.T("hw.paste"), () => { dev.To = Clipboard.Paste(); dev.Note = null; Open(dev); });
b.End();
b.BeginRow();
foreach (var a in SendAssets)
{
@@ -452,10 +504,10 @@ namespace Decentralized
}
/// <summary>别的交易让这个钱包的余额变了:开着的话刷新主页。</summary>
public static void RefreshDevice(Wallet w)
public static void RefreshDevice(Wallet w, bool any = false)
{
if (w == null || !Devices.TryGetValue(w.Address, out var dev) || !IsOpen(dev)) return;
if (dev.Screen == Screen.Home || dev.Screen == Screen.Send) Open(dev);
if (any || dev.Screen == Screen.Home || dev.Screen == Screen.Send) Open(dev);
}
/// <summary>每帧推进出块动画:只改状态文字和进度条,不重建窗口。</summary>