diff --git a/code/th_02_core.js b/code/th_02_core.js index d2fd4b6..3240043 100644 --- a/code/th_02_core.js +++ b/code/th_02_core.js @@ -43,6 +43,11 @@ function FloatBallAppWM(logger) { viewerPanelLp: null, viewerPanelType: null, + // 设置类 UI App 化:单窗口页面栈(settings -> 子页面 -> 编辑页) + toolAppActive: false, + toolAppNavStack: [], + toolAppRoute: null, + mask: null, maskLp: null, diff --git a/code/th_09_animation.js b/code/th_09_animation.js index c8b2e5a..62883e6 100644 --- a/code/th_09_animation.js +++ b/code/th_09_animation.js @@ -205,6 +205,9 @@ FloatBallAppWM.prototype.handlePanelBack = function(which, reason) { if (this.state.addedViewer) { var vt = String(this.state.viewerPanelType || w || "viewer"); + if (vt === "tool_app" && this.state.toolAppActive && this.popToolAppPage) { + return this.popToolAppPage(reason || "back_key"); + } if (vt === "btn_editor") { if (this.state.editingButtonIndex !== null && this.state.editingButtonIndex !== undefined) { this.state.editingButtonIndex = null; @@ -328,6 +331,9 @@ FloatBallAppWM.prototype.hideAllPanels = function() { this.hideMainPanel(); this.hideSettingsPanel(); this.hideViewerPanel(); + this.state.toolAppActive = false; + this.state.toolAppRoute = null; + this.state.toolAppNavStack = []; this.hideMask(); this._clearHeavyCachesIfAllHidden("hideAllPanels"); diff --git a/code/th_14_panels.js b/code/th_14_panels.js index cc1993b..3e60a69 100644 --- a/code/th_14_panels.js +++ b/code/th_14_panels.js @@ -54,8 +54,12 @@ FloatBallAppWM.prototype.buildSettingsPanelView = function() { // [恢复] 按钮管理 var btnMgr = this.ui.createFlatButton(this, "按钮管理", C.primary, function() { - self.hideSettingsPanel(); - self.showPanelAvoidBall("btn_editor"); + if (self.state.toolAppActive && self.pushToolAppPage) { + self.pushToolAppPage("btn_editor"); + } else { + self.hideSettingsPanel(); + self.showPanelAvoidBall("btn_editor"); + } }); btnMgr.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13); header.addView(btnMgr); @@ -118,7 +122,8 @@ FloatBallAppWM.prototype.buildSettingsPanelView = function() { self.state.previewMode = false; if (self.state.addedPanel) self.hideMainPanel(); - self.hideSettingsPanel(); + if (self.state.toolAppActive && self.closeToolApp) self.closeToolApp(); + else self.hideSettingsPanel(); if (r && r.ok) self.toast("已确认并生效"); else self.toast("确认失败: " + (r && r.reason ? r.reason : (r && r.err ? r.err : "unknown"))); @@ -410,7 +415,8 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { // 新增按钮 (右侧) var btnAdd = self.ui.createSolidButton(self, "新增", C.primary, android.graphics.Color.WHITE, function() { self.state.editingButtonIndex = -1; - refreshPanel(); + if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("btn_editor"); + else refreshPanel(); }); // 调整新增按钮样式,使其更紧凑 btnAdd.setPadding(self.dp(12), self.dp(6), self.dp(12), self.dp(6)); @@ -463,7 +469,8 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { card.setOnClickListener(new android.view.View.OnClickListener({ onClick: function() { self.state.editingButtonIndex = idx; - refreshPanel(); + if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("btn_editor"); + else refreshPanel(); } })); diff --git a/code/th_15_extra.js b/code/th_15_extra.js index ed5745c..9c3d4bf 100644 --- a/code/th_15_extra.js +++ b/code/th_15_extra.js @@ -402,7 +402,7 @@ FloatBallAppWM.prototype.addPanel = function(panel, x, y, which) { if (this.state.closing) return; // Determine if this panel should be modal (blocking background touches, better for IME) - var isModal = (which === "settings" || which === "btn_editor" || which === "schema_editor"); + var isModal = (which === "settings" || which === "btn_editor" || which === "schema_editor" || which === "tool_app"); var flags; if (isModal) { @@ -477,9 +477,179 @@ FloatBallAppWM.prototype.addPanel = function(panel, x, y, which) { } }; +// =======================【设置类 UI:App 页面栈实验框架】====================== +FloatBallAppWM.prototype.isToolAppRoute = function(route) { + var r = String(route || ""); + return r === "settings" || r === "btn_editor" || r === "schema_editor"; +}; + +FloatBallAppWM.prototype.getToolAppTitle = function(route) { + var r = String(route || "settings"); + if (r === "settings") return "ToolHub 设置"; + if (r === "btn_editor") { + if (this.state.editingButtonIndex !== null && this.state.editingButtonIndex !== undefined) { + return (this.state.editingButtonIndex === -1) ? "新增按钮" : "编辑按钮"; + } + return "按钮管理"; + } + if (r === "schema_editor") { + if (this.state.editingSchemaIndex !== null && this.state.editingSchemaIndex !== undefined) { + return (this.state.editingSchemaIndex === -1) ? "新增布局项" : "编辑布局项"; + } + return "布局管理"; + } + return "ToolHub"; +}; + +FloatBallAppWM.prototype.closeToolApp = function() { + try { + this.state.toolAppActive = false; + this.state.toolAppRoute = null; + this.state.toolAppNavStack = []; + this.hideViewerPanel(); + } catch (e) { safeLog(this.L, 'e', "closeToolApp fail: " + String(e)); } +}; + +FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBack) { + var self = this; + var isDark = this.isDarkTheme(); + var C = this.ui.colors; + var root = new android.widget.LinearLayout(context); + root.setOrientation(android.widget.LinearLayout.VERTICAL); + root.setBackground(this.ui.createRoundDrawable(isDark ? C.bgDark : C.bgLight, this.dp(18))); + try { root.setElevation(this.dp(10)); } catch(eElev) { safeLog(null, 'e', "catch " + String(eElev)); } + + var bar = new android.widget.LinearLayout(context); + bar.setOrientation(android.widget.LinearLayout.HORIZONTAL); + bar.setGravity(android.view.Gravity.CENTER_VERTICAL); + bar.setPadding(this.dp(8), this.dp(8), this.dp(8), this.dp(6)); + + var btnBack = this.ui.createFlatButton(this, canBack ? "‹" : "", C.primary, function() { + if (canBack) self.popToolAppPage("topbar"); + }); + btnBack.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 24); + 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))); + + var tvTitle = new android.widget.TextView(context); + tvTitle.setText(String(title || "ToolHub")); + tvTitle.setTextColor(isDark ? C.textPriDark : C.textPriLight); + tvTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 16); + tvTitle.setTypeface(null, android.graphics.Typeface.BOLD); + tvTitle.setGravity(android.view.Gravity.CENTER_VERTICAL); + var titleLp = new android.widget.LinearLayout.LayoutParams(0, -1); + titleLp.weight = 1; + bar.addView(tvTitle, titleLp); + + var btnClose = this.ui.createFlatButton(this, "✕", isDark ? C.textSecDark : C.textSecLight, function() { + self.closeToolApp(); + }); + btnClose.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 18); + btnClose.setPadding(this.dp(8), 0, this.dp(8), 0); + 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))); + + try { contentView.setBackground(null); } catch(eBg) { safeLog(null, 'e', "catch " + String(eBg)); } + try { contentView.setElevation(0); } catch(eEl) { safeLog(null, 'e', "catch " + String(eEl)); } + root.addView(contentView, new android.widget.LinearLayout.LayoutParams(-1, 0, 1)); + return root; +}; + +FloatBallAppWM.prototype.showToolApp = function(route, resetStack) { + if (this.state.closing) return; + var r = this.isToolAppRoute(route) ? String(route) : "settings"; + try { + this.touchActivity(); + this.hideMainPanel(); + this.hideSettingsPanel(); + this.hideViewerPanel(); + this.showMask(); + this.state.toolAppActive = true; + this.state.toolAppRoute = r; + if (resetStack || !this.state.toolAppNavStack || !this.state.toolAppNavStack.length) { + this.state.toolAppNavStack = [{ route: r }]; + } else { + this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1] = { route: r }; + } + + var raw = this.buildPanelView(r); + var shell = this.buildToolAppShell(raw, this.getToolAppTitle(r), this.state.toolAppNavStack.length > 1); + var maxW = Math.floor(this.state.screen.w * 0.92); + var maxH = Math.floor(this.state.screen.h * 0.82); + shell.measure( + android.view.View.MeasureSpec.makeMeasureSpec(maxW, android.view.View.MeasureSpec.AT_MOST), + android.view.View.MeasureSpec.makeMeasureSpec(maxH, android.view.View.MeasureSpec.AT_MOST) + ); + var pw = Math.max(this.dp(300), Math.min(maxW, shell.getMeasuredWidth())); + var ph = Math.max(this.dp(380), Math.min(maxH, shell.getMeasuredHeight())); + var lp0 = shell.getLayoutParams(); + if (!lp0) lp0 = new android.view.ViewGroup.LayoutParams(pw, ph); + lp0.width = pw; lp0.height = ph; + 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"); + } catch (e) { + this.state.toolAppActive = false; + safeLog(this.L, 'e', "showToolApp fail route=" + r + " err=" + String(e)); + try { this.toast("设置页面显示失败: " + String(e)); } catch(et) {} + } +}; + +FloatBallAppWM.prototype.pushToolAppPage = function(route) { + if (!this.isToolAppRoute(route)) return; + if (!this.state.toolAppNavStack) this.state.toolAppNavStack = []; + this.state.toolAppNavStack.push({ route: String(route) }); + this.showToolApp(route, false); +}; + +FloatBallAppWM.prototype.replaceToolAppPage = function(route) { + if (!this.isToolAppRoute(route)) return; + if (!this.state.toolAppNavStack || !this.state.toolAppNavStack.length) this.state.toolAppNavStack = [{ route: String(route) }]; + else this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1] = { route: String(route) }; + this.showToolApp(route, false); +}; + +FloatBallAppWM.prototype.popToolAppPage = function(reason) { + try { + var curRoute = this.state.toolAppRoute ? String(this.state.toolAppRoute) : ""; + if (curRoute === "btn_editor" && this.state.editingButtonIndex !== null && this.state.editingButtonIndex !== undefined) { + this.state.editingButtonIndex = null; + this.state.keepBtnEditorState = true; + } + if (curRoute === "schema_editor" && this.state.editingSchemaIndex !== null && this.state.editingSchemaIndex !== undefined) { + this.state.editingSchemaIndex = null; + this.state.keepSchemaEditorState = true; + } + if (!this.state.toolAppNavStack || this.state.toolAppNavStack.length <= 1) { + this.closeToolApp(); + return true; + } + this.state.toolAppNavStack.pop(); + var top = this.state.toolAppNavStack[this.state.toolAppNavStack.length - 1]; + this.showToolApp(top && top.route ? top.route : "settings", false); + return true; + } catch (e) { + safeLog(this.L, 'e', "popToolAppPage fail reason=" + String(reason || "") + " err=" + String(e)); + } + return false; +}; + FloatBallAppWM.prototype.showPanelAvoidBall = function(which) { if (this.state.closing) return; + // 设置入口先走 AppShell + 页面栈;子页面在 AppShell 内刷新,逐步替换旧多浮窗模式。 + if (this.isToolAppRoute && this.isToolAppRoute(which)) { + if (which === "settings" && !this.state.toolAppActive) { + this.showToolApp("settings", true); + return; + } + if (this.state.toolAppActive) { + this.replaceToolAppPage(which); + return; + } + } + // 优化:如果是刷新编辑器面板(btn_editor/schema_editor),且面板已存在,则直接更新内容,避免闪烁 if ((which === "btn_editor" || which === "schema_editor") && this.state.addedViewer && this.state.viewerPanel) { try { diff --git a/manifest.json b/manifest.json index 6b754df..d4e82df 100644 --- a/manifest.json +++ b/manifest.json @@ -6,8 +6,8 @@ "size": 52546 }, "th_02_core.js": { - "sha256": "f44f88f0ce3f44f0d1675e55a9f0b66d831caa7819dda54aef6e308ec27faaeb", - "size": 3934 + "sha256": "3d77bcdbe588cd173f6ed4d20c01bea42798acf9a07cd9392f8679f7cccc0ad1", + "size": 4099 }, "th_03_icon.js": { "sha256": "717f7f37474d3616c2cd944581455f600020a850ec8812100d0546ec1302c987", @@ -34,8 +34,8 @@ "size": 7938 }, "th_09_animation.js": { - "sha256": "7120d208910955a2a163c4ad535b2eca674f7a0c2c462ef2f03bad11c9511933", - "size": 27541 + "sha256": "e42d72199c13849b3222719dadbcde7fca9ead69c94bf46c01f831e36adb920f", + "size": 27794 }, "th_10_shell.js": { "sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc", @@ -54,12 +54,12 @@ "size": 20386 }, "th_14_panels.js": { - "sha256": "cf18cb06a9e221b671360f94040542c1c27e8776bb5037abe4f4b0f3de3e1073", - "size": 217347 + "sha256": "1356191b8d4da5cc668282c4d3aeb9d0a016ceb2fae0baf2d6b691606b571733", + "size": 217779 }, "th_15_extra.js": { - "sha256": "ed56b19a5a798a785c024eb931140ded69d16921d2e87ad4ccd861df1c1907d8", - "size": 62936 + "sha256": "ffcdc8f8e4dda94658b1e32e4c4ed61e58d4525306a4a1697cccf3635b942d28", + "size": 70523 }, "th_16_entry.js": { "sha256": "e7c99c3dfbd6aedab05551426955081ae6cae034754f2f557cefa01dc75dc001", @@ -68,5 +68,5 @@ }, "keyId": "toolhub-targets-2026-rsa3072", "schema": 2, - "version": 20260512022403 + "version": 20260512024621 } diff --git a/manifest.sig b/manifest.sig index 27a10b3..83e8888 100644 --- a/manifest.sig +++ b/manifest.sig @@ -1 +1 @@ -mNKpW6JxqzeRVFYnEjJcxIogH2bMLbR5rExVdGHMQcOXhtAbamkl6ZLEtUG534wweU95Amku8lxWO+Pe5p2Bg7ufExHqtBgm3eFsDtftGlG7KYOakPvCluGcWZivGMrxFdUq4CsF/Y4dhi9UG9Dtr2X8UdnKQRXLIMtPW+EpDvZvheg4G+7hsEKOYyRzkX1HK8/KEUaaiDogr7Hp+rT0jPa0iCMBn3fiLAIODYMWj0mzjML7WqtBOwXBoLGSeHxJ7BwJd1USxQh+pMo0/jxf/Uas7oTue67bYpyub9v2ESAfiLIg+qijP9oQRnptvtSA99c0Qj/8BJLiXIl6AD1Qp+PhnJwmGgQg62OtJdp7ly0zPz51UbULXfPwqj9djguac1yN7qVGQroT2oo93brZMpV3iRwQw2ov2E/efFf4iXSXnd/aMbzozUNQtqINnsWfZsrjBNxuDLLHvuSUh5h19/Yok+5EczPL5iZDl/W1GNmurrDvOhnTCZjuVS7t97Zt +cSL09aFJ2OR3BEJ0OwDHjWNE/DzW6d74CBwm8pkomomQc3c+EM5m8OF6+ahQxE0s+/W7p/8bTRxQyqNbDwEwSwcXCp0A1BYCkLKq3Zl6weSk45lamAqb/OsFJM4t+T+VJ4bA4FJ0jMF/R1mMDY1TpgztS5EwST6bSRLuMQDflY12RcGCDQsGwunDKcIRpJCrxZFvs6rgEANNQ6AVzW7SbT18qUk3LgUGehtpKAEtEU+87+4R9cIe9TE2OgQtgWn271U2omJF3x8BBXVqiekNI2rpWkxnsqtRpoqaMf7cugy+GcJnxiylQ3eqs1WpfIv02XzbU3vOiPckG7Xvb7R0JON7Z0Z+6FijqHgHLVK8Ga8B4oCF5WrMPccS53d5yTCgIVedi/VSprYlWi4KGC9a0nMS+/EFzJB8en3MU7OVXZt83mnRCYBPJbvcOHfkf6y7UaCyWOmtQb1v69ymGt00UPy2ihXkDVOjkiayvT69icFS5IoAqUWLZwND7DJwJekn