refactor: keep ToolApp as single root view
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|
||||||
|
var host = new android.widget.FrameLayout(context);
|
||||||
|
if (contentView) {
|
||||||
try { contentView.setBackground(null); } catch(eBg) { safeLog(null, 'e', "catch " + String(eBg)); }
|
try { contentView.setBackground(null); } catch(eBg) { safeLog(null, 'e', "catch " + String(eBg)); }
|
||||||
try { contentView.setElevation(0); } catch(eEl) { safeLog(null, 'e', "catch " + String(eEl)); }
|
try { contentView.setElevation(0); } catch(eEl) { safeLog(null, 'e', "catch " + String(eEl)); }
|
||||||
root.addView(contentView, new android.widget.LinearLayout.LayoutParams(-1, 0, 1));
|
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);
|
||||||
|
|
||||||
|
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);
|
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");
|
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));
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
37
scripts/verify_toolapp_single_root.py
Normal file
37
scripts/verify_toolapp_single_root.py
Normal 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")
|
||||||
Reference in New Issue
Block a user