refactor: keep ToolApp as single root view

This commit is contained in:
7015725
2026-05-13 07:27:39 +08:00
parent 2a53c872eb
commit 080cb9abf7
6 changed files with 129 additions and 17 deletions

View File

@@ -50,6 +50,10 @@ function FloatBallAppWM(logger) {
toolAppActive: false, toolAppActive: false,
toolAppNavStack: [], toolAppNavStack: [],
toolAppRoute: null, toolAppRoute: null,
toolAppRoot: null,
toolAppContentHost: null,
toolAppTitleView: null,
toolAppBackButton: null,
settingsGroupKey: null, settingsGroupKey: null,
mask: null, mask: null,

View File

@@ -183,10 +183,17 @@ FloatBallAppWM.prototype.hideViewerPanel = function() {
if (!this.state.addedViewer) return; if (!this.state.addedViewer) return;
if (!this.state.viewerPanel) return; if (!this.state.viewerPanel) return;
var oldViewerType = String(this.state.viewerPanelType || "");
this.safeRemoveView(this.state.viewerPanel, "viewerPanel"); this.safeRemoveView(this.state.viewerPanel, "viewerPanel");
this.state.viewerPanel = null; this.state.viewerPanel = null;
this.state.viewerPanelLp = null; this.state.viewerPanelLp = null;
this.state.viewerPanelType = null; this.state.viewerPanelType = null;
if (oldViewerType === "tool_app") {
this.state.toolAppRoot = null;
this.state.toolAppContentHost = null;
this.state.toolAppTitleView = null;
this.state.toolAppBackButton = null;
}
this.state.addedViewer = false; this.state.addedViewer = false;
this.hideMask(); this.hideMask();

View File

@@ -509,6 +509,10 @@ FloatBallAppWM.prototype.closeToolApp = function() {
this.state.toolAppNavStack = []; this.state.toolAppNavStack = [];
this.state.settingsGroupKey = null; this.state.settingsGroupKey = null;
this.hideViewerPanel(); this.hideViewerPanel();
this.state.toolAppRoot = null;
this.state.toolAppContentHost = null;
this.state.toolAppTitleView = null;
this.state.toolAppBackButton = null;
} catch (e) { safeLog(this.L, 'e', "closeToolApp fail: " + String(e)); } } catch (e) { safeLog(this.L, 'e', "closeToolApp fail: " + String(e)); }
}; };
@@ -527,11 +531,10 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
bar.setPadding(this.dp(8), this.dp(8), this.dp(8), this.dp(6)); bar.setPadding(this.dp(8), this.dp(8), this.dp(8), this.dp(6));
var btnBack = this.ui.createFlatButton(this, canBack ? "" : "", C.primary, function() { var btnBack = this.ui.createFlatButton(this, canBack ? "" : "", C.primary, function() {
if (canBack) self.popToolAppPage("topbar"); self.popToolAppPage("topbar");
}); });
btnBack.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 24); btnBack.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 24);
btnBack.setPadding(this.dp(8), 0, this.dp(8), 0); btnBack.setPadding(this.dp(8), 0, this.dp(8), 0);
if (!canBack) btnBack.setVisibility(android.view.View.INVISIBLE);
bar.addView(btnBack, new android.widget.LinearLayout.LayoutParams(this.dp(42), this.dp(38))); bar.addView(btnBack, new android.widget.LinearLayout.LayoutParams(this.dp(42), this.dp(38)));
var tvTitle = new android.widget.TextView(context); var tvTitle = new android.widget.TextView(context);
@@ -552,12 +555,58 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
bar.addView(btnClose, new android.widget.LinearLayout.LayoutParams(this.dp(42), this.dp(38))); bar.addView(btnClose, new android.widget.LinearLayout.LayoutParams(this.dp(42), this.dp(38)));
root.addView(bar, new android.widget.LinearLayout.LayoutParams(-1, this.dp(52))); root.addView(bar, new android.widget.LinearLayout.LayoutParams(-1, this.dp(52)));
try { contentView.setBackground(null); } catch(eBg) { safeLog(null, 'e', "catch " + String(eBg)); } var host = new android.widget.FrameLayout(context);
try { contentView.setElevation(0); } catch(eEl) { safeLog(null, 'e', "catch " + String(eEl)); } if (contentView) {
root.addView(contentView, new android.widget.LinearLayout.LayoutParams(-1, 0, 1)); try { contentView.setBackground(null); } catch(eBg) { safeLog(null, 'e', "catch " + String(eBg)); }
try { contentView.setElevation(0); } catch(eEl) { safeLog(null, 'e', "catch " + String(eEl)); }
host.addView(contentView, new android.widget.FrameLayout.LayoutParams(-1, -1));
}
root.addView(host, new android.widget.LinearLayout.LayoutParams(-1, 0, 1));
this.state.toolAppRoot = root;
this.state.toolAppContentHost = host;
this.state.toolAppTitleView = tvTitle;
this.state.toolAppBackButton = btnBack;
this.updateToolAppShellChrome(title, canBack);
return root; return root;
}; };
FloatBallAppWM.prototype.ensureToolAppShell = function() {
try {
if (this.state.toolAppRoot && this.state.toolAppContentHost) return this.state.toolAppRoot;
return this.buildToolAppShell(null, "ToolHub", false);
} catch (e) {
safeLog(this.L, 'e', "ensureToolAppShell fail: " + String(e));
}
return null;
};
FloatBallAppWM.prototype.updateToolAppShellChrome = function(title, canBack) {
try {
if (this.state.toolAppTitleView) this.state.toolAppTitleView.setText(String(title || "ToolHub"));
if (this.state.toolAppBackButton) {
this.state.toolAppBackButton.setText(canBack ? "" : "");
this.state.toolAppBackButton.setVisibility(canBack ? android.view.View.VISIBLE : android.view.View.INVISIBLE);
this.state.toolAppBackButton.setEnabled(!!canBack);
}
} catch (e) { safeLog(this.L, 'w', "updateToolAppShellChrome fail: " + String(e)); }
};
FloatBallAppWM.prototype.setToolAppContent = function(contentView) {
try {
var host = this.state.toolAppContentHost;
if (!host || !contentView) return false;
host.removeAllViews();
try { contentView.setBackground(null); } catch(eBg) { safeLog(null, 'e', "catch " + String(eBg)); }
try { contentView.setElevation(0); } catch(eEl) { safeLog(null, 'e', "catch " + String(eEl)); }
host.addView(contentView, new android.widget.FrameLayout.LayoutParams(-1, -1));
return true;
} catch (e) {
safeLog(this.L, 'e', "setToolAppContent fail: " + String(e));
}
return false;
};
FloatBallAppWM.prototype.showToolApp = function(route, resetStack) { FloatBallAppWM.prototype.showToolApp = function(route, resetStack) {
if (this.state.closing) return; if (this.state.closing) return;
var r = this.isToolAppRoute(route) ? String(route) : "settings"; var r = this.isToolAppRoute(route) ? String(route) : "settings";
@@ -565,7 +614,6 @@ FloatBallAppWM.prototype.showToolApp = function(route, resetStack) {
this.touchActivity(); this.touchActivity();
this.hideMainPanel(); this.hideMainPanel();
this.hideSettingsPanel(); this.hideSettingsPanel();
this.hideViewerPanel();
this.showMask(); this.showMask();
this.state.toolAppActive = true; this.state.toolAppActive = true;
this.state.toolAppRoute = r; this.state.toolAppRoute = r;
@@ -582,7 +630,11 @@ FloatBallAppWM.prototype.showToolApp = function(route, resetStack) {
} }
var raw = this.buildPanelView(r); var raw = this.buildPanelView(r);
var shell = this.buildToolAppShell(raw, this.getToolAppTitle(r), this.state.toolAppNavStack.length > 1); var shell = this.ensureToolAppShell();
if (!shell) throw "ToolApp shell missing";
this.updateToolAppShellChrome(this.getToolAppTitle(r), this.state.toolAppNavStack.length > 1);
this.setToolAppContent(raw);
var maxW = Math.floor(this.state.screen.w * 0.92); var maxW = Math.floor(this.state.screen.w * 0.92);
var maxH = Math.floor(this.state.screen.h * 0.82); var maxH = Math.floor(this.state.screen.h * 0.82);
shell.measure( shell.measure(
@@ -595,8 +647,20 @@ FloatBallAppWM.prototype.showToolApp = function(route, resetStack) {
if (!lp0) lp0 = new android.view.ViewGroup.LayoutParams(pw, ph); if (!lp0) lp0 = new android.view.ViewGroup.LayoutParams(pw, ph);
lp0.width = pw; lp0.height = ph; lp0.width = pw; lp0.height = ph;
shell.setLayoutParams(lp0); shell.setLayoutParams(lp0);
var pos = this.getBestPanelPosition(pw, ph, this.state.ballLp.x, this.state.ballLp.y, this.getDockInfo().ballSize);
this.addPanel(shell, pos.x, pos.y, "tool_app"); if (!this.state.addedViewer || this.state.viewerPanel !== shell) {
var pos = this.getBestPanelPosition(pw, ph, this.state.ballLp.x, this.state.ballLp.y, this.getDockInfo().ballSize);
this.addPanel(shell, pos.x, pos.y, "tool_app");
} else {
try {
if (this.state.viewerPanelLp) {
this.state.viewerPanelLp.width = pw;
this.state.viewerPanelLp.height = ph;
this.state.wm.updateViewLayout(shell, this.state.viewerPanelLp);
}
} catch (eUpd) { safeLog(this.L, 'w', "tool_app update layout fail: " + String(eUpd)); }
try { shell.requestFocus(); } catch (eFocus) {}
}
} catch (e) { } catch (e) {
this.state.toolAppActive = false; this.state.toolAppActive = false;
safeLog(this.L, 'e', "showToolApp fail route=" + r + " err=" + String(e)); safeLog(this.L, 'e', "showToolApp fail route=" + r + " err=" + String(e));

View File

@@ -6,8 +6,8 @@
"size": 52546 "size": 52546
}, },
"th_02_core.js": { "th_02_core.js": {
"sha256": "72c208f17d79c35926f30ab00482f0cf901d7aa42a2c703836e35b6d9953840a", "sha256": "23b94eb4afbf978a75363b412b542d4f2b1f5260c63ab966dcf5b44076a05b6f",
"size": 4313 "size": 4423
}, },
"th_03_icon.js": { "th_03_icon.js": {
"sha256": "717f7f37474d3616c2cd944581455f600020a850ec8812100d0546ec1302c987", "sha256": "717f7f37474d3616c2cd944581455f600020a850ec8812100d0546ec1302c987",
@@ -34,8 +34,8 @@
"size": 7938 "size": 7938
}, },
"th_09_animation.js": { "th_09_animation.js": {
"sha256": "35cb81040678cfb164992f1369fdb303424457fafa6a6fe0c74cbf9736f1263d", "sha256": "8c8c0c166816c36569923de31c2951d16fce5ca2e68168cb6190283e058d14c3",
"size": 34943 "size": 35207
}, },
"th_10_shell.js": { "th_10_shell.js": {
"sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc", "sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc",
@@ -58,8 +58,8 @@
"size": 237123 "size": 237123
}, },
"th_15_extra.js": { "th_15_extra.js": {
"sha256": "b48c16ea5cbe5b9033d593eb18ef3028c0d661c65cbf2ad8f3f650ee55bbe2e4", "sha256": "554092fb880e1f645e8665feac752b7412c3a9b604f960ad73f7fa889c8e39a8",
"size": 71280 "size": 73786
}, },
"th_16_entry.js": { "th_16_entry.js": {
"sha256": "e7c99c3dfbd6aedab05551426955081ae6cae034754f2f557cefa01dc75dc001", "sha256": "e7c99c3dfbd6aedab05551426955081ae6cae034754f2f557cefa01dc75dc001",
@@ -68,5 +68,5 @@
}, },
"keyId": "toolhub-targets-2026-rsa3072", "keyId": "toolhub-targets-2026-rsa3072",
"schema": 2, "schema": 2,
"version": 20260512232015 "version": 20260512232713
} }

View File

@@ -1 +1 @@
PnK7gl+dD9S3+8iK6E0lRmWVT8ike/ZOKniNy3O3yIdUGIkLiMBQXSLshHsj4bL15MQOweu8o9FLSEgqj1Htk4Viao7K8QbYhaf92LMVnPMXiPcA0d6t/v0VO/QSNQT5h74dVCk9fnwBlzwrGrSsRUU5ejuwyDMyEOPFhZQLnetpjql2NSRQEOOX0UNfAT4BHjV/MSchiAzJE+wcAPudNLqdOGjKN2flmU8YVOt9zc2PFszCYW4gtWMp0ETCkp3Uw3BbY8FPkbz8u17zM8+WDrrHBUMAoMtUULtcHX0EBgrRsCvLPeSKJSWuJaltLYA5A+tAGz9w2BccptGrQhO4Sn7rMWOpqn+2ajkiutTllPYPGvOTSI6m+GiOvctuIG9NyeR8BJEdX4IVkY9f+wh2Qmp1HliKU6YOoy/hvpAa36nm1UNGzYuJ195TqBv6CFU6eEkdLb0IXHvDnmHX3KB39q5OyItyST5eKKnQ5irEo3AkPTOktqnJSSbGy4IbxOwD Y5kHcy+jDtyrB0AAvEgLdWkQr85CJ7UAvOtzDrp93yI2c+PAItrzWjh+inSskBv7UBa7bHIDXGHVPBN9O5qIPn2Zgb01baozxz+KqO4WomLdEKWTOrRhrla2rJAKf7hcrTLnI2WjHWgg2kMikypvm1S45vcwu1u4uVBEFwE9EExrvjmQiHShqWTJ8ejiMbV3AqQFKCA0QpLujPscedW8YFJOpTJarwKIwSTuAGJdLtKh7ixgzB46kcqkxb+bVdi0+kEblY+heYlTxP5p7F75n0X1aIOuRXYnuGf3ODQi+rioTHYRTQGoJ36SNzxgIHS0JyEDgeduJEhaYaYXLz7BaKFFsJ+2SWPyKS0rx+XW+j91Am9C+wCXKNcPa5/r1y1D/hxtEHSK/YEYVX7IYrCNx3xMJVfILzpCB9lFpEiHNjw0qZU/hjXnRzJhstkK7wrMt6kfvmX7feG34a3t8jlDa0H7Kc3BfkO1wn+wT/P/VurdiaEa5xPU6OPUnyb4fdHY

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
import pathlib
import re
import sys
root = pathlib.Path(__file__).resolve().parents[1]
core = (root / "code" / "th_02_core.js").read_text(encoding="utf-8")
extra = (root / "code" / "th_15_extra.js").read_text(encoding="utf-8")
checks = []
def check(name, ok):
checks.append((name, bool(ok)))
check("state keeps a single ToolApp root view", "toolAppRoot" in core)
check("state keeps ToolApp content host", "toolAppContentHost" in core)
check("state keeps ToolApp title view", "toolAppTitleView" in core)
check("state keeps ToolApp back button", "toolAppBackButton" in core)
check("has ensureToolAppShell", "FloatBallAppWM.prototype.ensureToolAppShell" in extra)
check("has updateToolAppShellChrome", "FloatBallAppWM.prototype.updateToolAppShellChrome" in extra)
check("has setToolAppContent", "FloatBallAppWM.prototype.setToolAppContent" in extra)
show_match = re.search(r"FloatBallAppWM\.prototype\.showToolApp\s*=\s*function\([^)]*\)\s*\{(?P<body>.*?)\n\};", extra, re.S)
show_body = show_match.group("body") if show_match else ""
check("showToolApp exists", bool(show_match))
check("showToolApp does not remove viewer on page switch", "this.hideViewerPanel();" not in show_body)
check("showToolApp ensures shell once", "this.ensureToolAppShell" in show_body)
check("showToolApp swaps content host", "this.setToolAppContent" in show_body)
check("showToolApp only addPanel when shell not added", "!this.state.addedViewer" in show_body and "this.addPanel" in show_body)
failed = [name for name, ok in checks if not ok]
if failed:
print("ToolApp single-root verification FAILED:")
for name in failed:
print(" - " + name)
sys.exit(1)
print("ToolApp single-root verification OK")