From b36af7f78a5a0a8eebaf1047b9456626451eeeea Mon Sep 17 00:00:00 2001 From: 7015725 Date: Thu, 21 May 2026 23:46:33 +0800 Subject: [PATCH] refactor: split picker module and improve diagnostics --- ToolHub.js | 26 +- ToolHub.js.sha256 | 2 +- code/th_01_base.js | 92 --- code/th_07_shortcut.js | 685 +--------------- code/th_09_animation.js | 8 +- code/th_14_panels.js | 1156 +-------------------------- code/th_15_picker.js | 1109 +++++++++++++++++++++++++ code/th_16_entry.js | 16 +- manifest.json | 26 +- manifest.sig | 2 +- scripts/generate_signed_manifest.py | 2 +- 11 files changed, 1187 insertions(+), 1937 deletions(-) create mode 100644 code/th_15_picker.js diff --git a/ToolHub.js b/ToolHub.js index ee5fba1..bea88da 100644 --- a/ToolHub.js +++ b/ToolHub.js @@ -42,7 +42,9 @@ function writeLog(msg) { var writer = new java.io.FileWriter(f, true); writer.write("[" + ts + "] " + String(msg) + "\n"); writer.close(); - } catch (e) {} + } catch (e) { + try { android.util.Log.e("ToolHub", "writeLog failed: " + String(e)); } catch(eLog) {} + } } function runShell(cmdArr) { @@ -50,7 +52,7 @@ function runShell(cmdArr) { var proc = java.lang.Runtime.getRuntime().exec(cmdArr); proc.waitFor(); return proc.exitValue() === 0; - } catch (e) { return false; } + } catch (e) { writeLog("runShell failed: " + String(e)); return false; } } function checkDirPerms(path) { @@ -64,7 +66,7 @@ function checkDirPerms(path) { var parts = String(line).trim().split(/\s+/); if (parts.length >= 3) return String(parts[0]) === "1000" && String(parts[1]) === "1000" && String(parts[2]) === "700"; } - } catch (e) {} + } catch (e) { writeLog("checkDirPerms failed: " + String(e)); } return false; } @@ -100,7 +102,7 @@ function readTextFile(path) { while ((line = r.readLine()) != null) sb.append(line).append("\n"); r.close(); return String(sb.toString()); - } catch (e) { return null; } + } catch (e) { writeLog("readTextFile failed: " + String(path) + " err=" + String(e)); return null; } } function writeTextFile(path, text) { @@ -112,7 +114,7 @@ function writeTextFile(path, text) { w.write(String(text)); w.close(); return true; - } catch (e) { return false; } + } catch (e) { writeLog("writeTextFile failed: " + String(path) + " err=" + String(e)); return false; } } function readFirstLine(path) { @@ -284,15 +286,15 @@ function ensureVerifiedModule(relPath, destFile) { } var tmpFile = new java.io.File(destFile.getAbsolutePath() + ".tmp"); - try { if (tmpFile.exists()) tmpFile.delete(); } catch (eDelTmp0) {} + try { if (tmpFile.exists()) tmpFile.delete(); } catch (eDelTmp0) { writeLog("tmp delete before download failed: " + String(eDelTmp0)); } var size = downloadFile(GIT_BASE + relPath, tmpFile); if (expectedSize > 0 && size !== expectedSize) { - try { tmpFile.delete(); } catch (eDelSize) {} + try { tmpFile.delete(); } catch (eDelSize) { writeLog("tmp delete after size mismatch failed: " + String(eDelSize)); } throw "manifest size mismatch for " + relPath + ": expected=" + expectedSize + ", got=" + size; } var tmpHash = sha256File(tmpFile.getAbsolutePath()); if (!tmpHash || String(tmpHash).toLowerCase() !== expectedHash) { - try { tmpFile.delete(); } catch (eDelHash) {} + try { tmpFile.delete(); } catch (eDelHash) { writeLog("tmp delete after hash mismatch failed: " + String(eDelHash)); } throw "manifest SHA256 mismatch for " + relPath + ": expected=" + expectedHash + ", actual=" + tmpHash; } var wasNew = !destFile.exists(); @@ -333,7 +335,7 @@ function loadScript(relPath) { geval(String(code)); } catch(e) { var errMsg = "loadScript(" + relPath + ") failed: " + e; - try { android.util.Log.e("ToolHub", errMsg); } catch(eLog) {} + try { android.util.Log.e("ToolHub", errMsg); } catch(eLog) { writeLog("android log failed: " + String(eLog)); } throw errMsg; } } @@ -341,7 +343,7 @@ function loadScript(relPath) { var modules = ["th_01_base.js", "th_02_core.js", "th_03_icon.js", "th_04_theme.js", "th_05_persistence.js", "th_06_icon_parser.js", "th_07_shortcut.js", "th_08_content.js", "th_09_animation.js", "th_10_shell.js", "th_11_action.js", "th_12_rebuild.js", "th_13_panel_ui.js", - "th_14_panels.js", "th_15_extra.js", "th_16_entry.js"]; + "th_14_panels.js", "th_15_picker.js", "th_15_extra.js", "th_16_entry.js"]; var __moduleUpdates = []; var loadErrors = []; var criticalModules = { "th_01_base.js": true, "th_16_entry.js": true }; @@ -353,7 +355,7 @@ for (var i = 0; i < modules.length; i++) { } catch (e) { var modErr = "Module load failed: " + modules[i] + " -> " + String(e); writeLog(modErr); - try { android.util.Log.e("ToolHub", modErr); } catch(eLog) {} + try { android.util.Log.e("ToolHub", modErr); } catch(eLog) { writeLog("android log failed: " + String(eLog)); } loadErrors.push({ module: modules[i], err: String(e) }); if (criticalModules[modules[i]]) throw "Critical module failed: " + modules[i] + " (" + String(e) + ")"; } @@ -404,7 +406,7 @@ var __out = (function() { var startRet = null; try { startRet = app.startAsync(entryInfo, closeRule); } catch (eTop) { - try { logger.fatal("TOP startAsync crash err=" + String(eTop)); } catch(eLog) {} + try { logger.fatal("TOP startAsync crash err=" + String(eTop)); } catch(eLog) { writeLog("logger fatal failed: " + String(eLog)); } startRet = { ok: false, err: String(eTop) }; } var syncInfo = summarizeModuleUpdates(__moduleUpdates); diff --git a/ToolHub.js.sha256 b/ToolHub.js.sha256 index 5cf9220..05a1cc9 100644 --- a/ToolHub.js.sha256 +++ b/ToolHub.js.sha256 @@ -1 +1 @@ -458579d31a727c021e5ceb83db751a52aeede6db087679f40bf6f1ebc5114ae4 ToolHub.js +10291f41f1715ce9182bf463dd728a1f1f9b471e07e179918c6e8e4002fe876c ToolHub.js diff --git a/code/th_01_base.js b/code/th_01_base.js index 14b17db..84bb0a5 100644 --- a/code/th_01_base.js +++ b/code/th_01_base.js @@ -1195,98 +1195,6 @@ function ToolHubLogger(procInfo) { ToolHubLogger.prototype._now = function() { return new Date().getTime(); }; -ToolHubLogger.prototype._initOnce = function() { - try { - if (FileIO.ensureDir(this.dir)) { - this.initOk = true; - this.cleanupOldFiles(); - } else { - this.initOk = false; - this.lastInitErr = "Mkdirs failed: " + this.dir; - } - } catch (e) { - this.initOk = false; - this.lastInitErr = String(e); - } -}; - -ToolHubLogger.prototype.updateConfig = function(cfg) { - if (!cfg) return; - if (typeof cfg.LOG_KEEP_DAYS === "number") this.keepDays = cfg.LOG_KEEP_DAYS; - if (typeof cfg.LOG_ENABLE !== "undefined") this.enable = !!cfg.LOG_ENABLE; - if (typeof cfg.LOG_DEBUG !== "undefined") this.debug = !!cfg.LOG_DEBUG; -}; - -ToolHubLogger.prototype._line = function(level, msg) { - var ts = this._now(); - var d = new Date(ts); - function pad2(x) { return (x < 10 ? "0" : "") + x; } - var t = d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate()) + - " " + pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + ":" + pad2(d.getSeconds()); - return t + " [" + level + "] " + msg + "\n"; -}; - -ToolHubLogger.prototype._scheduleFlush = function() { - if (this._flushTimer) try { this._flushTimer.cancel(); } catch(e) {} - var self = this; - this._flushTimer = new java.util.Timer(); - this._flushTimer.schedule(new java.util.TimerTask({ - run: function() { self._flushBuffer(); } - }), 3000); // 3秒后强制刷新 -}; - -ToolHubLogger.prototype._flushBuffer = function() { - if (this._buffer.length === 0) return; - var content = this._buffer.join(''); - this._buffer = []; - var path = this.dir + "/" + this.prefix + "_" + this._ymd() + ".log"; - FileIO.appendText(path, content); -}; - -ToolHubLogger.prototype._ymd = function() { - var d = new Date(); - return "" + d.getFullYear() + - ((d.getMonth() < 9 ? "0" : "") + (d.getMonth() + 1)) + - ((d.getDate() < 10 ? "0" : "") + d.getDate()); -}; - -ToolHubLogger.prototype._write = function(level, msg) { - if (!this.enable) return false; - this._buffer.push(this._line(level, msg)); - - // 缓冲满或错误级别立即写入 - if (this._buffer.length >= this._bufferSize || level === 'F' || level === 'E') { - this._flushBuffer(); - } else { - this._scheduleFlush(); // 延迟写入 - } - return true; -}; - -ToolHubLogger.prototype.d = function(msg) { if (this.debug) this._write("D", msg); }; -ToolHubLogger.prototype.i = function(msg) { this._write("I", msg); }; -ToolHubLogger.prototype.w = function(msg) { this._write("W", msg); }; -ToolHubLogger.prototype.e = function(msg) { this._write("E", msg); }; -ToolHubLogger.prototype.fatal = function(msg) { this._write("F", msg); this._flushBuffer(); }; - -ToolHubLogger.prototype.cleanupOldFiles = function() { - try { - if (!this.initOk) return false; - var dirF = new java.io.File(this.dir); - var files = dirF.listFiles(); - if (!files) return false; - var now = this._now(); - var cutoff = now - this.keepDays * 24 * 60 * 60 * 1000; - for (var i = 0; i < files.length; i++) { - var f = files[i]; - if (f && f.isFile() && f.getName().indexOf(this.prefix) === 0 && f.lastModified() < cutoff) { - f["delete"](); - } - } - return true; - } catch (e) { return false; }; -}; - ToolHubLogger.prototype._filePathForToday = function() { var name = this.prefix + "_" + this._ymd(this._now()) + ".log"; return this.dir + "/" + name; diff --git a/code/th_07_shortcut.js b/code/th_07_shortcut.js index 3c04ea3..f9a9b62 100644 --- a/code/th_07_shortcut.js +++ b/code/th_07_shortcut.js @@ -1173,683 +1173,20 @@ try { state.root.setVisibility(android.view.View.VISIBLE); } catch(eVis) { safe api.show(opts); }; -// =======================【工具:ShortX 图标库选择器【多宫格自适应排列】====================== +// =======================【工具:ShortX 图标库选择器(兼容入口)】====================== FloatBallAppWM.prototype.showIconPicker = function(opts) { var self = this; var opt = opts || {}; - var onPick = (typeof opt.onPick === "function") ? opt.onPick : null; - var onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null; - - // # 会话隔离 - var sessionId = String(new Date().getTime()) + "_" + Math.random().toString(36).substr(2, 9); - var currentSession = sessionId; - this.__currentIconSession = sessionId; - - function checkSession() { - if (self.__currentIconSession !== currentSession) return false; - return true; + if (typeof self.showShortXIconPickerPopup !== "function") { + try { safeLog(self.L, 'e', "showIconPicker fallback unavailable: showShortXIconPickerPopup missing"); } catch(eLog) {} + return null; } - - // # 单例复用(关键修复:跳过已销毁的实例) - try { - if (self.__iconPickerSingleton && typeof self.__iconPickerSingleton.show === "function") { - self.__iconPickerSingleton.show(opts); - return; - } - } catch(eSingle) { safeLog(null, 'e', "catch " + String(eSingle)); } - - // # 获取图标列表 - var allIcons = []; - try { - allIcons = self.getShortXIconCatalog(true) || []; - } catch(eCatalog) { - safeLog(self.L, 'e', "icon picker catalog failed: " + String(eCatalog)); - } - if (!allIcons || allIcons.length === 0) { - self.toast("无法加载 ShortX 图标库"); - if (onDismiss) try { onDismiss(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); } - return; - } - - var Context = android.content.Context; - var wm = context.getSystemService(Context.WINDOW_SERVICE); - - // # 状态(新增分页状态) - var state = { - destroyed: false, - hidden: false, - root: null, - params: null, - isAdded: false, - query: "", - filteredIcons: allIcons.slice(), - grid: null, - etSearch: null, - tvStat: null, - scrollView: null, - pagerBar: null, // 分页栏 - tvPager: null, // 页码显示 - onDismiss: onDismiss, - onDismissCalled: false, - // 分页 - currentPage: 0, - itemsPerPage: 24, // 动态计算,这里是默认 - totalPages: 1 - }; - - function Li(msg) { try { if (self.L) self.L.i("[iconPicker] " + msg); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } } - function Le(msg) { try { if (self.L) self.L.e("[iconPicker] " + msg); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } } - - // # 列数自适应计算 - function computeColumns() { - try { - var sw = self.state.screen.w; - var sh = self.state.screen.h; - var isLandscape = sw > sh; - var cellMinDp = isLandscape ? 60 : 68; - var paddingTotalDp = 32; - var availablePx = sw - self.dp(paddingTotalDp); - var cols = Math.floor(availablePx / self.dp(cellMinDp)); - if (isLandscape) { - cols = Math.max(6, Math.min(12, cols)); - } else { - cols = Math.max(3, Math.min(6, cols)); - } - return cols; - } catch(e) { return 4; } - } - - // # 计算每页图标数(根据屏幕高度动态计算行数) - function computePageSize() { - try { - var sh = self.state.screen.h; - var maxPanelH = Math.floor(sh * 0.75); - var cols = computeColumns(); - - // 估算各部分占用的 dp 数 - var headerH = 48; // 标题栏 - var sepH = 1; // 分隔线 - var searchH = 72; // 搜索框(含边距) - var footerH = 30; // 状态栏 - var pagerH = 44; // 分页栏 - var panelPad = 24; // 面板内边距 top+bottom - var scrollPad = 16; // 滚动区域内边距 - var fixedDp = headerH + sepH + searchH + footerH + pagerH + panelPad + scrollPad; - - var density = self.state.density || 2.75; - var availableDp = (maxPanelH / density) - fixedDp; - var cellH = 72; // 每个格子大约 72dp(图标+文字+边距) - var rows = Math.max(3, Math.floor(availableDp / cellH)); - - var pageSize = cols * rows; - Li("pageSize calc cols=" + cols + " rows=" + rows + " → " + pageSize + " per page"); - return Math.max(12, pageSize); - } catch(e) { - return 24; - } - } - - // # 过滤 - function filterIcons(q) { - var qLower = ""; - try { qLower = String(q || "").toLowerCase(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - if (!qLower) return allIcons.slice(); - var out = []; - for (var i = 0; i < allIcons.length; i++) { - var ic = allIcons[i]; - if (!ic) continue; - var name = ""; - try { name = String(ic.shortName || ic.name || "").toLowerCase(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - if (name.indexOf(qLower) >= 0) out.push(ic); - } - return out; - } - - // # 分页:跳到指定页 - function goPage(page) { - try { - if (state.totalPages <= 1) return; - if (page < 0) page = 0; - if (page >= state.totalPages) page = state.totalPages - 1; - state.currentPage = page; - buildGrid(); - // 滚回顶部 - try { - if (state.scrollView) state.scrollView.scrollTo(0, 0); - } catch(eScroll) { safeLog(null, 'e', "catch " + String(eScroll)); } - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - - // # 更新统计/页码显示 - function updateStat() { - try { - if (state.tvStat) { - state.tvStat.setText("共 " + String(allIcons.length) + " 个 · 当前 " + String(state.filteredIcons.length) + " 个"); - } - if (state.tvPager) { - state.tvPager.setText(String(state.currentPage + 1) + " / " + String(state.totalPages)); - } - // 更新分页按钮状态 - try { - if (state.pagerBar) { - var childCount = state.pagerBar.getChildCount(); - for (var i = 0; i < childCount; i++) { - var child = state.pagerBar.getChildAt(i); - if (!child) continue; - try { - var tag = String(child.getTag() || ""); - if (tag === "btnPrev") { - child.setEnabled(state.currentPage > 0); - child.setAlpha(state.currentPage > 0 ? 1.0 : 0.35); - } else if (tag === "btnNext") { - child.setEnabled(state.currentPage < state.totalPages - 1); - child.setAlpha(state.currentPage < state.totalPages - 1 ? 1.0 : 0.35); - } - } catch(eTag) { safeLog(null, 'e', "catch " + String(eTag)); } - } - } - } catch(eBar) { safeLog(null, 'e', "catch " + String(eBar)); } - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - - // # 构建网格(只渲染当前页) - function buildGrid() { - try { - if (!state.grid) return; - state.grid.removeAllViews(); - - var cols = computeColumns(); - state.grid.setColumnCount(cols); - - var isDark = self.isDarkTheme(); - var C = self.ui.colors; - var cardColor = isDark ? C.cardDark : C.cardLight; - var textColor = isDark ? C.textPriDark : C.textPriLight; - - var cellSizeDp = 56; - var labelSizeSp = 9; - var cellPadDp = 4; - - // 计算当前页要显示的图标范围 - var startIdx = state.currentPage * state.itemsPerPage; - var endIdx = Math.min(startIdx + state.itemsPerPage, state.filteredIcons.length); - var pageIcons = []; - for (var pi = startIdx; pi < endIdx; pi++) { - pageIcons.push(state.filteredIcons[pi]); - } - - for (var i = 0; i < pageIcons.length; i++) { - (function(idx, iconInfo) { - var cell = new android.widget.LinearLayout(context); - cell.setOrientation(android.widget.LinearLayout.VERTICAL); - cell.setGravity(android.view.Gravity.CENTER); - cell.setPadding(self.dp(cellPadDp), self.dp(cellPadDp), self.dp(cellPadDp), self.dp(cellPadDp)); - - var lp = new android.widget.GridLayout.LayoutParams(); - lp.width = 0; - lp.height = android.widget.GridLayout.LayoutParams.WRAP_CONTENT; - lp.columnSpec = android.widget.GridLayout.spec(android.widget.GridLayout.UNDEFINED, 1, 1.0); - lp.setMargins(self.dp(2), self.dp(2), self.dp(2), self.dp(2)); - cell.setLayoutParams(lp); - - // 图标 - var iv = new android.widget.ImageView(context); - try { - var dr = self.resolveShortXDrawable(iconInfo.name); - if (dr) iv.setImageDrawable(dr); - } catch(eIcon) { safeLog(null, 'e', "catch " + String(eIcon)); } - try { iv.setScaleType(android.widget.ImageView.ScaleType.CENTER_INSIDE); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - var ivLp = new android.widget.LinearLayout.LayoutParams(self.dp(cellSizeDp), self.dp(cellSizeDp)); - iv.setLayoutParams(ivLp); - cell.addView(iv); - - // 文字 - var tv = new android.widget.TextView(context); - var label = ""; - try { label = String(iconInfo.shortName || iconInfo.name || ""); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - if (label.length > 10) label = label.substring(0, 9) + "…"; - tv.setText(label); - tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, labelSizeSp); - tv.setTextColor(textColor); - tv.setGravity(android.view.Gravity.CENTER); - try { tv.setLines(1); tv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - var tvLp = new android.widget.LinearLayout.LayoutParams( - android.widget.LinearLayout.LayoutParams.MATCH_PARENT, - android.widget.LinearLayout.LayoutParams.WRAP_CONTENT - ); - tvLp.topMargin = self.dp(2); - tv.setLayoutParams(tvLp); - cell.addView(tv); - - // 点击效果 - try { - var rippleDr = self.ui.createRippleDrawable(cardColor, self.withAlpha(C.primary, 0.2), self.dp(8)); - cell.setBackground(rippleDr); - } catch(eRipple) { - cell.setBackground(self.ui.createRoundDrawable(cardColor, self.dp(8))); - } - cell.setClickable(true); - - cell.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { - try { - self.touchActivity(); - if (onPick) onPick(iconInfo.name); - hide(); - } catch(eClick) { safeLog(null, 'e', "catch " + String(eClick)); } - } - })); - - state.grid.addView(cell); - })(i, pageIcons[i]); - } - - updateStat(); - } catch(eBuild) { - Le("buildGrid err=" + String(eBuild)); - } - } - - // # 重新渲染(过滤 + 重置到第1页) - function rebuild() { - try { - state.filteredIcons = filterIcons(state.query); - // 重新计算分页 - state.itemsPerPage = computePageSize(); - state.totalPages = Math.max(1, Math.ceil(state.filteredIcons.length / state.itemsPerPage)); - state.currentPage = 0; - buildGrid(); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - - // # 隐藏 - function hide() { - try { - if (state.destroyed || state.hidden) return; - state.hidden = true; - - // 退输入法 - try { - if (state.etSearch) { - state.etSearch.clearFocus(); - var imm = context.getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm) imm.hideSoftInputFromWindow(state.etSearch.getWindowToken(), 0); - } - } catch(eK) { safeLog(null, 'e', "catch " + String(eK)); } - - // 隐藏 View - try { - if (state.root) state.root.setVisibility(android.view.View.GONE); - } catch(eV) { safeLog(null, 'e', "catch " + String(eV)); } - - // 通知外层 - try { - if (state.onDismiss && !state.onDismissCalled) { - state.onDismissCalled = true; - state.onDismiss(); - } - } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); } - - Li("icon picker hidden"); - } catch(eHide) { safeLog(null, 'e', "catch " + String(eHide)); } - } - - // # 销毁 - function destroy() { - try { - if (state.destroyed) return; - state.destroyed = true; - state.hidden = true; - - hide(); - - try { - if (state.isAdded && state.root) { - wm.removeView(state.root); - } - } catch(eR) { safeLog(null, 'e', "catch " + String(eR)); } - - state.isAdded = false; - state.root = null; - - if (self.__currentIconSession === currentSession) { - self.__currentIconSession = null; - } - try { self.__iconPickerSingleton = null; } catch(eS) { safeLog(null, 'e', "catch " + String(eS)); } - - Li("icon picker destroyed"); - } catch(eDes) { safeLog(null, 'e', "catch " + String(eDes)); } - } - - // # 显示(关键修复:正确处理隐藏后重新显示) - function show() { - if (!checkSession()) return; - if (state.destroyed) return; - - // 如果已有实例 - if (state.root && state.isAdded) { - if (state.hidden) { - // 之前被隐藏了,重新显示 - try { - state.hidden = false; - state.onDismissCalled = false; // 重置 dismiss 标志 - state.root.setVisibility(android.view.View.VISIBLE); - // 刷新列数(可能旋转了屏幕)和内容 - rebuild(); - Li("icon picker re-shown (from hidden)"); - return; - } catch(eShow) { - Le("re-show failed: " + String(eShow)); - } - } else { - // 已经是显示状态,什么都不做 - Li("icon picker already visible"); - return; - } - } - - // 如果 root 存在但未添加(不应该发生),清掉重建 - if (state.root && !state.isAdded) { - try { state.root = null; } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - - // ========== 新建面板 ========== - var isDark = self.isDarkTheme(); - var C = self.ui.colors; - var bgColor = isDark ? C.bgDark : C.bgLight; - var textColor = isDark ? C.textPriDark : C.textPriLight; - var subTextColor = isDark ? C.textSecDark : C.textSecLight; - var dividerColor = isDark ? C.dividerDark : C.dividerLight; - var inputBgColor = isDark ? C.inputBgDark : C.inputBgLight; - - var sw = self.state.screen.w; - var sh = self.state.screen.h; - - var panelW = sw - self.dp(32); - var maxPanelH = Math.floor(sh * 0.75); - - // --- Root --- - var root = new android.widget.FrameLayout(context); - root.setBackgroundColor(0x00000000); - root.setOnTouchListener(new JavaAdapter(android.view.View.OnTouchListener, { - onTouch: function(v, e) { - self.touchActivity(); - if (e.getAction() === android.view.MotionEvent.ACTION_DOWN) { - try { - var rect = new android.graphics.Rect(); - if (state.root) { - state.root.getGlobalVisibleRect(rect); - var x = e.getRawX(); - var y = e.getRawY(); - if (!rect.contains(x, y)) { - hide(); - } - } - } catch(eOut) { safeLog(null, 'e', "catch " + String(eOut)); } - } - return false; - } - })); - - // --- 面板容器 --- - var panel = new android.widget.LinearLayout(context); - panel.setOrientation(android.widget.LinearLayout.VERTICAL); - var bgDr = new android.graphics.drawable.GradientDrawable(); - bgDr.setColor(bgColor); - bgDr.setCornerRadius(self.dp(16)); - panel.setBackground(bgDr); - try { panel.setElevation(self.dp(8)); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - panel.setPadding(self.dp(16), self.dp(12), self.dp(16), self.dp(12)); - - var panelLp = new android.widget.FrameLayout.LayoutParams(panelW, android.widget.FrameLayout.LayoutParams.WRAP_CONTENT); - panelLp.gravity = android.view.Gravity.CENTER; - root.addView(panel, panelLp); - - // --- 标题栏 --- - var header = new android.widget.LinearLayout(context); - header.setOrientation(android.widget.LinearLayout.HORIZONTAL); - header.setGravity(android.view.Gravity.CENTER_VERTICAL); - - var tvTitle = new android.widget.TextView(context); - tvTitle.setText("选择图标"); - tvTitle.setTextColor(textColor); - tvTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 16); - tvTitle.setTypeface(null, android.graphics.Typeface.BOLD); - header.addView(tvTitle); - - var spacer = new android.widget.LinearLayout(context); - spacer.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, 0, 1)); - header.addView(spacer); - - var btnClose = new android.widget.TextView(context); - btnClose.setText("✕"); - btnClose.setTextColor(subTextColor); - btnClose.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 18); - btnClose.setPadding(self.dp(8), self.dp(4), self.dp(4), self.dp(4)); - btnClose.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { hide(); } - })); - header.addView(btnClose); - - panel.addView(header); - - // --- 分隔线 --- - var sep = new android.view.View(context); - sep.setLayoutParams(new android.widget.LinearLayout.LayoutParams( - android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(1) - )); - sep.setBackgroundColor(dividerColor); - panel.addView(sep); - - // --- 搜索框 --- - var searchBox = new android.widget.LinearLayout(context); - searchBox.setOrientation(android.widget.LinearLayout.HORIZONTAL); - searchBox.setGravity(android.view.Gravity.CENTER_VERTICAL); - searchBox.setBackground(self.ui.createRoundDrawable(inputBgColor, self.dp(8))); - searchBox.setPadding(self.dp(10), self.dp(6), self.dp(10), self.dp(6)); - var searchLp = new android.widget.LinearLayout.LayoutParams( - android.widget.LinearLayout.LayoutParams.MATCH_PARENT, - android.widget.LinearLayout.LayoutParams.WRAP_CONTENT - ); - searchLp.setMargins(0, self.dp(8), 0, self.dp(8)); - searchBox.setLayoutParams(searchLp); - - var et = new android.widget.EditText(context); - et.setHint("搜索图标名称..."); - try { et.setHintTextColor(subTextColor); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - et.setTextColor(textColor); - et.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); - et.setBackground(null); - et.setSingleLine(true); - var etLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1); - et.setLayoutParams(etLp); - - et.addTextChangedListener(new JavaAdapter(android.text.TextWatcher, { - beforeTextChanged: function(s, start, count, after) {}, - onTextChanged: function(s, start, before, count) {}, - afterTextChanged: function(s) { - try { - state.query = String(s || ""); - rebuild(); - } catch(eTxt) { safeLog(null, 'e', "catch " + String(eTxt)); } - } - })); - - searchBox.addView(et); - state.etSearch = et; - - var btnClear = new android.widget.TextView(context); - btnClear.setText("✕"); - btnClear.setTextColor(subTextColor); - btnClear.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); - btnClear.setPadding(self.dp(6), self.dp(2), self.dp(2), self.dp(2)); - btnClear.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { - try { - et.setText(""); - state.query = ""; - rebuild(); - } catch(eClr) { safeLog(null, 'e', "catch " + String(eClr)); } - } - })); - searchBox.addView(btnClear); - - panel.addView(searchBox); - - // --- 滚动区域 --- - var scroll = new android.widget.ScrollView(context); - try { scroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - try { scroll.setVerticalScrollBarEnabled(false); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - - var grid = new android.widget.GridLayout(context); - state.grid = grid; - scroll.addView(grid); - - var scrollLp = new android.widget.LinearLayout.LayoutParams( - android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0 - ); - scrollLp.weight = 1; - scroll.setLayoutParams(scrollLp); - panel.addView(scroll); - state.scrollView = scroll; - - // --- 底部:状态栏 + 分页栏 --- - // 状态栏 - var footer = new android.widget.LinearLayout(context); - footer.setOrientation(android.widget.LinearLayout.HORIZONTAL); - footer.setGravity(android.view.Gravity.CENTER_VERTICAL); - footer.setPadding(0, self.dp(4), 0, self.dp(4)); - - var tvStat = new android.widget.TextView(context); - tvStat.setTextColor(subTextColor); - tvStat.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); - footer.addView(tvStat); - state.tvStat = tvStat; - - panel.addView(footer); - - // 分页栏 - var pagerBar = new android.widget.LinearLayout(context); - pagerBar.setOrientation(android.widget.LinearLayout.HORIZONTAL); - pagerBar.setGravity(android.view.Gravity.CENTER); - pagerBar.setPadding(0, self.dp(2), 0, 0); - var pagerLp = new android.widget.LinearLayout.LayoutParams( - android.widget.LinearLayout.LayoutParams.MATCH_PARENT, - android.widget.LinearLayout.LayoutParams.WRAP_CONTENT - ); - pagerBar.setLayoutParams(pagerLp); - - // 分页按钮工厂 - function makePagerBtn(text, tag, onClick) { - var btn = new android.widget.TextView(context); - btn.setText(text); - btn.setTag(tag); - btn.setTextColor(C.primary); - btn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13); - btn.setTypeface(null, android.graphics.Typeface.BOLD); - btn.setPadding(self.dp(12), self.dp(6), self.dp(12), self.dp(6)); - try { - var btnBg = self.ui.createRoundDrawable( - self.withAlpha(C.primary, 0.08), - self.dp(6) - ); - btn.setBackground(btnBg); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - btn.setClickable(true); - btn.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { - try { self.touchActivity(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - onClick(); - } - })); - return btn; - } - - var btnPrev = makePagerBtn("◀ 上一页", "btnPrev", function() { - goPage(state.currentPage - 1); - }); - pagerBar.addView(btnPrev); - - // 页码 - var tvPager = new android.widget.TextView(context); - tvPager.setText("1 / 1"); - tvPager.setTextColor(subTextColor); - tvPager.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - tvPager.setPadding(self.dp(16), self.dp(4), self.dp(16), self.dp(4)); - pagerBar.addView(tvPager); - state.tvPager = tvPager; - - var btnNext = makePagerBtn("下一页 ▶", "btnNext", function() { - goPage(state.currentPage + 1); - }); - pagerBar.addView(btnNext); - - panel.addView(pagerBar); - state.pagerBar = pagerBar; - - // --- 添加到 WM --- - var lp = new android.view.WindowManager.LayoutParams( - android.view.WindowManager.LayoutParams.MATCH_PARENT, - android.view.WindowManager.LayoutParams.MATCH_PARENT, - android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, - android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND, - android.graphics.PixelFormat.TRANSLUCENT - ); - lp.dimAmount = 0.5; - lp.gravity = android.view.Gravity.TOP | android.view.Gravity.START; - lp.x = 0; - lp.y = 0; - - try { - wm.addView(root, lp); - state.isAdded = true; - } catch(eAdd) { - Le("addView failed: " + String(eAdd)); - self.toast("图标选择器打开失败"); - if (onDismiss) try { onDismiss(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); } - return; - } - - state.root = root; - state.params = lp; - - // 动画 - try { - panel.setScaleX(0.95); - panel.setScaleY(0.95); - panel.setAlpha(0); - panel.animate() - .scaleX(1) - .scaleY(1) - .alpha(1) - .setDuration(180) - .setInterpolator(new android.view.animation.AccelerateDecelerateInterpolator()) - .start(); - } catch(eA) { safeLog(null, 'e', "catch " + String(eA)); } - - // 初始渲染(会计算分页) - rebuild(); - - Li("icon picker shown icons=" + String(allIcons.length)); - } - - // 入口(关键修复:不在 api.show 中提前设置 state.hidden,让 show() 自己判断) - var api = { - show: function(newOpts) { - try { - var o = newOpts || {}; - onPick = (typeof o.onPick === "function") ? o.onPick : onPick; - onDismiss = (typeof o.onDismiss === "function") ? o.onDismiss : onDismiss; - state.onDismiss = onDismiss; - state.onDismissCalled = false; - } catch(eOpt) { safeLog(null, 'e', "catch " + String(eOpt)); } - // 不在这里设置 state.hidden = false!让内部 show() 自己判断是隐藏重显还是新建 - show(); + return self.showShortXIconPickerPopup({ + currentName: String(opt.currentName || opt.name || ""), + onSelect: function(name) { + if (typeof opt.onPick === "function") opt.onPick(name); + else if (typeof opt.onSelect === "function") opt.onSelect(name); }, - hide: hide, - destroy: destroy - }; - try { self.__iconPickerSingleton = api; } catch(eSet) { safeLog(null, 'e', "catch " + String(eSet)); } - api.show(opts); + onDismiss: (typeof opt.onDismiss === "function") ? opt.onDismiss : null + }); }; - diff --git a/code/th_09_animation.js b/code/th_09_animation.js index 640da7e..a651e3b 100644 --- a/code/th_09_animation.js +++ b/code/th_09_animation.js @@ -127,7 +127,7 @@ FloatBallAppWM.prototype.playBounce = function(v) { FloatBallAppWM.prototype.safeRemoveView = function(v, whichName) { try { if (!v) return { ok: true, skipped: true }; - try { if (this.unregisterPanelPredictiveBack) this.unregisterPanelPredictiveBack(v); } catch (eBack) {} + try { if (this.unregisterPanelPredictiveBack) this.unregisterPanelPredictiveBack(v); } catch (eBack) { safeLog(this.L, 'e', "unregisterPanelPredictiveBack before remove failed: " + String(eBack)); } try { if (whichName === "viewerPanel" && this.state && String(this.state.viewerPanelType || "") === "tool_app" && this.hideToolAppScreenBackStrips) this.hideToolAppScreenBackStrips(); } catch (eStrip) {} this.state.wm.removeView(v); return { ok: true }; @@ -283,7 +283,7 @@ FloatBallAppWM.prototype.hidePanelPredictiveBackIndicator = function() { try { var v = this.state.predictiveBackIndicatorView; if (v && this.state.wm) { - try { this.state.wm.removeView(v); } catch (eRm) {} + try { this.state.wm.removeView(v); } catch (eRm) { safeLog(this.L, 'e', "safeRemoveView removeView failed: " + String(eRm)); } } this.state.predictiveBackIndicatorView = null; this.state.predictiveBackIndicatorLp = null; @@ -398,7 +398,7 @@ FloatBallAppWM.prototype.unregisterPanelPredictiveBack = function(panel) { for (var i = 0; i < entries.length; i++) { var it = entries[i]; if (!it || it.view === panel) { - try { if (it && it.dispatcher && it.callback) it.dispatcher.unregisterOnBackInvokedCallback(it.callback); } catch (eUnreg) {} + try { if (it && it.dispatcher && it.callback) it.dispatcher.unregisterOnBackInvokedCallback(it.callback); } catch (eUnreg) { safeLog(this.L, 'e', "panel predictive unregister failed: " + String(eUnreg)); } } else { kept.push(it); } @@ -993,7 +993,7 @@ FloatBallAppWM.prototype.scheduleScreenReflow = function(reason) { } }), 260); } catch (e0) { - try { this.onScreenChangedReflow(reason); } catch(e1) {} + try { this.onScreenChangedReflow(reason); } catch(e1) { safeLog(this.L, 'e', "onScreenChangedReflow failed: " + String(e1)); } } }; diff --git a/code/th_14_panels.js b/code/th_14_panels.js index 266c02a..9a7ad3e 100644 --- a/code/th_14_panels.js +++ b/code/th_14_panels.js @@ -994,8 +994,8 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { // Placeholder to push buttons to the right header.addView(this.ui.createSpacer(this)); - // 刷新面板辅助函数 - function refreshPanel() { + // 刷新按钮编辑面板辅助函数 + function refreshButtonEditorPanel() { // # 列表滚动位置保持:刷新前记录当前 ScrollView 的 scrollY,避免操作后回到第一页 try { if (__btnEditorListScroll) self.state.btnEditorListScrollY = __btnEditorListScroll.getScrollY(); @@ -1026,7 +1026,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { var btnAdd = self.ui.createSolidButton(self, "添加", T.primary, T.onPrimary, function() { self.state.editingButtonIndex = -1; if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("btn_editor"); - else refreshPanel(); + else refreshButtonEditorPanel(); }); // 调整新增按钮样式,使其更紧凑 btnAdd.setPadding(self.dp(12), self.dp(6), self.dp(12), self.dp(6)); @@ -1061,7 +1061,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { var btnSearch = self.createButtonManagerActionChip("搜索", T.primaryDeep, self.withAlpha(T.primaryDeep, 0.32), function() { try { self.state.buttonManagerQuery = String(searchInput.getValue ? searchInput.getValue() : ""); } catch(eQ) { self.state.buttonManagerQuery = ""; } self.state.btnEditorListScrollY = 0; - refreshPanel(); + refreshButtonEditorPanel(); }); var btnSearchLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT); btnSearchLp.leftMargin = self.dp(8); @@ -1069,7 +1069,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { var btnClearSearch = self.createButtonManagerActionChip("重置", subTextColor, self.withAlpha(subTextColor, 0.24), function() { self.state.buttonManagerQuery = ""; self.state.btnEditorListScrollY = 0; - refreshPanel(); + refreshButtonEditorPanel(); }); var btnClearLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT); btnClearLp.leftMargin = self.dp(6); @@ -1131,7 +1131,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { onClick: function() { self.state.editingButtonIndex = idx; if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("btn_editor"); - else refreshPanel(); + else refreshButtonEditorPanel(); } })); @@ -1195,7 +1195,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { var temp = buttons[idx]; buttons[idx] = buttons[idx - 1]; buttons[idx - 1] = temp; - refreshPanel(); + refreshButtonEditorPanel(); } : null); try { btnUp.setEnabled(canUp); } catch(eUpEn) {} actions.addView(btnUp); @@ -1205,7 +1205,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { var temp = buttons[idx]; buttons[idx] = buttons[idx + 1]; buttons[idx + 1] = temp; - refreshPanel(); + refreshButtonEditorPanel(); } : null); try { var lpDown = new android.widget.LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); @@ -1220,7 +1220,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { btnCfg.enabled = (btnCfg.enabled === false) ? true : false; ConfigManager.saveButtons(buttons); } catch(eTg) { safeLog(null, 'e', "catch " + String(eTg)); } - refreshPanel(); + refreshButtonEditorPanel(); }); try { var lpTg = new android.widget.LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); @@ -1231,7 +1231,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { var btnDel = self.createButtonManagerTextAction("移除", self.withAlpha(C.danger, 0.85), function() { buttons.splice(idx, 1); - refreshPanel(); + refreshButtonEditorPanel(); }); try { var lpDel = new android.widget.LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); @@ -1312,7 +1312,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { self.panels.main = buttons; self.state.tempButtons = null; self.toast("保存成功"); - refreshPanel(); + refreshButtonEditorPanel(); } catch(e) { self.toast("保存失败:" + e); } }); var btnListSaveLp = new android.widget.LinearLayout.LayoutParams(0, self.dp(44)); @@ -3816,7 +3816,7 @@ shortcutWrap.addView(scBody); if (self.state.toolAppActive && self.popToolAppPage) { self.state.keepBtnEditorState = true; self.popToolAppPage("button_edit_cancel"); - } else refreshPanel(); + } else refreshButtonEditorPanel(); }); var btnCancelLp = new android.widget.LinearLayout.LayoutParams(0, self.dp(44)); btnCancelLp.weight = 1; @@ -3918,7 +3918,7 @@ try { if (self.state.toolAppActive && self.popToolAppPage) { self.state.keepBtnEditorState = true; self.popToolAppPage("button_edit_save"); - } else refreshPanel(); + } else refreshButtonEditorPanel(); self.toast("已暂存,请在列表页点击保存"); } catch (e) { self.toast("暂存失败: " + e); @@ -3980,7 +3980,7 @@ FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { // Placeholder to push buttons to right header.addView(this.ui.createSpacer(this)); - function refreshPanel() { + function refreshSchemaEditorPanel() { self.state.keepSchemaEditorState = true; self.showPanelAvoidBall("schema_editor"); } @@ -3991,13 +3991,13 @@ FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { ConfigManager.resetSchema(); self.state.tempSchema = null; self.toast("已重置为默认布局"); - refreshPanel(); + refreshSchemaEditorPanel(); })); header.addView(self.ui.createFlatButton(self, "新增", C.primary, function() { self.state.editingSchemaIndex = -1; if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("schema_editor"); - else refreshPanel(); + else refreshSchemaEditorPanel(); })); var btnClose = self.ui.createFlatButton(self, "✕", C.textSecLight, function() { @@ -4081,7 +4081,7 @@ FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { var temp = schema[idx]; schema[idx] = schema[idx - 1]; schema[idx - 1] = temp; - refreshPanel(); + refreshSchemaEditorPanel(); })); } if (idx < schema.length - 1) { @@ -4089,17 +4089,17 @@ FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { var temp = schema[idx]; schema[idx] = schema[idx + 1]; schema[idx + 1] = temp; - refreshPanel(); + refreshSchemaEditorPanel(); })); } actions.addView(self.ui.createFlatButton(self, "✎", C.primary, function() { self.state.editingSchemaIndex = idx; if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("schema_editor"); - else refreshPanel(); + else refreshSchemaEditorPanel(); })); actions.addView(self.ui.createFlatButton(self, "✕", C.danger, function() { schema.splice(idx, 1); - refreshPanel(); + refreshSchemaEditorPanel(); })); card.addView(actions); @@ -4119,7 +4119,7 @@ FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { if (self.state.toolAppActive && self.popToolAppPage) { self.state.keepSchemaEditorState = true; self.popToolAppPage("schema_edit_back"); - } else refreshPanel(); + } else refreshSchemaEditorPanel(); }); header.addView(btnBack); panel.addView(header); @@ -4173,7 +4173,7 @@ FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { var currIdx = types.indexOf(editItem.type); var nextIdx = (currIdx + 1) % types.length; editItem.type = types[nextIdx]; - refreshPanel(); + refreshSchemaEditorPanel(); }); typeBox.addView(typeBtn); form.addView(typeBox); @@ -4229,7 +4229,7 @@ FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { if (self.state.toolAppActive && self.popToolAppPage) { self.state.keepSchemaEditorState = true; self.popToolAppPage("schema_edit_save"); - } else refreshPanel(); + } else refreshSchemaEditorPanel(); self.toast("已暂存,请在列表页点击保存生效"); } catch (e) { self.toast("暂存失败: " + e); @@ -4245,1114 +4245,4 @@ FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { return panel; }; - - - -// =======================【弹出式选择器(WindowManager 覆盖层)】====================== -FloatBallAppWM.prototype.showPopupOverlay = function(opts) { - var self = this; - var opt = opts || {}; - var title = String(opt.title || ""); - var onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null; - var builder = (typeof opt.builder === "function") ? opt.builder : null; - - var PT = this.getIslandPickerTheme ? this.getIslandPickerTheme() : null; - var isDark = PT ? PT.isDark : this.isDarkTheme(); - var C = this.ui.colors; - var T = PT ? PT.T : this.getAnimalIslandTheme(); - var wm = this.state.wm; - - var root = new android.widget.FrameLayout(context); - root.setBackgroundColor(self.withAlpha(isDark ? 0xFF000000 : 0xFFFFFFFF, isDark ? 0.58 : 0.42)); - root.setClickable(true); - - var card = new android.widget.LinearLayout(context); - card.setOrientation(android.widget.LinearLayout.VERTICAL); - var cardLp = new android.widget.FrameLayout.LayoutParams( - self.dp(340), self.dp(520) - ); - cardLp.gravity = android.view.Gravity.CENTER; - card.setLayoutParams(cardLp); - card.setBackground(self.ui.createStrokeDrawable(T.card, self.withAlpha(T.primaryDeep, isDark ? 0.28 : 0.22), self.dp(1), self.dp(24))); - card.setPadding(self.dp(14), self.dp(12), self.dp(14), self.dp(12)); - try { card.setElevation(self.dp(10)); } catch(eCardElev) {} - - var header = new android.widget.LinearLayout(context); - header.setOrientation(android.widget.LinearLayout.HORIZONTAL); - header.setGravity(android.view.Gravity.CENTER_VERTICAL); - var titleTv = new android.widget.TextView(context); - titleTv.setText("❧ " + title + " ❧"); - titleTv.setTextColor(T.text); - titleTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 16); - titleTv.setTypeface(null, android.graphics.Typeface.BOLD); - header.addView(titleTv, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); - - var closeBtn = self.ui.createFlatButton(self, "✕", T.primaryDeep, function() { - closePopup(); - }); - closeBtn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 18); - closeBtn.setTypeface(null, android.graphics.Typeface.BOLD); - try { closeBtn.setBackground(self.ui.createStrokeDrawable(T.primarySoft, self.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), self.dp(1), self.dp(18))); } catch(eCloseBg) {} - header.addView(closeBtn, new android.widget.LinearLayout.LayoutParams(self.dp(42), self.dp(38))); - card.addView(header); - - var content = new android.widget.LinearLayout(context); - content.setOrientation(android.widget.LinearLayout.VERTICAL); - content.setPadding(0, self.dp(8), 0, 0); - var contentLp = new android.widget.LinearLayout.LayoutParams( - android.widget.LinearLayout.LayoutParams.MATCH_PARENT, - android.widget.LinearLayout.LayoutParams.MATCH_PARENT - ); - content.setLayoutParams(contentLp); - card.addView(content); - - root.addView(card); - - root.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function(v) { closePopup(); } - })); - card.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function(v) { /* 阻止冒泡 */ } - })); - - var lp = new android.view.WindowManager.LayoutParams( - android.view.WindowManager.LayoutParams.MATCH_PARENT, - android.view.WindowManager.LayoutParams.MATCH_PARENT, - android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, - android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND, - android.graphics.PixelFormat.TRANSLUCENT - ); - lp.dimAmount = isDark ? 0.55 : 0.38; - lp.gravity = android.view.Gravity.TOP | android.view.Gravity.START; - lp.x = 0; - lp.y = 0; - lp.softInputMode = android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING - | android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; - - try { wm.addView(root, lp); } catch(eAdd) { safeLog(self.L, 'e', "popup addView fail: " + String(eAdd)); return null; } - - function closePopup() { - try { wm.removeView(root); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - if (typeof onDismiss === "function") { - try { onDismiss(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); } - } - } - - if (typeof builder === "function") { - try { builder(content, closePopup); } catch(eB) { safeLog(self.L, 'e', "popup builder fail: " + String(eB)); } - } - - return { close: closePopup, content: content }; -}; - -FloatBallAppWM.prototype.showShortXIconPickerPopup = function(opts) { - var self = this; - var opt = opts || {}; - var currentName = String(opt.currentName || ""); - var onSelect = (typeof opt.onSelect === "function") ? opt.onSelect : null; - var onDismissCb = (typeof opt.onDismiss === "function") ? opt.onDismiss : null; - - var catalog = []; - try { catalog = self.getShortXIconCatalog() || []; } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - if (!catalog.length) { - try { catalog = self.getShortXIconCatalog(true) || []; } catch(e2) { safeLog(null, 'e', "catch " + String(e2)); } - } - if (!catalog.length) { - self.toast("图标库未加载"); - return null; - } - - var selectedName = currentName; - var popupState = { currentPage: 0, filter: "全部" }; - var PT = self.getIslandPickerTheme ? self.getIslandPickerTheme() : null; - var isDark = PT ? PT.isDark : self.isDarkTheme(); - var C = self.ui.colors; - var T = PT ? PT.T : self.getAnimalIslandTheme(); - var textColor = PT ? PT.text : (isDark ? C.textPriDark : C.textPriLight); - var subTextColor = PT ? PT.sub : (isDark ? C.textSecDark : C.textSecLight); - var cardColor = PT ? PT.card2 : (isDark ? C.cardDark : C.cardLight); - var wm = self.state.wm; - var filterTags = ["全部", "常用", "最近", "收藏", "线框", "实心"]; - var filterViews = []; - var FAVORITE_ICONS_KEY = "shortx_icon_favorites"; - var favoriteIcons = []; - var favoriteMap = {}; - - function rebuildFavoriteMap() { - favoriteMap = {}; - for (var fi = 0; fi < favoriteIcons.length; fi++) { - var fn = String(favoriteIcons[fi] || ""); - if (fn) favoriteMap[fn] = true; - } - } - - function loadFavoriteIcons() { - favoriteIcons = []; - try { - var saved = self.loadPanelState ? self.loadPanelState(FAVORITE_ICONS_KEY) : null; - var arr = saved && saved.icons ? saved.icons : []; - for (var li = 0; li < arr.length && favoriteIcons.length < 300; li++) { - var name = String(arr[li] || ""); - if (name && !favoriteMap[name]) { - favoriteIcons.push(name); - favoriteMap[name] = true; - } - } - } catch(eFavLoad) { safeLog(null, 'e', "catch " + String(eFavLoad)); } - rebuildFavoriteMap(); - } - - function saveFavoriteIcons() { - try { - if (self.savePanelState) self.savePanelState(FAVORITE_ICONS_KEY, { icons: favoriteIcons.slice(0, 300) }); - } catch(eFavSave) { safeLog(null, 'e', "catch " + String(eFavSave)); } - } - - function isFavoriteIcon(name) { - return !!favoriteMap[String(name || "")]; - } - - function toggleFavoriteIcon(name) { - name = String(name || ""); - if (!name) return false; - var next = []; - var existed = false; - for (var ti = 0; ti < favoriteIcons.length; ti++) { - var oldName = String(favoriteIcons[ti] || ""); - if (!oldName) continue; - if (oldName === name) { existed = true; continue; } - next.push(oldName); - } - if (!existed) next.unshift(name); - favoriteIcons = next.slice(0, 300); - rebuildFavoriteMap(); - saveFavoriteIcons(); - return !existed; - } - loadFavoriteIcons(); - - function matchesFilter(entry, f) { - if (!entry) return false; - if (!f || f === "全部") return true; - var n = String(entry.shortName || entry.name || "").toLowerCase(); - if (f === "常用") return n.indexOf("home") >= 0 || n.indexOf("share") >= 0 || n.indexOf("search") >= 0 || n.indexOf("settings") >= 0 || n.indexOf("add") >= 0 || n.indexOf("back") >= 0 || n.indexOf("close") >= 0; - if (f === "最近") return selectedName && String(entry.name) === String(selectedName); - if (f === "收藏") return isFavoriteIcon(entry.name); - if (f === "线框") return n.indexOf("outline") >= 0 || n.indexOf("line") >= 0 || n.indexOf("stroke") >= 0 || n.indexOf("border") >= 0; - if (f === "实心") return n.indexOf("fill") >= 0 || n.indexOf("solid") >= 0 || n.indexOf("round") >= 0; - return true; - } - - function filterCatalog(q) { - var qLower = String(q || "").toLowerCase(); - var out = []; - for (var i = 0; i < catalog.length; i++) { - var entry = catalog[i]; - if (!entry) continue; - if (!matchesFilter(entry, popupState.filter)) continue; - if (qLower) { - var n = String(entry.shortName || entry.name).toLowerCase(); - if (n.indexOf(qLower) < 0) continue; - } - out.push(entry); - } - return out; - } - - var dm = context.getResources().getDisplayMetrics(); - var sw = dm.widthPixels; - var sh = dm.heightPixels; - var panelWidth = Math.round(sw * 0.92); - var panelHeight = Math.round(sh * 0.90); - try { - if (self.calculateToolAppLayout) { - var toolLayout = self.calculateToolAppLayout(null); - if (toolLayout && toolLayout.width > 0) panelWidth = toolLayout.width; - if (toolLayout && toolLayout.height > 0) panelHeight = toolLayout.height; - } - } catch(eLayout) { safeLog(null, 'e', "catch " + String(eLayout)); } - if (panelWidth > self.dp(560)) panelWidth = self.dp(560); - if (panelWidth < self.dp(320)) panelWidth = Math.min(sw - self.dp(16), self.dp(320)); - if (panelHeight > sh - self.dp(24)) panelHeight = sh - self.dp(24); - if (panelHeight < self.dp(460)) panelHeight = Math.min(sh - self.dp(16), self.dp(460)); - - var padH = self.dp(14); - var padV = self.dp(12); - var gap = self.dp(8); - var colCount = 5; - var availW = panelWidth - padH * 2 - self.dp(10) * 2; - var cellW = Math.floor((availW - gap * (colCount - 1)) / colCount); - if (cellW < self.dp(46)) cellW = self.dp(46); - var iconSize = Math.max(self.dp(23), Math.min(self.dp(30), Math.floor(cellW * 0.46))); - var cellH = self.dp(70); - var headerH = self.dp(176); - var bottomH = self.dp(58); - var maxGridH = Math.max(self.dp(250), panelHeight - headerH - bottomH); - var rowCount = Math.max(3, Math.floor(maxGridH / (cellH + gap))); - var pageSize = colCount * rowCount; - - var rootOverlay = new android.widget.FrameLayout(context); - try { rootOverlay.setBackgroundColor(self.withAlpha(isDark ? 0xFF000000 : 0xFFFFFFFF, isDark ? 0.58 : 0.42)); } - catch(eOverlayBg) { rootOverlay.setBackgroundColor(0x33000000); } - rootOverlay.setClickable(true); - - var card = new android.widget.LinearLayout(context); - card.setOrientation(android.widget.LinearLayout.VERTICAL); - card.setPadding(padH, padV, padH, padV); - card.setBackground(self.ui.createStrokeDrawable(T.card, self.withAlpha(T.primaryDeep, isDark ? 0.28 : 0.22), self.dp(1), self.dp(24))); - try { card.setElevation(self.dp(10)); } catch(eCardElev) { safeLog(null, 'e', "catch " + String(eCardElev)); } - - var cardLp = new android.widget.FrameLayout.LayoutParams(panelWidth, panelHeight); - cardLp.gravity = android.view.Gravity.CENTER; - card.setLayoutParams(cardLp); - rootOverlay.addView(card); - - var overlayLp = new android.view.WindowManager.LayoutParams( - android.view.WindowManager.LayoutParams.MATCH_PARENT, - android.view.WindowManager.LayoutParams.MATCH_PARENT, - android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, - android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, - android.graphics.PixelFormat.TRANSLUCENT - ); - overlayLp.softInputMode = android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING - | android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; - - try { wm.addView(rootOverlay, overlayLp); } catch(eAdd) { - safeLog(self.L, 'e', "icon picker addView fail: " + String(eAdd)); - return null; - } - - var isDismissed = false; - function dismiss() { - if (isDismissed) return; - isDismissed = true; - try { wm.removeView(rootOverlay); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - if (typeof onDismissCb === "function") { - try { onDismissCb(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); } - } - } - rootOverlay.setOnClickListener(new android.view.View.OnClickListener({ onClick: function() { dismiss(); } })); - card.setOnClickListener(new android.view.View.OnClickListener({ onClick: function() { } })); - - var header = new android.widget.LinearLayout(context); - header.setOrientation(android.widget.LinearLayout.HORIZONTAL); - header.setGravity(android.view.Gravity.CENTER_VERTICAL); - - var titleBox = new android.widget.LinearLayout(context); - titleBox.setOrientation(android.widget.LinearLayout.VERTICAL); - titleBox.setGravity(android.view.Gravity.CENTER_VERTICAL); - titleBox.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); - - var titleTv = new android.widget.TextView(context); - titleTv.setText("岛上图标库"); - titleTv.setTextColor(textColor); - titleTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 17); - titleTv.setTypeface(null, android.graphics.Typeface.BOLD); - titleBox.addView(titleTv); - - var countTv = new android.widget.TextView(context); - countTv.setText("共 " + catalog.length + " 个图标"); - countTv.setTextColor(subTextColor); - countTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); - titleBox.addView(countTv); - header.addView(titleBox); - - var closeBtn = self.ui.createFlatButton(self, "✕", T.primaryDeep, function() { dismiss(); }); - closeBtn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 18); - closeBtn.setTypeface(null, android.graphics.Typeface.BOLD); - closeBtn.setPadding(self.dp(8), 0, self.dp(8), 0); - try { closeBtn.setBackground(self.ui.createStrokeDrawable(T.primarySoft, self.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), self.dp(1), self.dp(18))); } catch(eCloseBg) {} - header.addView(closeBtn, new android.widget.LinearLayout.LayoutParams(self.dp(42), self.dp(38))); - card.addView(header); - - var searchEt = new android.widget.EditText(context); - searchEt.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); - searchEt.setTextColor(textColor); - try { searchEt.setHintTextColor(subTextColor); } catch(eHint) { safeLog(null, 'e', "catch " + String(eHint)); } - searchEt.setHint("寻找岛上图标,如 share / home"); - searchEt.setSingleLine(true); - searchEt.setFocusable(true); - searchEt.setFocusableInTouchMode(true); - searchEt.setPadding(self.dp(14), self.dp(10), self.dp(14), self.dp(10)); - searchEt.setBackground(self.ui.createStrokeDrawable(T.card2, self.withAlpha(T.primaryDeep, isDark ? 0.24 : 0.18), self.dp(1), self.dp(20))); - var searchLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(46)); - searchLp.setMargins(0, self.dp(10), 0, self.dp(8)); - card.addView(searchEt, searchLp); - searchEt.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function(v) { - self.touchActivity(); - try { - v.requestFocus(); - var imm = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE); - if (imm) imm.showSoftInput(v, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); - } catch(eIme) { safeLog(null, 'e', "catch " + String(eIme)); } - } - })); - - var filterScroll = new android.widget.HorizontalScrollView(context); - filterScroll.setHorizontalScrollBarEnabled(false); - var filterRow = new android.widget.LinearLayout(context); - filterRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); - filterRow.setGravity(android.view.Gravity.CENTER_VERTICAL); - filterRow.setPadding(0, 0, 0, 0); - filterScroll.addView(filterRow); - var filterLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(36)); - filterLp.setMargins(0, 0, 0, self.dp(4)); - card.addView(filterScroll, filterLp); - - function refreshFilterTags() { - try { - for (var i = 0; i < filterViews.length; i++) { - var item = filterViews[i]; - if (!item || !item.view) continue; - var active = item.name === popupState.filter; - item.view.setTextColor(active ? T.onPrimary : T.primaryDeep); - item.view.setTypeface(null, active ? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL); - item.view.setBackground(self.ui.createStrokeDrawable(active ? T.primary : T.primarySoft, self.withAlpha(T.primaryDeep, active ? 0.36 : 0.18), self.dp(1), self.dp(16))); - } - } catch(eTags) { safeLog(null, 'e', "catch " + String(eTags)); } - } - - for (var ft = 0; ft < filterTags.length; ft++) { - (function(tag) { - var chip = new android.widget.TextView(context); - chip.setText(tag); - chip.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - chip.setGravity(android.view.Gravity.CENTER); - chip.setPadding(self.dp(12), 0, self.dp(12), 0); - chip.setSingleLine(true); - chip.setClickable(true); - chip.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { - self.touchActivity(); - popupState.filter = tag; - popupState.currentPage = 0; - refreshFilterTags(); - renderGrid(); - } - })); - var chipLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, self.dp(30)); - chipLp.setMargins(0, 0, self.dp(7), 0); - filterRow.addView(chip, chipLp); - filterViews.push({ name: tag, view: chip }); - })(filterTags[ft]); - } - refreshFilterTags(); - - var pageBar = new android.widget.LinearLayout(context); - pageBar.setOrientation(android.widget.LinearLayout.HORIZONTAL); - pageBar.setGravity(android.view.Gravity.CENTER_VERTICAL); - pageBar.setPadding(self.dp(2), 0, self.dp(2), self.dp(4)); - var pageBarLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(34)); - card.addView(pageBar, pageBarLp); - - var btnPrev = self.ui.createFlatButton(self, "上一页", self.withAlpha(T.primaryDeep, 0.72), function() { - if (popupState.currentPage > 0) { popupState.currentPage--; renderGrid(); } - }); - pageBar.addView(btnPrev, new android.widget.LinearLayout.LayoutParams(self.dp(78), self.dp(30))); - - var pageInfo = new android.widget.TextView(context); - pageInfo.setTextColor(self.withAlpha(textColor, 0.76)); - pageInfo.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - pageInfo.setGravity(android.view.Gravity.CENTER); - pageInfo.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); - pageBar.addView(pageInfo); - - var btnNext = self.ui.createFlatButton(self, "下一页", self.withAlpha(T.primaryDeep, 0.72), function() { - popupState.currentPage++; - renderGrid(); - }); - pageBar.addView(btnNext, new android.widget.LinearLayout.LayoutParams(self.dp(78), self.dp(30))); - - var gridScroll = new android.widget.ScrollView(context); - gridScroll.setVerticalScrollBarEnabled(false); - gridScroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); - gridScroll.setLayoutParams(new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1)); - card.addView(gridScroll); - - var grid = new android.widget.GridLayout(context); - grid.setColumnCount(colCount); - grid.setPadding(self.dp(10), self.dp(6), self.dp(10), self.dp(8)); - gridScroll.addView(grid); - - var selectRow = new android.widget.LinearLayout(context); - selectRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); - selectRow.setGravity(android.view.Gravity.CENTER_VERTICAL); - selectRow.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8)); - selectRow.setBackground(self.ui.createStrokeDrawable(T.card2, self.withAlpha(T.primaryDeep, isDark ? 0.22 : 0.16), self.dp(1), self.dp(22))); - var selectRowLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(58)); - selectRowLp.setMargins(0, self.dp(6), 0, 0); - card.addView(selectRow, selectRowLp); - - var selectNameTv = new android.widget.TextView(context); - selectNameTv.setTextColor(textColor); - selectNameTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13); - selectNameTv.setSingleLine(true); - try { selectNameTv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(eEll) {} - selectNameTv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); - selectRow.addView(selectNameTv); - - var selectConfirm = self.ui.createSolidButton(self, "带回小岛", T.primary, T.onPrimary, function() { - self.touchActivity(); - try { - if (typeof onSelect === "function") onSelect(selectedName); - } catch(eSelect) { - safeLog(self.L, 'e', "icon onSelect err=" + String(eSelect)); - } - dismiss(); - }); - var confirmLp = new android.widget.LinearLayout.LayoutParams(self.dp(104), self.dp(42)); - confirmLp.setMargins(self.dp(10), 0, 0, 0); - selectRow.addView(selectConfirm, confirmLp); - - function updateSelectedLabel() { - try { - selectNameTv.setText(selectedName ? ("已选:" + String(selectedName)) : "还没选图标"); - } catch(eLabel) { safeLog(null, 'e', "catch " + String(eLabel)); } - } - updateSelectedLabel(); - - function renderGrid() { - try { - grid.removeAllViews(); - var q = String(searchEt.getText() || ""); - var matched = filterCatalog(q); - var totalPages = Math.max(1, Math.ceil(matched.length / pageSize)); - if (popupState.currentPage >= totalPages) popupState.currentPage = totalPages - 1; - if (popupState.currentPage < 0) popupState.currentPage = 0; - var start = popupState.currentPage * pageSize; - var pageItems = matched.slice(start, start + pageSize); - - pageInfo.setText("第 " + (popupState.currentPage + 1) + " / " + totalPages + " 页"); - btnPrev.setEnabled(popupState.currentPage > 0); - btnNext.setEnabled(popupState.currentPage < totalPages - 1); - - if (pageItems.length === 0) { - var emptyTv = new android.widget.TextView(context); - emptyTv.setText(popupState.filter === "收藏" ? "收藏夹还空着,点图标左上角 ☆ 收藏" : "没有找到这枚小图标"); - emptyTv.setTextColor(subTextColor); - emptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); - emptyTv.setGravity(android.view.Gravity.CENTER); - emptyTv.setPadding(0, self.dp(60), 0, self.dp(60)); - grid.addView(emptyTv); - return; - } - - grid.setColumnCount(colCount); - for (var idx = 0; idx < pageItems.length; idx++) { - (function(item) { - var frame = new android.widget.FrameLayout(context); - frame.setClickable(true); - var isSelected = selectedName === item.name; - var frameBg = isSelected ? T.primarySoft : self.withAlpha(cardColor, 0.96); - var frameStroke = isSelected ? self.withAlpha(T.primaryDeep, isDark ? 0.50 : 0.42) : self.withAlpha(T.primaryDeep, isDark ? 0.18 : 0.12); - frame.setBackground(self.ui.createStrokeDrawable(frameBg, frameStroke, isSelected ? self.dp(2) : self.dp(1), self.dp(15))); - - var cell = new android.widget.LinearLayout(context); - cell.setOrientation(android.widget.LinearLayout.VERTICAL); - cell.setGravity(android.view.Gravity.CENTER); - cell.setPadding(self.dp(4), self.dp(6), self.dp(4), self.dp(5)); - frame.addView(cell, new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)); - - var iv = new android.widget.ImageView(context); - iv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(iconSize, iconSize)); - iv.setScaleType(android.widget.ImageView.ScaleType.FIT_CENTER); - try { - var dr = self.getShortXIconDrawable(item.name); - if (dr) { iv.setImageDrawable(dr); } - } catch(eIcon) { safeLog(null, 'e', "catch " + String(eIcon)); } - cell.addView(iv); - - var tv = new android.widget.TextView(context); - tv.setText(String(item.shortName || item.name)); - tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 9); - tv.setGravity(android.view.Gravity.CENTER); - tv.setMaxLines(1); - try { tv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(eTvEll) {} - tv.setPadding(self.dp(2), self.dp(5), self.dp(2), 0); - tv.setTextColor(isSelected ? T.primaryDeep : subTextColor); - cell.addView(tv, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT)); - - var favBtn = new android.widget.TextView(context); - favBtn.setText(isFavoriteIcon(item.name) ? "★" : "☆"); - favBtn.setTextColor(isFavoriteIcon(item.name) ? T.primaryDeep : self.withAlpha(T.primaryDeep, 0.52)); - favBtn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13); - favBtn.setGravity(android.view.Gravity.CENTER); - favBtn.setTypeface(null, android.graphics.Typeface.BOLD); - favBtn.setBackground(self.ui.createRoundDrawable(isFavoriteIcon(item.name) ? T.primarySoft : self.withAlpha(T.card, 0.88), self.dp(9))); - favBtn.setClickable(true); - favBtn.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { - self.touchActivity(); - var added = toggleFavoriteIcon(item.name); - try { self.toast(added ? "已收藏到小岛" : "已取消收藏"); } catch(eFavToast) {} - if (popupState.filter === "收藏") popupState.currentPage = 0; - renderGrid(); - } - })); - var favLp = new android.widget.FrameLayout.LayoutParams(self.dp(18), self.dp(18)); - favLp.gravity = android.view.Gravity.TOP | android.view.Gravity.LEFT; - favLp.setMargins(self.dp(4), self.dp(4), 0, 0); - frame.addView(favBtn, favLp); - - if (isSelected) { - var badge = new android.widget.TextView(context); - badge.setText("✓"); - badge.setTextColor(T.onPrimary); - badge.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 10); - badge.setGravity(android.view.Gravity.CENTER); - badge.setTypeface(null, android.graphics.Typeface.BOLD); - badge.setBackground(self.ui.createRoundDrawable(T.primary, self.dp(9))); - var badgeLp = new android.widget.FrameLayout.LayoutParams(self.dp(18), self.dp(18)); - badgeLp.gravity = android.view.Gravity.TOP | android.view.Gravity.RIGHT; - badgeLp.setMargins(0, self.dp(4), self.dp(4), 0); - frame.addView(badge, badgeLp); - } - - frame.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { - self.touchActivity(); - selectedName = item.name; - updateSelectedLabel(); - renderGrid(); - } - })); - - var cellLp = new android.widget.GridLayout.LayoutParams(); - cellLp.width = cellW; - cellLp.height = cellH; - var col = idx % colCount; - var mr = (col === colCount - 1) ? 0 : gap; - cellLp.setMargins(0, 0, mr, gap); - frame.setLayoutParams(cellLp); - grid.addView(frame); - })(pageItems[idx]); - } - } catch(eRender) { - safeLog(self.L, 'e', "renderShortXIconGrid err=" + String(eRender)); - } - } - - searchEt.addTextChangedListener(new android.text.TextWatcher({ - beforeTextChanged: function() {}, - onTextChanged: function() { - self.touchActivity(); - popupState.currentPage = 0; - renderGrid(); - }, - afterTextChanged: function() {} - })); - - renderGrid(); - - return { close: dismiss }; -}; - - -FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { - var self = this; - var opt = opts || {}; - var currentColor = String(opt.currentColor || ""); - var currentIconName = String(opt.currentIconName || ""); - var onSelect = (typeof opt.onSelect === "function") ? opt.onSelect : null; - var onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null; - - var PT = this.getIslandPickerTheme ? this.getIslandPickerTheme() : null; - var isDark = PT ? PT.isDark : this.isDarkTheme(); - var C = this.ui.colors; - var T = PT ? PT.T : this.getAnimalIslandTheme(); - var textColor = PT ? PT.text : (isDark ? C.textPriDark : C.textPriLight); - var subTextColor = PT ? PT.sub : (isDark ? C.textSecDark : C.textSecLight); - - function getThemeTintHex() { - try { - if (self.ui.colors && self.ui.colors.accent) { - var c = self.ui.colors.accent; - return "#" + ("00000000" + (c >>> 0).toString(16)).slice(-8); - } - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - return "#FF4081"; - } - - function buildArgbHex(alphaByte, rgbHex) { - var a = ("00" + (Math.max(0, Math.min(255, Number(alphaByte || 0))) >>> 0).toString(16)).slice(-2); - var rgb = String(rgbHex || "000000").replace(/^#/, ""); - if (rgb.length === 3) rgb = rgb.split("").map(function(c){ return c+c; }).join(""); - if (rgb.length > 6) rgb = rgb.slice(-6); - while (rgb.length < 6) rgb = "0" + rgb; - return "#" + a + rgb; - } - - function extractTintRgbHex(hex) { - var h = String(hex || "").replace(/^#/, ""); - if (h.length >= 8) return h.slice(-6); - if (h.length === 6) return h; - if (h.length === 3) return h.split("").map(function(c){ return c+c; }).join(""); - return "000000"; - } - - function extractTintAlphaByte(hex) { - var h = String(hex || "").replace(/^#/, ""); - if (h.length >= 8) return parseInt(h.slice(0, 2), 16); - return 255; - } - - function normalizeTintColorValue(val) { - var s = String(val || "").trim(); - if (!s) return ""; - if (s.charAt(0) === "#") s = s.substring(1); - if (/^[0-9A-Fa-f]{1,8}$/.test(s)) { - while (s.length < 6) s = "0" + s; - if (s.length === 6) s = "FF" + s; - else if (s.length > 8) s = s.substring(0, 8); - return "#" + s.toUpperCase(); - } - return ""; - } - - var commonTintHexValues = [ - "#FFFF0000", "#FFFF5722", "#FFFF9800", "#FFFFC107", "#FFFFEB3B", - "#FFCDDC39", "#FF8BC34A", "#FF4CAF50", "#FF009688", "#FF00BCD4", - "#FF03A9F4", "#FF2196F3", "#FF3F51B5", "#FF673AB7", "#FF9C27B0", - "#FFE91E63", "#FF795548", "#FF9E9E9E", "#FF607D8B", "#FF000000", "#FFFFFFFF" - ]; - - // ========== 最近使用颜色 ========== - var RECENT_COLORS_KEY = "color_picker_recent"; - var MAX_RECENT_COLORS = 8; - var recentColors = []; - try { - var recentSaved = self.loadPanelState(RECENT_COLORS_KEY); - if (recentSaved && recentSaved.colors && recentSaved.colors.length) { - var rc; - for (rc = 0; rc < recentSaved.colors.length && rc < MAX_RECENT_COLORS; rc++) { - var rn = normalizeTintColorValue(recentSaved.colors[rc], false); - if (rn) recentColors.push(rn); - } - } - } catch(eRecentLoad) { safeLog(null, 'e', "catch " + String(eRecentLoad)); } - - function saveRecentColors() { - try { - self.savePanelState(RECENT_COLORS_KEY, { colors: recentColors.slice(0, MAX_RECENT_COLORS) }); - } catch(eRecentSave) { safeLog(null, 'e', "catch " + String(eRecentSave)); } - } - - function pushRecentColor(hex) { - var normalized = normalizeTintColorValue(hex, false); - if (!normalized) return; - var next = [normalized]; - var i; - for (i = 0; i < recentColors.length; i++) { - if (recentColors[i] !== normalized) { - next.push(recentColors[i]); - } - if (next.length >= MAX_RECENT_COLORS) break; - } - recentColors = next; - saveRecentColors(); - } - - var selectedColor = currentColor; - var isFollowTheme = !currentColor; - var currentBaseRgbHex = extractTintRgbHex(currentColor); - var currentAlphaByte = extractTintAlphaByte(currentColor); - - var popupResult = self.showPopupOverlay({ - title: "换颜色", - onDismiss: onDismiss, - builder: function(content, closePopup) { - // 图标预览区 - var previewRow = new android.widget.LinearLayout(context); - previewRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); - previewRow.setGravity(android.view.Gravity.CENTER_VERTICAL); - previewRow.setPadding(self.dp(12), self.dp(10), self.dp(12), self.dp(10)); - previewRow.setBackground(self.ui.createStrokeDrawable(T.primarySoft, self.withAlpha(T.primaryDeep, isDark ? 0.24 : 0.18), self.dp(1), self.dp(18))); - - var previewIv = new android.widget.ImageView(context); - previewIv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(48), self.dp(48))); - previewIv.setScaleType(android.widget.ImageView.ScaleType.FIT_CENTER); - previewRow.addView(previewIv); - - var previewLabel = new android.widget.TextView(context); - previewLabel.setText("小图标试衣间"); - previewLabel.setTextColor(subTextColor); - previewLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - previewLabel.setPadding(self.dp(12), 0, 0, 0); - previewRow.addView(previewLabel); - - content.addView(previewRow); - - function updatePreview() { - try { - var dr = null; - if (currentIconName) { - try { dr = self.getShortXIconDrawable(currentIconName); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - if (dr) { - if (!isFollowTheme && selectedColor) { - try { - var parsed = android.graphics.Color.parseColor(selectedColor); - dr.setColorFilter(parsed, android.graphics.PorterDuff.Mode.SRC_IN); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } else { - try { dr.clearColorFilter(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - previewIv.setImageDrawable(dr); - } else { - previewIv.setImageDrawable(null); - } - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - updatePreview(); - - // ========== 最近使用颜色 ========== - var recentTitle = new android.widget.TextView(context); - recentTitle.setText("最近用过的小颜色"); - recentTitle.setTextColor(subTextColor); - recentTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - recentTitle.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(4)); - content.addView(recentTitle); - - var recentGrid = new android.widget.GridLayout(context); - recentGrid.setColumnCount(8); - recentGrid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(4)); - content.addView(recentGrid); - - var recentEmptyTv = new android.widget.TextView(context); - recentEmptyTv.setText("还没有最近颜色"); - recentEmptyTv.setTextColor(subTextColor); - recentEmptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); - recentEmptyTv.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(8)); - recentEmptyTv.setVisibility(android.view.View.GONE); - content.addView(recentEmptyTv); - - function refreshRecentGrid() { - try { - recentGrid.removeAllViews(); - if (!recentColors.length) { - recentEmptyTv.setVisibility(android.view.View.VISIBLE); - return; - } - recentEmptyTv.setVisibility(android.view.View.GONE); - var ri; - for (ri = 0; ri < recentColors.length && ri < MAX_RECENT_COLORS; ri++) { - (function(hex) { - var cell = new android.widget.FrameLayout(context); - var margin = self.dp(3); - try { - var lp = new android.widget.GridLayout.LayoutParams(); - lp.width = self.dp(28); - lp.height = self.dp(28); - lp.setMargins(margin, margin, margin, margin); - cell.setLayoutParams(lp); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - - var swatch = new android.view.View(context); - swatch.setLayoutParams(new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)); - try { - var bg = new android.graphics.drawable.GradientDrawable(); - bg.setColor(android.graphics.Color.parseColor(hex)); - bg.setCornerRadius(self.dp(5)); - swatch.setBackground(bg); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - cell.addView(swatch); - - if (selectedColor === hex) { - try { - var border = new android.graphics.drawable.GradientDrawable(); - border.setColor(android.graphics.Color.TRANSPARENT); - border.setCornerRadius(self.dp(5)); - border.setStroke(self.dp(2), T.primaryDeep); - cell.setForeground(border); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - - cell.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { - self.touchActivity(); - isFollowTheme = false; - selectedColor = hex; - currentBaseRgbHex = extractTintRgbHex(hex); - currentAlphaByte = extractTintAlphaByte(hex); - updatePreview(); - updateValueTv(); - refreshRecentGrid(); - refreshCommonGrid(); - syncRgbSeeks(); - } - })); - recentGrid.addView(cell); - })(recentColors[ri]); - } - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - refreshRecentGrid(); - - // 21 色常用颜色 - var commonTitle = new android.widget.TextView(context); - commonTitle.setText("糖果常用色"); - commonTitle.setTextColor(subTextColor); - commonTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - commonTitle.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(4)); - content.addView(commonTitle); - - var commonGrid = new android.widget.GridLayout(context); - commonGrid.setColumnCount(7); - commonGrid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(8)); - var ci; - for (ci = 0; ci < commonTintHexValues.length; ci++) { - (function(hex) { - var cell = new android.widget.FrameLayout(context); - var margin = self.dp(4); - try { - var lp = new android.widget.GridLayout.LayoutParams(); - lp.width = self.dp(32); - lp.height = self.dp(32); - lp.setMargins(margin, margin, margin, margin); - cell.setLayoutParams(lp); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - - var swatch = new android.view.View(context); - swatch.setLayoutParams(new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)); - try { - var bg = new android.graphics.drawable.GradientDrawable(); - bg.setColor(android.graphics.Color.parseColor(hex)); - bg.setCornerRadius(self.dp(6)); - swatch.setBackground(bg); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - cell.addView(swatch); - - if (selectedColor === hex) { - try { - var border = new android.graphics.drawable.GradientDrawable(); - border.setColor(android.graphics.Color.TRANSPARENT); - border.setCornerRadius(self.dp(6)); - border.setStroke(self.dp(3), T.primaryDeep); - cell.setForeground(border); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - - cell.setOnClickListener(new android.view.View.OnClickListener({ - onClick: function() { - self.touchActivity(); - isFollowTheme = false; - selectedColor = hex; - currentBaseRgbHex = extractTintRgbHex(hex); - currentAlphaByte = extractTintAlphaByte(hex); - updatePreview(); - updateValueTv(); - refreshRecentGrid(); - refreshCommonGrid(); - syncRgbSeeks(); - } - })); - commonGrid.addView(cell); - })(commonTintHexValues[ci]); - } - content.addView(commonGrid); - - function refreshCommonGrid() { - try { - var count = commonGrid.getChildCount(); - var i; - for (i = 0; i < count; i++) { - var cell = commonGrid.getChildAt(i); - if (!cell) continue; - try { cell.setForeground(null); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - var idx = commonTintHexValues.indexOf(selectedColor); - if (idx >= 0 && idx < count) { - var matchedCell = commonGrid.getChildAt(idx); - if (matchedCell) { - try { - var border = new android.graphics.drawable.GradientDrawable(); - border.setColor(android.graphics.Color.TRANSPARENT); - border.setCornerRadius(self.dp(6)); - border.setStroke(self.dp(3), T.primaryDeep); - matchedCell.setForeground(border); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - } - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - - // 颜色值显示 - var valueTv = new android.widget.TextView(context); - valueTv.setTextColor(textColor); - valueTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13); - valueTv.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(4)); - content.addView(valueTv); - - function updateValueTv() { - valueTv.setText(isFollowTheme ? "当前:跟随岛屿主题" : ("当前:" + (selectedColor || "无"))); - } - updateValueTv(); - - // RGB 滑块 - var rgbLabels = ["R", "G", "B"]; - var rgbSeeks = []; - var rgbValTvs = []; - var ri; - for (ri = 0; ri < 3; ri++) { - (function(idx) { - var row = new android.widget.LinearLayout(context); - row.setOrientation(android.widget.LinearLayout.HORIZONTAL); - row.setGravity(android.view.Gravity.CENTER_VERTICAL); - row.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(4)); - - var label = new android.widget.TextView(context); - label.setText(rgbLabels[idx]); - label.setTextColor(textColor); - label.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - label.setMinWidth(self.dp(20)); - row.addView(label); - - var seek = new android.widget.SeekBar(context); - seek.setMax(255); - var seekLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1); - seekLp.setMargins(self.dp(8), 0, self.dp(8), 0); - seek.setLayoutParams(seekLp); - row.addView(seek); - rgbSeeks.push(seek); - - var valTv = new android.widget.TextView(context); - valTv.setTextColor(subTextColor); - valTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); - valTv.setMinWidth(self.dp(28)); - row.addView(valTv); - rgbValTvs.push(valTv); - - seek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({ - onProgressChanged: function(s, progress, fromUser) { - if (!fromUser) return; - valTv.setText(String(progress)); - var r = rgbSeeks[0].getProgress(); - var g = rgbSeeks[1].getProgress(); - var b = rgbSeeks[2].getProgress(); - var hex = ("00" + (r >>> 0).toString(16)).slice(-2) + ("00" + (g >>> 0).toString(16)).slice(-2) + ("00" + (b >>> 0).toString(16)).slice(-2); - currentBaseRgbHex = hex; - isFollowTheme = false; - selectedColor = buildArgbHex(currentAlphaByte, currentBaseRgbHex); - updatePreview(); - updateValueTv(); - refreshRecentGrid(); - refreshCommonGrid(); - }, - onStartTrackingTouch: function() {}, - onStopTrackingTouch: function() {} - })); - - content.addView(row); - })(ri); - } - - function syncRgbSeeks() { - try { - var initR = parseInt(currentBaseRgbHex.slice(0, 2), 16) || 0; - var initG = parseInt(currentBaseRgbHex.slice(2, 4), 16) || 0; - var initB = parseInt(currentBaseRgbHex.slice(4, 6), 16) || 0; - rgbSeeks[0].setProgress(initR); - rgbSeeks[1].setProgress(initG); - rgbSeeks[2].setProgress(initB); - rgbValTvs[0].setText(String(initR)); - rgbValTvs[1].setText(String(initG)); - rgbValTvs[2].setText(String(initB)); - } catch(e) { safeLog(null, 'e', "catch " + String(e)); } - } - syncRgbSeeks(); - - // 透明度滑块 - var alphaRow = new android.widget.LinearLayout(context); - alphaRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); - alphaRow.setGravity(android.view.Gravity.CENTER_VERTICAL); - alphaRow.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(8)); - - var alphaLabel = new android.widget.TextView(context); - alphaLabel.setText("A"); - alphaLabel.setTextColor(textColor); - alphaLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - alphaLabel.setMinWidth(self.dp(20)); - alphaRow.addView(alphaLabel); - - var alphaSeek = new android.widget.SeekBar(context); - alphaSeek.setMax(255); - var alphaSeekLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1); - alphaSeekLp.setMargins(self.dp(8), 0, self.dp(8), 0); - alphaSeek.setLayoutParams(alphaSeekLp); - alphaRow.addView(alphaSeek); - - var alphaValTv = new android.widget.TextView(context); - alphaValTv.setTextColor(subTextColor); - alphaValTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); - alphaValTv.setMinWidth(self.dp(28)); - alphaRow.addView(alphaValTv); - - alphaSeek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({ - onProgressChanged: function(s, progress, fromUser) { - if (!fromUser) return; - alphaValTv.setText(String(progress)); - currentAlphaByte = progress; - isFollowTheme = false; - selectedColor = buildArgbHex(currentAlphaByte, currentBaseRgbHex); - updatePreview(); - updateValueTv(); - refreshRecentGrid(); - refreshCommonGrid(); - }, - onStartTrackingTouch: function() {}, - onStopTrackingTouch: function() {} - })); - - alphaSeek.setProgress(currentAlphaByte); - alphaValTv.setText(String(currentAlphaByte)); - content.addView(alphaRow); - - // 操作按钮 - var actionRow = new android.widget.LinearLayout(context); - actionRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); - actionRow.setGravity(android.view.Gravity.CENTER); - actionRow.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8)); - - var btnClear = self.ui.createFlatButton(self, "恢复默认", T.primaryDeep, function() { - self.touchActivity(); - isFollowTheme = true; - selectedColor = ""; - updatePreview(); - updateValueTv(); - refreshRecentGrid(); - refreshCommonGrid(); - syncRgbSeeks(); - alphaSeek.setProgress(255); - alphaValTv.setText("255"); - currentAlphaByte = 255; - }); - actionRow.addView(btnClear); - - var btnOk = self.ui.createSolidButton(self, "保存颜色", T.primary, T.onPrimary, function() { - self.touchActivity(); - try { - var finalColor = isFollowTheme ? "" : String(selectedColor || ""); - if (!isFollowTheme && selectedColor) { - pushRecentColor(selectedColor); - } - if (typeof onSelect === "function") { - try { onSelect(finalColor); } catch(eOnSelect) { - safeLog(self.L, 'e', "colorPicker onSelect err=" + String(eOnSelect)); - } - } - } catch(e) { - safeLog(self.L, 'e', "colorPicker confirm err=" + String(e)); - } - closePopup(); - }); - actionRow.addView(btnOk); - - content.addView(actionRow); - } - }); - - return popupResult; -}; - // =======================【查看器面板:UI】====================== diff --git a/code/th_15_picker.js b/code/th_15_picker.js new file mode 100644 index 0000000..4452c3a --- /dev/null +++ b/code/th_15_picker.js @@ -0,0 +1,1109 @@ +// th_15_picker.js - ToolHub popup pickers (ShortX icon picker + color picker) +// 从 th_14_panels.js 拆出:降低设置/按钮面板模块体积,保留原型方法名与数据流。 + +// =======================【弹出式选择器(WindowManager 覆盖层)】====================== +FloatBallAppWM.prototype.showPopupOverlay = function(opts) { + var self = this; + var opt = opts || {}; + var title = String(opt.title || ""); + var onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null; + var builder = (typeof opt.builder === "function") ? opt.builder : null; + + var PT = this.getIslandPickerTheme ? this.getIslandPickerTheme() : null; + var isDark = PT ? PT.isDark : this.isDarkTheme(); + var C = this.ui.colors; + var T = PT ? PT.T : this.getAnimalIslandTheme(); + var wm = this.state.wm; + + var root = new android.widget.FrameLayout(context); + root.setBackgroundColor(self.withAlpha(isDark ? 0xFF000000 : 0xFFFFFFFF, isDark ? 0.58 : 0.42)); + root.setClickable(true); + + var card = new android.widget.LinearLayout(context); + card.setOrientation(android.widget.LinearLayout.VERTICAL); + var cardLp = new android.widget.FrameLayout.LayoutParams( + self.dp(340), self.dp(520) + ); + cardLp.gravity = android.view.Gravity.CENTER; + card.setLayoutParams(cardLp); + card.setBackground(self.ui.createStrokeDrawable(T.card, self.withAlpha(T.primaryDeep, isDark ? 0.28 : 0.22), self.dp(1), self.dp(24))); + card.setPadding(self.dp(14), self.dp(12), self.dp(14), self.dp(12)); + try { card.setElevation(self.dp(10)); } catch(eCardElev) {} + + var header = new android.widget.LinearLayout(context); + header.setOrientation(android.widget.LinearLayout.HORIZONTAL); + header.setGravity(android.view.Gravity.CENTER_VERTICAL); + var titleTv = new android.widget.TextView(context); + titleTv.setText("❧ " + title + " ❧"); + titleTv.setTextColor(T.text); + titleTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 16); + titleTv.setTypeface(null, android.graphics.Typeface.BOLD); + header.addView(titleTv, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); + + var closeBtn = self.ui.createFlatButton(self, "✕", T.primaryDeep, function() { + closePopup(); + }); + closeBtn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 18); + closeBtn.setTypeface(null, android.graphics.Typeface.BOLD); + try { closeBtn.setBackground(self.ui.createStrokeDrawable(T.primarySoft, self.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), self.dp(1), self.dp(18))); } catch(eCloseBg) {} + header.addView(closeBtn, new android.widget.LinearLayout.LayoutParams(self.dp(42), self.dp(38))); + card.addView(header); + + var content = new android.widget.LinearLayout(context); + content.setOrientation(android.widget.LinearLayout.VERTICAL); + content.setPadding(0, self.dp(8), 0, 0); + var contentLp = new android.widget.LinearLayout.LayoutParams( + android.widget.LinearLayout.LayoutParams.MATCH_PARENT, + android.widget.LinearLayout.LayoutParams.MATCH_PARENT + ); + content.setLayoutParams(contentLp); + card.addView(content); + + root.addView(card); + + root.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function(v) { closePopup(); } + })); + card.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function(v) { /* 阻止冒泡 */ } + })); + + var lp = new android.view.WindowManager.LayoutParams( + android.view.WindowManager.LayoutParams.MATCH_PARENT, + android.view.WindowManager.LayoutParams.MATCH_PARENT, + android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND, + android.graphics.PixelFormat.TRANSLUCENT + ); + lp.dimAmount = isDark ? 0.55 : 0.38; + lp.gravity = android.view.Gravity.TOP | android.view.Gravity.START; + lp.x = 0; + lp.y = 0; + lp.softInputMode = android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING + | android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; + + try { wm.addView(root, lp); } catch(eAdd) { safeLog(self.L, 'e', "popup addView fail: " + String(eAdd)); return null; } + + function closePopup() { + try { wm.removeView(root); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + if (typeof onDismiss === "function") { + try { onDismiss(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); } + } + } + + if (typeof builder === "function") { + try { builder(content, closePopup); } catch(eB) { safeLog(self.L, 'e', "popup builder fail: " + String(eB)); } + } + + return { close: closePopup, content: content }; +}; + +FloatBallAppWM.prototype.showShortXIconPickerPopup = function(opts) { + var self = this; + var opt = opts || {}; + var currentName = String(opt.currentName || ""); + var onSelect = (typeof opt.onSelect === "function") ? opt.onSelect : null; + var onDismissCb = (typeof opt.onDismiss === "function") ? opt.onDismiss : null; + + var catalog = []; + try { catalog = self.getShortXIconCatalog() || []; } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + if (!catalog.length) { + try { catalog = self.getShortXIconCatalog(true) || []; } catch(e2) { safeLog(null, 'e', "catch " + String(e2)); } + } + if (!catalog.length) { + self.toast("图标库未加载"); + return null; + } + + var selectedName = currentName; + var popupState = { currentPage: 0, filter: "全部" }; + var PT = self.getIslandPickerTheme ? self.getIslandPickerTheme() : null; + var isDark = PT ? PT.isDark : self.isDarkTheme(); + var C = self.ui.colors; + var T = PT ? PT.T : self.getAnimalIslandTheme(); + var textColor = PT ? PT.text : (isDark ? C.textPriDark : C.textPriLight); + var subTextColor = PT ? PT.sub : (isDark ? C.textSecDark : C.textSecLight); + var cardColor = PT ? PT.card2 : (isDark ? C.cardDark : C.cardLight); + var wm = self.state.wm; + var filterTags = ["全部", "常用", "最近", "收藏", "线框", "实心"]; + var filterViews = []; + var FAVORITE_ICONS_KEY = "shortx_icon_favorites"; + var favoriteIcons = []; + var favoriteMap = {}; + + function rebuildFavoriteMap() { + favoriteMap = {}; + for (var fi = 0; fi < favoriteIcons.length; fi++) { + var fn = String(favoriteIcons[fi] || ""); + if (fn) favoriteMap[fn] = true; + } + } + + function loadFavoriteIcons() { + favoriteIcons = []; + try { + var saved = self.loadPanelState ? self.loadPanelState(FAVORITE_ICONS_KEY) : null; + var arr = saved && saved.icons ? saved.icons : []; + for (var li = 0; li < arr.length && favoriteIcons.length < 300; li++) { + var name = String(arr[li] || ""); + if (name && !favoriteMap[name]) { + favoriteIcons.push(name); + favoriteMap[name] = true; + } + } + } catch(eFavLoad) { safeLog(null, 'e', "catch " + String(eFavLoad)); } + rebuildFavoriteMap(); + } + + function saveFavoriteIcons() { + try { + if (self.savePanelState) self.savePanelState(FAVORITE_ICONS_KEY, { icons: favoriteIcons.slice(0, 300) }); + } catch(eFavSave) { safeLog(null, 'e', "catch " + String(eFavSave)); } + } + + function isFavoriteIcon(name) { + return !!favoriteMap[String(name || "")]; + } + + function toggleFavoriteIcon(name) { + name = String(name || ""); + if (!name) return false; + var next = []; + var existed = false; + for (var ti = 0; ti < favoriteIcons.length; ti++) { + var oldName = String(favoriteIcons[ti] || ""); + if (!oldName) continue; + if (oldName === name) { existed = true; continue; } + next.push(oldName); + } + if (!existed) next.unshift(name); + favoriteIcons = next.slice(0, 300); + rebuildFavoriteMap(); + saveFavoriteIcons(); + return !existed; + } + loadFavoriteIcons(); + + function matchesFilter(entry, f) { + if (!entry) return false; + if (!f || f === "全部") return true; + var n = String(entry.shortName || entry.name || "").toLowerCase(); + if (f === "常用") return n.indexOf("home") >= 0 || n.indexOf("share") >= 0 || n.indexOf("search") >= 0 || n.indexOf("settings") >= 0 || n.indexOf("add") >= 0 || n.indexOf("back") >= 0 || n.indexOf("close") >= 0; + if (f === "最近") return selectedName && String(entry.name) === String(selectedName); + if (f === "收藏") return isFavoriteIcon(entry.name); + if (f === "线框") return n.indexOf("outline") >= 0 || n.indexOf("line") >= 0 || n.indexOf("stroke") >= 0 || n.indexOf("border") >= 0; + if (f === "实心") return n.indexOf("fill") >= 0 || n.indexOf("solid") >= 0 || n.indexOf("round") >= 0; + return true; + } + + function filterCatalog(q) { + var qLower = String(q || "").toLowerCase(); + var out = []; + for (var i = 0; i < catalog.length; i++) { + var entry = catalog[i]; + if (!entry) continue; + if (!matchesFilter(entry, popupState.filter)) continue; + if (qLower) { + var n = String(entry.shortName || entry.name).toLowerCase(); + if (n.indexOf(qLower) < 0) continue; + } + out.push(entry); + } + return out; + } + + var dm = context.getResources().getDisplayMetrics(); + var sw = dm.widthPixels; + var sh = dm.heightPixels; + var panelWidth = Math.round(sw * 0.92); + var panelHeight = Math.round(sh * 0.90); + try { + if (self.calculateToolAppLayout) { + var toolLayout = self.calculateToolAppLayout(null); + if (toolLayout && toolLayout.width > 0) panelWidth = toolLayout.width; + if (toolLayout && toolLayout.height > 0) panelHeight = toolLayout.height; + } + } catch(eLayout) { safeLog(null, 'e', "catch " + String(eLayout)); } + if (panelWidth > self.dp(560)) panelWidth = self.dp(560); + if (panelWidth < self.dp(320)) panelWidth = Math.min(sw - self.dp(16), self.dp(320)); + if (panelHeight > sh - self.dp(24)) panelHeight = sh - self.dp(24); + if (panelHeight < self.dp(460)) panelHeight = Math.min(sh - self.dp(16), self.dp(460)); + + var padH = self.dp(14); + var padV = self.dp(12); + var gap = self.dp(8); + var colCount = 5; + var availW = panelWidth - padH * 2 - self.dp(10) * 2; + var cellW = Math.floor((availW - gap * (colCount - 1)) / colCount); + if (cellW < self.dp(46)) cellW = self.dp(46); + var iconSize = Math.max(self.dp(23), Math.min(self.dp(30), Math.floor(cellW * 0.46))); + var cellH = self.dp(70); + var headerH = self.dp(176); + var bottomH = self.dp(58); + var maxGridH = Math.max(self.dp(250), panelHeight - headerH - bottomH); + var rowCount = Math.max(3, Math.floor(maxGridH / (cellH + gap))); + var pageSize = colCount * rowCount; + + var rootOverlay = new android.widget.FrameLayout(context); + try { rootOverlay.setBackgroundColor(self.withAlpha(isDark ? 0xFF000000 : 0xFFFFFFFF, isDark ? 0.58 : 0.42)); } + catch(eOverlayBg) { rootOverlay.setBackgroundColor(0x33000000); } + rootOverlay.setClickable(true); + + var card = new android.widget.LinearLayout(context); + card.setOrientation(android.widget.LinearLayout.VERTICAL); + card.setPadding(padH, padV, padH, padV); + card.setBackground(self.ui.createStrokeDrawable(T.card, self.withAlpha(T.primaryDeep, isDark ? 0.28 : 0.22), self.dp(1), self.dp(24))); + try { card.setElevation(self.dp(10)); } catch(eCardElev) { safeLog(null, 'e', "catch " + String(eCardElev)); } + + var cardLp = new android.widget.FrameLayout.LayoutParams(panelWidth, panelHeight); + cardLp.gravity = android.view.Gravity.CENTER; + card.setLayoutParams(cardLp); + rootOverlay.addView(card); + + var overlayLp = new android.view.WindowManager.LayoutParams( + android.view.WindowManager.LayoutParams.MATCH_PARENT, + android.view.WindowManager.LayoutParams.MATCH_PARENT, + android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + android.graphics.PixelFormat.TRANSLUCENT + ); + overlayLp.softInputMode = android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING + | android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; + + try { wm.addView(rootOverlay, overlayLp); } catch(eAdd) { + safeLog(self.L, 'e', "icon picker addView fail: " + String(eAdd)); + return null; + } + + var isDismissed = false; + function dismiss() { + if (isDismissed) return; + isDismissed = true; + try { wm.removeView(rootOverlay); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + if (typeof onDismissCb === "function") { + try { onDismissCb(); } catch(eD) { safeLog(null, 'e', "catch " + String(eD)); } + } + } + rootOverlay.setOnClickListener(new android.view.View.OnClickListener({ onClick: function() { dismiss(); } })); + card.setOnClickListener(new android.view.View.OnClickListener({ onClick: function() { } })); + + var header = new android.widget.LinearLayout(context); + header.setOrientation(android.widget.LinearLayout.HORIZONTAL); + header.setGravity(android.view.Gravity.CENTER_VERTICAL); + + var titleBox = new android.widget.LinearLayout(context); + titleBox.setOrientation(android.widget.LinearLayout.VERTICAL); + titleBox.setGravity(android.view.Gravity.CENTER_VERTICAL); + titleBox.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); + + var titleTv = new android.widget.TextView(context); + titleTv.setText("岛上图标库"); + titleTv.setTextColor(textColor); + titleTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 17); + titleTv.setTypeface(null, android.graphics.Typeface.BOLD); + titleBox.addView(titleTv); + + var countTv = new android.widget.TextView(context); + countTv.setText("共 " + catalog.length + " 个图标"); + countTv.setTextColor(subTextColor); + countTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); + titleBox.addView(countTv); + header.addView(titleBox); + + var closeBtn = self.ui.createFlatButton(self, "✕", T.primaryDeep, function() { dismiss(); }); + closeBtn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 18); + closeBtn.setTypeface(null, android.graphics.Typeface.BOLD); + closeBtn.setPadding(self.dp(8), 0, self.dp(8), 0); + try { closeBtn.setBackground(self.ui.createStrokeDrawable(T.primarySoft, self.withAlpha(T.primaryDeep, isDark ? 0.30 : 0.22), self.dp(1), self.dp(18))); } catch(eCloseBg) {} + header.addView(closeBtn, new android.widget.LinearLayout.LayoutParams(self.dp(42), self.dp(38))); + card.addView(header); + + var searchEt = new android.widget.EditText(context); + searchEt.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); + searchEt.setTextColor(textColor); + try { searchEt.setHintTextColor(subTextColor); } catch(eHint) { safeLog(null, 'e', "catch " + String(eHint)); } + searchEt.setHint("寻找岛上图标,如 share / home"); + searchEt.setSingleLine(true); + searchEt.setFocusable(true); + searchEt.setFocusableInTouchMode(true); + searchEt.setPadding(self.dp(14), self.dp(10), self.dp(14), self.dp(10)); + searchEt.setBackground(self.ui.createStrokeDrawable(T.card2, self.withAlpha(T.primaryDeep, isDark ? 0.24 : 0.18), self.dp(1), self.dp(20))); + var searchLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(46)); + searchLp.setMargins(0, self.dp(10), 0, self.dp(8)); + card.addView(searchEt, searchLp); + searchEt.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function(v) { + self.touchActivity(); + try { + v.requestFocus(); + var imm = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE); + if (imm) imm.showSoftInput(v, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); + } catch(eIme) { safeLog(null, 'e', "catch " + String(eIme)); } + } + })); + + var filterScroll = new android.widget.HorizontalScrollView(context); + filterScroll.setHorizontalScrollBarEnabled(false); + var filterRow = new android.widget.LinearLayout(context); + filterRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); + filterRow.setGravity(android.view.Gravity.CENTER_VERTICAL); + filterRow.setPadding(0, 0, 0, 0); + filterScroll.addView(filterRow); + var filterLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(36)); + filterLp.setMargins(0, 0, 0, self.dp(4)); + card.addView(filterScroll, filterLp); + + function refreshFilterTags() { + try { + for (var i = 0; i < filterViews.length; i++) { + var item = filterViews[i]; + if (!item || !item.view) continue; + var active = item.name === popupState.filter; + item.view.setTextColor(active ? T.onPrimary : T.primaryDeep); + item.view.setTypeface(null, active ? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL); + item.view.setBackground(self.ui.createStrokeDrawable(active ? T.primary : T.primarySoft, self.withAlpha(T.primaryDeep, active ? 0.36 : 0.18), self.dp(1), self.dp(16))); + } + } catch(eTags) { safeLog(null, 'e', "catch " + String(eTags)); } + } + + for (var ft = 0; ft < filterTags.length; ft++) { + (function(tag) { + var chip = new android.widget.TextView(context); + chip.setText(tag); + chip.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + chip.setGravity(android.view.Gravity.CENTER); + chip.setPadding(self.dp(12), 0, self.dp(12), 0); + chip.setSingleLine(true); + chip.setClickable(true); + chip.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function() { + self.touchActivity(); + popupState.filter = tag; + popupState.currentPage = 0; + refreshFilterTags(); + renderGrid(); + } + })); + var chipLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, self.dp(30)); + chipLp.setMargins(0, 0, self.dp(7), 0); + filterRow.addView(chip, chipLp); + filterViews.push({ name: tag, view: chip }); + })(filterTags[ft]); + } + refreshFilterTags(); + + var pageBar = new android.widget.LinearLayout(context); + pageBar.setOrientation(android.widget.LinearLayout.HORIZONTAL); + pageBar.setGravity(android.view.Gravity.CENTER_VERTICAL); + pageBar.setPadding(self.dp(2), 0, self.dp(2), self.dp(4)); + var pageBarLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(34)); + card.addView(pageBar, pageBarLp); + + var btnPrev = self.ui.createFlatButton(self, "上一页", self.withAlpha(T.primaryDeep, 0.72), function() { + if (popupState.currentPage > 0) { popupState.currentPage--; renderGrid(); } + }); + pageBar.addView(btnPrev, new android.widget.LinearLayout.LayoutParams(self.dp(78), self.dp(30))); + + var pageInfo = new android.widget.TextView(context); + pageInfo.setTextColor(self.withAlpha(textColor, 0.76)); + pageInfo.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + pageInfo.setGravity(android.view.Gravity.CENTER); + pageInfo.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); + pageBar.addView(pageInfo); + + var btnNext = self.ui.createFlatButton(self, "下一页", self.withAlpha(T.primaryDeep, 0.72), function() { + popupState.currentPage++; + renderGrid(); + }); + pageBar.addView(btnNext, new android.widget.LinearLayout.LayoutParams(self.dp(78), self.dp(30))); + + var gridScroll = new android.widget.ScrollView(context); + gridScroll.setVerticalScrollBarEnabled(false); + gridScroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); + gridScroll.setLayoutParams(new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1)); + card.addView(gridScroll); + + var grid = new android.widget.GridLayout(context); + grid.setColumnCount(colCount); + grid.setPadding(self.dp(10), self.dp(6), self.dp(10), self.dp(8)); + gridScroll.addView(grid); + + var selectRow = new android.widget.LinearLayout(context); + selectRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); + selectRow.setGravity(android.view.Gravity.CENTER_VERTICAL); + selectRow.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8)); + selectRow.setBackground(self.ui.createStrokeDrawable(T.card2, self.withAlpha(T.primaryDeep, isDark ? 0.22 : 0.16), self.dp(1), self.dp(22))); + var selectRowLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(58)); + selectRowLp.setMargins(0, self.dp(6), 0, 0); + card.addView(selectRow, selectRowLp); + + var selectNameTv = new android.widget.TextView(context); + selectNameTv.setTextColor(textColor); + selectNameTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13); + selectNameTv.setSingleLine(true); + try { selectNameTv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(eEll) {} + selectNameTv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); + selectRow.addView(selectNameTv); + + var selectConfirm = self.ui.createSolidButton(self, "带回小岛", T.primary, T.onPrimary, function() { + self.touchActivity(); + try { + if (typeof onSelect === "function") onSelect(selectedName); + } catch(eSelect) { + safeLog(self.L, 'e', "icon onSelect err=" + String(eSelect)); + } + dismiss(); + }); + var confirmLp = new android.widget.LinearLayout.LayoutParams(self.dp(104), self.dp(42)); + confirmLp.setMargins(self.dp(10), 0, 0, 0); + selectRow.addView(selectConfirm, confirmLp); + + function updateSelectedLabel() { + try { + selectNameTv.setText(selectedName ? ("已选:" + String(selectedName)) : "还没选图标"); + } catch(eLabel) { safeLog(null, 'e', "catch " + String(eLabel)); } + } + updateSelectedLabel(); + + function renderGrid() { + try { + grid.removeAllViews(); + var q = String(searchEt.getText() || ""); + var matched = filterCatalog(q); + var totalPages = Math.max(1, Math.ceil(matched.length / pageSize)); + if (popupState.currentPage >= totalPages) popupState.currentPage = totalPages - 1; + if (popupState.currentPage < 0) popupState.currentPage = 0; + var start = popupState.currentPage * pageSize; + var pageItems = matched.slice(start, start + pageSize); + + pageInfo.setText("第 " + (popupState.currentPage + 1) + " / " + totalPages + " 页"); + btnPrev.setEnabled(popupState.currentPage > 0); + btnNext.setEnabled(popupState.currentPage < totalPages - 1); + + if (pageItems.length === 0) { + var emptyTv = new android.widget.TextView(context); + emptyTv.setText(popupState.filter === "收藏" ? "收藏夹还空着,点图标左上角 ☆ 收藏" : "没有找到这枚小图标"); + emptyTv.setTextColor(subTextColor); + emptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); + emptyTv.setGravity(android.view.Gravity.CENTER); + emptyTv.setPadding(0, self.dp(60), 0, self.dp(60)); + grid.addView(emptyTv); + return; + } + + grid.setColumnCount(colCount); + for (var idx = 0; idx < pageItems.length; idx++) { + (function(item) { + var frame = new android.widget.FrameLayout(context); + frame.setClickable(true); + var isSelected = selectedName === item.name; + var frameBg = isSelected ? T.primarySoft : self.withAlpha(cardColor, 0.96); + var frameStroke = isSelected ? self.withAlpha(T.primaryDeep, isDark ? 0.50 : 0.42) : self.withAlpha(T.primaryDeep, isDark ? 0.18 : 0.12); + frame.setBackground(self.ui.createStrokeDrawable(frameBg, frameStroke, isSelected ? self.dp(2) : self.dp(1), self.dp(15))); + + var cell = new android.widget.LinearLayout(context); + cell.setOrientation(android.widget.LinearLayout.VERTICAL); + cell.setGravity(android.view.Gravity.CENTER); + cell.setPadding(self.dp(4), self.dp(6), self.dp(4), self.dp(5)); + frame.addView(cell, new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)); + + var iv = new android.widget.ImageView(context); + iv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(iconSize, iconSize)); + iv.setScaleType(android.widget.ImageView.ScaleType.FIT_CENTER); + try { + var dr = self.getShortXIconDrawable(item.name); + if (dr) { iv.setImageDrawable(dr); } + } catch(eIcon) { safeLog(null, 'e', "catch " + String(eIcon)); } + cell.addView(iv); + + var tv = new android.widget.TextView(context); + tv.setText(String(item.shortName || item.name)); + tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 9); + tv.setGravity(android.view.Gravity.CENTER); + tv.setMaxLines(1); + try { tv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(eTvEll) {} + tv.setPadding(self.dp(2), self.dp(5), self.dp(2), 0); + tv.setTextColor(isSelected ? T.primaryDeep : subTextColor); + cell.addView(tv, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT)); + + var favBtn = new android.widget.TextView(context); + favBtn.setText(isFavoriteIcon(item.name) ? "★" : "☆"); + favBtn.setTextColor(isFavoriteIcon(item.name) ? T.primaryDeep : self.withAlpha(T.primaryDeep, 0.52)); + favBtn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13); + favBtn.setGravity(android.view.Gravity.CENTER); + favBtn.setTypeface(null, android.graphics.Typeface.BOLD); + favBtn.setBackground(self.ui.createRoundDrawable(isFavoriteIcon(item.name) ? T.primarySoft : self.withAlpha(T.card, 0.88), self.dp(9))); + favBtn.setClickable(true); + favBtn.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function() { + self.touchActivity(); + var added = toggleFavoriteIcon(item.name); + try { self.toast(added ? "已收藏到小岛" : "已取消收藏"); } catch(eFavToast) {} + if (popupState.filter === "收藏") popupState.currentPage = 0; + renderGrid(); + } + })); + var favLp = new android.widget.FrameLayout.LayoutParams(self.dp(18), self.dp(18)); + favLp.gravity = android.view.Gravity.TOP | android.view.Gravity.LEFT; + favLp.setMargins(self.dp(4), self.dp(4), 0, 0); + frame.addView(favBtn, favLp); + + if (isSelected) { + var badge = new android.widget.TextView(context); + badge.setText("✓"); + badge.setTextColor(T.onPrimary); + badge.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 10); + badge.setGravity(android.view.Gravity.CENTER); + badge.setTypeface(null, android.graphics.Typeface.BOLD); + badge.setBackground(self.ui.createRoundDrawable(T.primary, self.dp(9))); + var badgeLp = new android.widget.FrameLayout.LayoutParams(self.dp(18), self.dp(18)); + badgeLp.gravity = android.view.Gravity.TOP | android.view.Gravity.RIGHT; + badgeLp.setMargins(0, self.dp(4), self.dp(4), 0); + frame.addView(badge, badgeLp); + } + + frame.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function() { + self.touchActivity(); + selectedName = item.name; + updateSelectedLabel(); + renderGrid(); + } + })); + + var cellLp = new android.widget.GridLayout.LayoutParams(); + cellLp.width = cellW; + cellLp.height = cellH; + var col = idx % colCount; + var mr = (col === colCount - 1) ? 0 : gap; + cellLp.setMargins(0, 0, mr, gap); + frame.setLayoutParams(cellLp); + grid.addView(frame); + })(pageItems[idx]); + } + } catch(eRender) { + safeLog(self.L, 'e', "renderShortXIconGrid err=" + String(eRender)); + } + } + + searchEt.addTextChangedListener(new android.text.TextWatcher({ + beforeTextChanged: function() {}, + onTextChanged: function() { + self.touchActivity(); + popupState.currentPage = 0; + renderGrid(); + }, + afterTextChanged: function() {} + })); + + renderGrid(); + + return { close: dismiss }; +}; + + +FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { + var self = this; + var opt = opts || {}; + var currentColor = String(opt.currentColor || ""); + var currentIconName = String(opt.currentIconName || ""); + var onSelect = (typeof opt.onSelect === "function") ? opt.onSelect : null; + var onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null; + + var PT = this.getIslandPickerTheme ? this.getIslandPickerTheme() : null; + var isDark = PT ? PT.isDark : this.isDarkTheme(); + var C = this.ui.colors; + var T = PT ? PT.T : this.getAnimalIslandTheme(); + var textColor = PT ? PT.text : (isDark ? C.textPriDark : C.textPriLight); + var subTextColor = PT ? PT.sub : (isDark ? C.textSecDark : C.textSecLight); + + function getThemeTintHex() { + try { + if (self.ui.colors && self.ui.colors.accent) { + var c = self.ui.colors.accent; + return "#" + ("00000000" + (c >>> 0).toString(16)).slice(-8); + } + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + return "#FF4081"; + } + + function buildArgbHex(alphaByte, rgbHex) { + var a = ("00" + (Math.max(0, Math.min(255, Number(alphaByte || 0))) >>> 0).toString(16)).slice(-2); + var rgb = String(rgbHex || "000000").replace(/^#/, ""); + if (rgb.length === 3) rgb = rgb.split("").map(function(c){ return c+c; }).join(""); + if (rgb.length > 6) rgb = rgb.slice(-6); + while (rgb.length < 6) rgb = "0" + rgb; + return "#" + a + rgb; + } + + function extractTintRgbHex(hex) { + var h = String(hex || "").replace(/^#/, ""); + if (h.length >= 8) return h.slice(-6); + if (h.length === 6) return h; + if (h.length === 3) return h.split("").map(function(c){ return c+c; }).join(""); + return "000000"; + } + + function extractTintAlphaByte(hex) { + var h = String(hex || "").replace(/^#/, ""); + if (h.length >= 8) return parseInt(h.slice(0, 2), 16); + return 255; + } + + function normalizeTintColorValue(val) { + var s = String(val || "").trim(); + if (!s) return ""; + if (s.charAt(0) === "#") s = s.substring(1); + if (/^[0-9A-Fa-f]{1,8}$/.test(s)) { + while (s.length < 6) s = "0" + s; + if (s.length === 6) s = "FF" + s; + else if (s.length > 8) s = s.substring(0, 8); + return "#" + s.toUpperCase(); + } + return ""; + } + + var commonTintHexValues = [ + "#FFFF0000", "#FFFF5722", "#FFFF9800", "#FFFFC107", "#FFFFEB3B", + "#FFCDDC39", "#FF8BC34A", "#FF4CAF50", "#FF009688", "#FF00BCD4", + "#FF03A9F4", "#FF2196F3", "#FF3F51B5", "#FF673AB7", "#FF9C27B0", + "#FFE91E63", "#FF795548", "#FF9E9E9E", "#FF607D8B", "#FF000000", "#FFFFFFFF" + ]; + + // ========== 最近使用颜色 ========== + var RECENT_COLORS_KEY = "color_picker_recent"; + var MAX_RECENT_COLORS = 8; + var recentColors = []; + try { + var recentSaved = self.loadPanelState(RECENT_COLORS_KEY); + if (recentSaved && recentSaved.colors && recentSaved.colors.length) { + var rc; + for (rc = 0; rc < recentSaved.colors.length && rc < MAX_RECENT_COLORS; rc++) { + var rn = normalizeTintColorValue(recentSaved.colors[rc], false); + if (rn) recentColors.push(rn); + } + } + } catch(eRecentLoad) { safeLog(null, 'e', "catch " + String(eRecentLoad)); } + + function saveRecentColors() { + try { + self.savePanelState(RECENT_COLORS_KEY, { colors: recentColors.slice(0, MAX_RECENT_COLORS) }); + } catch(eRecentSave) { safeLog(null, 'e', "catch " + String(eRecentSave)); } + } + + function pushRecentColor(hex) { + var normalized = normalizeTintColorValue(hex, false); + if (!normalized) return; + var next = [normalized]; + var i; + for (i = 0; i < recentColors.length; i++) { + if (recentColors[i] !== normalized) { + next.push(recentColors[i]); + } + if (next.length >= MAX_RECENT_COLORS) break; + } + recentColors = next; + saveRecentColors(); + } + + var selectedColor = currentColor; + var isFollowTheme = !currentColor; + var currentBaseRgbHex = extractTintRgbHex(currentColor); + var currentAlphaByte = extractTintAlphaByte(currentColor); + + var popupResult = self.showPopupOverlay({ + title: "换颜色", + onDismiss: onDismiss, + builder: function(content, closePopup) { + // 图标预览区 + var previewRow = new android.widget.LinearLayout(context); + previewRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); + previewRow.setGravity(android.view.Gravity.CENTER_VERTICAL); + previewRow.setPadding(self.dp(12), self.dp(10), self.dp(12), self.dp(10)); + previewRow.setBackground(self.ui.createStrokeDrawable(T.primarySoft, self.withAlpha(T.primaryDeep, isDark ? 0.24 : 0.18), self.dp(1), self.dp(18))); + + var previewIv = new android.widget.ImageView(context); + previewIv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(48), self.dp(48))); + previewIv.setScaleType(android.widget.ImageView.ScaleType.FIT_CENTER); + previewRow.addView(previewIv); + + var previewLabel = new android.widget.TextView(context); + previewLabel.setText("小图标试衣间"); + previewLabel.setTextColor(subTextColor); + previewLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + previewLabel.setPadding(self.dp(12), 0, 0, 0); + previewRow.addView(previewLabel); + + content.addView(previewRow); + + function updatePreview() { + try { + var dr = null; + if (currentIconName) { + try { dr = self.getShortXIconDrawable(currentIconName); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + if (dr) { + if (!isFollowTheme && selectedColor) { + try { + var parsed = android.graphics.Color.parseColor(selectedColor); + dr.setColorFilter(parsed, android.graphics.PorterDuff.Mode.SRC_IN); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } else { + try { dr.clearColorFilter(); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + previewIv.setImageDrawable(dr); + } else { + previewIv.setImageDrawable(null); + } + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + updatePreview(); + + // ========== 最近使用颜色 ========== + var recentTitle = new android.widget.TextView(context); + recentTitle.setText("最近用过的小颜色"); + recentTitle.setTextColor(subTextColor); + recentTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + recentTitle.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(4)); + content.addView(recentTitle); + + var recentGrid = new android.widget.GridLayout(context); + recentGrid.setColumnCount(8); + recentGrid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(4)); + content.addView(recentGrid); + + var recentEmptyTv = new android.widget.TextView(context); + recentEmptyTv.setText("还没有最近颜色"); + recentEmptyTv.setTextColor(subTextColor); + recentEmptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); + recentEmptyTv.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(8)); + recentEmptyTv.setVisibility(android.view.View.GONE); + content.addView(recentEmptyTv); + + function refreshRecentGrid() { + try { + recentGrid.removeAllViews(); + if (!recentColors.length) { + recentEmptyTv.setVisibility(android.view.View.VISIBLE); + return; + } + recentEmptyTv.setVisibility(android.view.View.GONE); + var ri; + for (ri = 0; ri < recentColors.length && ri < MAX_RECENT_COLORS; ri++) { + (function(hex) { + var cell = new android.widget.FrameLayout(context); + var margin = self.dp(3); + try { + var lp = new android.widget.GridLayout.LayoutParams(); + lp.width = self.dp(28); + lp.height = self.dp(28); + lp.setMargins(margin, margin, margin, margin); + cell.setLayoutParams(lp); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + + var swatch = new android.view.View(context); + swatch.setLayoutParams(new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)); + try { + var bg = new android.graphics.drawable.GradientDrawable(); + bg.setColor(android.graphics.Color.parseColor(hex)); + bg.setCornerRadius(self.dp(5)); + swatch.setBackground(bg); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + cell.addView(swatch); + + if (selectedColor === hex) { + try { + var border = new android.graphics.drawable.GradientDrawable(); + border.setColor(android.graphics.Color.TRANSPARENT); + border.setCornerRadius(self.dp(5)); + border.setStroke(self.dp(2), T.primaryDeep); + cell.setForeground(border); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + + cell.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function() { + self.touchActivity(); + isFollowTheme = false; + selectedColor = hex; + currentBaseRgbHex = extractTintRgbHex(hex); + currentAlphaByte = extractTintAlphaByte(hex); + updatePreview(); + updateValueTv(); + refreshRecentGrid(); + refreshCommonGrid(); + syncRgbSeeks(); + } + })); + recentGrid.addView(cell); + })(recentColors[ri]); + } + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + refreshRecentGrid(); + + // 21 色常用颜色 + var commonTitle = new android.widget.TextView(context); + commonTitle.setText("糖果常用色"); + commonTitle.setTextColor(subTextColor); + commonTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + commonTitle.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(4)); + content.addView(commonTitle); + + var commonGrid = new android.widget.GridLayout(context); + commonGrid.setColumnCount(7); + commonGrid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(8)); + var ci; + for (ci = 0; ci < commonTintHexValues.length; ci++) { + (function(hex) { + var cell = new android.widget.FrameLayout(context); + var margin = self.dp(4); + try { + var lp = new android.widget.GridLayout.LayoutParams(); + lp.width = self.dp(32); + lp.height = self.dp(32); + lp.setMargins(margin, margin, margin, margin); + cell.setLayoutParams(lp); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + + var swatch = new android.view.View(context); + swatch.setLayoutParams(new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)); + try { + var bg = new android.graphics.drawable.GradientDrawable(); + bg.setColor(android.graphics.Color.parseColor(hex)); + bg.setCornerRadius(self.dp(6)); + swatch.setBackground(bg); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + cell.addView(swatch); + + if (selectedColor === hex) { + try { + var border = new android.graphics.drawable.GradientDrawable(); + border.setColor(android.graphics.Color.TRANSPARENT); + border.setCornerRadius(self.dp(6)); + border.setStroke(self.dp(3), T.primaryDeep); + cell.setForeground(border); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + + cell.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function() { + self.touchActivity(); + isFollowTheme = false; + selectedColor = hex; + currentBaseRgbHex = extractTintRgbHex(hex); + currentAlphaByte = extractTintAlphaByte(hex); + updatePreview(); + updateValueTv(); + refreshRecentGrid(); + refreshCommonGrid(); + syncRgbSeeks(); + } + })); + commonGrid.addView(cell); + })(commonTintHexValues[ci]); + } + content.addView(commonGrid); + + function refreshCommonGrid() { + try { + var count = commonGrid.getChildCount(); + var i; + for (i = 0; i < count; i++) { + var cell = commonGrid.getChildAt(i); + if (!cell) continue; + try { cell.setForeground(null); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + var idx = commonTintHexValues.indexOf(selectedColor); + if (idx >= 0 && idx < count) { + var matchedCell = commonGrid.getChildAt(idx); + if (matchedCell) { + try { + var border = new android.graphics.drawable.GradientDrawable(); + border.setColor(android.graphics.Color.TRANSPARENT); + border.setCornerRadius(self.dp(6)); + border.setStroke(self.dp(3), T.primaryDeep); + matchedCell.setForeground(border); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + } + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + + // 颜色值显示 + var valueTv = new android.widget.TextView(context); + valueTv.setTextColor(textColor); + valueTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13); + valueTv.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(4)); + content.addView(valueTv); + + function updateValueTv() { + valueTv.setText(isFollowTheme ? "当前:跟随岛屿主题" : ("当前:" + (selectedColor || "无"))); + } + updateValueTv(); + + // RGB 滑块 + var rgbLabels = ["R", "G", "B"]; + var rgbSeeks = []; + var rgbValTvs = []; + var ri; + for (ri = 0; ri < 3; ri++) { + (function(idx) { + var row = new android.widget.LinearLayout(context); + row.setOrientation(android.widget.LinearLayout.HORIZONTAL); + row.setGravity(android.view.Gravity.CENTER_VERTICAL); + row.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(4)); + + var label = new android.widget.TextView(context); + label.setText(rgbLabels[idx]); + label.setTextColor(textColor); + label.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + label.setMinWidth(self.dp(20)); + row.addView(label); + + var seek = new android.widget.SeekBar(context); + seek.setMax(255); + var seekLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1); + seekLp.setMargins(self.dp(8), 0, self.dp(8), 0); + seek.setLayoutParams(seekLp); + row.addView(seek); + rgbSeeks.push(seek); + + var valTv = new android.widget.TextView(context); + valTv.setTextColor(subTextColor); + valTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); + valTv.setMinWidth(self.dp(28)); + row.addView(valTv); + rgbValTvs.push(valTv); + + seek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({ + onProgressChanged: function(s, progress, fromUser) { + if (!fromUser) return; + valTv.setText(String(progress)); + var r = rgbSeeks[0].getProgress(); + var g = rgbSeeks[1].getProgress(); + var b = rgbSeeks[2].getProgress(); + var hex = ("00" + (r >>> 0).toString(16)).slice(-2) + ("00" + (g >>> 0).toString(16)).slice(-2) + ("00" + (b >>> 0).toString(16)).slice(-2); + currentBaseRgbHex = hex; + isFollowTheme = false; + selectedColor = buildArgbHex(currentAlphaByte, currentBaseRgbHex); + updatePreview(); + updateValueTv(); + refreshRecentGrid(); + refreshCommonGrid(); + }, + onStartTrackingTouch: function() {}, + onStopTrackingTouch: function() {} + })); + + content.addView(row); + })(ri); + } + + function syncRgbSeeks() { + try { + var initR = parseInt(currentBaseRgbHex.slice(0, 2), 16) || 0; + var initG = parseInt(currentBaseRgbHex.slice(2, 4), 16) || 0; + var initB = parseInt(currentBaseRgbHex.slice(4, 6), 16) || 0; + rgbSeeks[0].setProgress(initR); + rgbSeeks[1].setProgress(initG); + rgbSeeks[2].setProgress(initB); + rgbValTvs[0].setText(String(initR)); + rgbValTvs[1].setText(String(initG)); + rgbValTvs[2].setText(String(initB)); + } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + } + syncRgbSeeks(); + + // 透明度滑块 + var alphaRow = new android.widget.LinearLayout(context); + alphaRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); + alphaRow.setGravity(android.view.Gravity.CENTER_VERTICAL); + alphaRow.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(8)); + + var alphaLabel = new android.widget.TextView(context); + alphaLabel.setText("A"); + alphaLabel.setTextColor(textColor); + alphaLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + alphaLabel.setMinWidth(self.dp(20)); + alphaRow.addView(alphaLabel); + + var alphaSeek = new android.widget.SeekBar(context); + alphaSeek.setMax(255); + var alphaSeekLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1); + alphaSeekLp.setMargins(self.dp(8), 0, self.dp(8), 0); + alphaSeek.setLayoutParams(alphaSeekLp); + alphaRow.addView(alphaSeek); + + var alphaValTv = new android.widget.TextView(context); + alphaValTv.setTextColor(subTextColor); + alphaValTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); + alphaValTv.setMinWidth(self.dp(28)); + alphaRow.addView(alphaValTv); + + alphaSeek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({ + onProgressChanged: function(s, progress, fromUser) { + if (!fromUser) return; + alphaValTv.setText(String(progress)); + currentAlphaByte = progress; + isFollowTheme = false; + selectedColor = buildArgbHex(currentAlphaByte, currentBaseRgbHex); + updatePreview(); + updateValueTv(); + refreshRecentGrid(); + refreshCommonGrid(); + }, + onStartTrackingTouch: function() {}, + onStopTrackingTouch: function() {} + })); + + alphaSeek.setProgress(currentAlphaByte); + alphaValTv.setText(String(currentAlphaByte)); + content.addView(alphaRow); + + // 操作按钮 + var actionRow = new android.widget.LinearLayout(context); + actionRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); + actionRow.setGravity(android.view.Gravity.CENTER); + actionRow.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8)); + + var btnClear = self.ui.createFlatButton(self, "恢复默认", T.primaryDeep, function() { + self.touchActivity(); + isFollowTheme = true; + selectedColor = ""; + updatePreview(); + updateValueTv(); + refreshRecentGrid(); + refreshCommonGrid(); + syncRgbSeeks(); + alphaSeek.setProgress(255); + alphaValTv.setText("255"); + currentAlphaByte = 255; + }); + actionRow.addView(btnClear); + + var btnOk = self.ui.createSolidButton(self, "保存颜色", T.primary, T.onPrimary, function() { + self.touchActivity(); + try { + var finalColor = isFollowTheme ? "" : String(selectedColor || ""); + if (!isFollowTheme && selectedColor) { + pushRecentColor(selectedColor); + } + if (typeof onSelect === "function") { + try { onSelect(finalColor); } catch(eOnSelect) { + safeLog(self.L, 'e', "colorPicker onSelect err=" + String(eOnSelect)); + } + } + } catch(e) { + safeLog(self.L, 'e', "colorPicker confirm err=" + String(e)); + } + closePopup(); + }); + actionRow.addView(btnOk); + + content.addView(actionRow); + } + }); + + return popupResult; +}; diff --git a/code/th_16_entry.js b/code/th_16_entry.js index 931550b..6f32ff5 100644 --- a/code/th_16_entry.js +++ b/code/th_16_entry.js @@ -7,7 +7,7 @@ function runOnMainSync(fn, timeoutMs) { if (mainLooper !== null && myLooper !== null && myLooper === mainLooper) { return { ok: true, value: fn() }; } - } catch (eLoop) {} + } catch (eLoop) { safeLog(this.L, 'e', "runOnMainSync looper check failed: " + String(eLoop)); } try { var box = { ok: false, value: null, error: null }; @@ -109,7 +109,7 @@ FloatBallAppWM.prototype.close = function() { if (android.os.Build.VERSION.SDK_INT >= 18) this.state.ht.quitSafely(); else this.state.ht.quit(); } - } catch (eQ) {} + } catch (eQ) { safeLog(this.L, 'e', "shortcut picker singleton destroy failed: " + String(eQ)); } // # 清理图标加载线程 try { @@ -117,13 +117,13 @@ FloatBallAppWM.prototype.close = function() { if (android.os.Build.VERSION.SDK_INT >= 18) this._iconLoader.ht.quitSafely(); else this._iconLoader.ht.quit(); } - } catch (eIcon) {} + } catch (eIcon) { safeLog(this.L, 'e', "icon picker singleton destroy failed: " + String(eIcon)); } try { if (this.__scIconLoaderSingleton && this.__scIconLoaderSingleton.ht) { if (android.os.Build.VERSION.SDK_INT >= 18) this.__scIconLoaderSingleton.ht.quitSafely(); else this.__scIconLoaderSingleton.ht.quit(); } - } catch (eScIcon) {} + } catch (eScIcon) { safeLog(this.L, 'e', "shortcut icon loader singleton destroy failed: " + String(eScIcon)); } try { this.__scIconLoaderSingleton = null; } catch (eScIcon2) {} safeLog(this.L, 'i', "close done"); @@ -211,7 +211,7 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) { var closeRcv = registerReceiverOnMain(this.config.ACTION_CLOSE_ALL, function(ctx, it) { try { h.post(new JavaAdapter(java.lang.Runnable, { - run: function() { try { self.close(); } catch (e1) {} } + run: function() { try { self.close(); } catch (e1) { safeLog(self.L, 'e', "close broadcast handler failed: " + String(e1)); } } })); } catch (e2) {} }); @@ -250,7 +250,7 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) { var reason = ""; try { reason = String(intent.getStringExtra("reason") || ""); } catch (eReason) { reason = ""; } h.post(new JavaAdapter(java.lang.Runnable, { - run: function() { try { self.handleSystemUiDismiss(reason); } catch (eSysDlg) {} } + run: function() { try { self.handleSystemUiDismiss(reason); } catch (eSysDlg) { safeLog(self.L, 'e', "system ui dismiss handler failed: " + String(eSysDlg)); } } })); } catch (eSysDlgOuter) {} }); @@ -282,10 +282,10 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) { } catch (eAdd) { startBox.ok = false; startBox.err = "悬浮球 addView 失败: " + String(eAdd); - try { self.toast(startBox.err); } catch (eT) {} + try { self.toast(startBox.err); } catch (eT) { safeLog(self.L, 'e', "toast start error failed: " + String(eT)); } if (self.L) self.L.fatal("addView ball fail err=" + String(eAdd)); self.state.addedBall = false; - try { self.close(); } catch (eC) {} + try { self.close(); } catch (eC) { safeLog(self.L, 'e', "close after start failure failed: " + String(eC)); } return; } diff --git a/manifest.json b/manifest.json index cb78ac8..0479e49 100644 --- a/manifest.json +++ b/manifest.json @@ -2,8 +2,8 @@ "alg": "SHA256withRSA", "files": { "th_01_base.js": { - "sha256": "6b7a51c97f76b797b176c8fcf712dbafb8d33ebab448d0c7574f26568dcd14df", - "size": 56392 + "sha256": "d4b409e42d3b3d5b3b107c4b2338fe83ad9b7d6d7f7f6bc0bbc9e28a94a866cd", + "size": 53253 }, "th_02_core.js": { "sha256": "15bb9bfbd19a673d442e221b0a00a456ed5f87af2666b9c73b117d6223faeecd", @@ -26,16 +26,16 @@ "size": 22909 }, "th_07_shortcut.js": { - "sha256": "7b2dbd1e35c636cca4ccce335dfb9e0b972342972ce012116ff4bbcfc438caa1", - "size": 72992 + "sha256": "f992dcd6dd7993690d789e2a04b92d4e4be96b708a1a21920c5e8038f2d814c8", + "size": 48962 }, "th_08_content.js": { "sha256": "8a76f15dfd1292081cba4b2dd218424be66540350e2807065421a6176a86c2db", "size": 7938 }, "th_09_animation.js": { - "sha256": "2a991cbf4bf38bfb3d1dda876c0c86778e5dbbe75d6eb268a068e3f200ce0c13", - "size": 40229 + "sha256": "5e0e70319c13c39674b3524e30d6ade5c4188ad4f52f331c9bee83193580d0cd", + "size": 40549 }, "th_10_shell.js": { "sha256": "0ed793079c2f6ba7d29f4c0d411705cb72419f45f572cbe37ed32ac16527a8bc", @@ -54,19 +54,23 @@ "size": 21198 }, "th_14_panels.js": { - "sha256": "185d7ed9d85ffa7e31b70abbd425c0d79d6a435a4c5cb6dc7610d942fdc0fc87", - "size": 262019 + "sha256": "611c00c8362c346ff0a674aa29cd98e6240cbbe36a2d6c171e0188996bca22a5", + "size": 213491 }, "th_15_extra.js": { "sha256": "1aa3bbe9c99e047cfaf663f17501d87bba1b5810a8002e66bca1f6ea605371a4", "size": 99378 }, + "th_15_picker.js": { + "sha256": "9d59acbbee0db6058e2eba2db310f75b581cbd84d1b8fdaa48f13e244f1cd4f8", + "size": 48989 + }, "th_16_entry.js": { - "sha256": "6c59d9891cd010647f84c3db93f1cf95c7bbfb758470ea21044bf72eb8ff73d1", - "size": 12799 + "sha256": "9b42884503be328c19a8f95e9e9ff986b1938586909bcf515971f3ff881133be", + "size": 13423 } }, "keyId": "toolhub-targets-2026-rsa3072", "schema": 2, - "version": 20260518211239 + "version": 20260521154208 } diff --git a/manifest.sig b/manifest.sig index 182ab86..099f9b5 100644 --- a/manifest.sig +++ b/manifest.sig @@ -1 +1 @@ -PrkoHVO63rKwu62gMoRZjInrjs7YRo6ShBWdms6/U+qDOdLH6uFzIZI8TqLb9AIydoxjoT96r4FuPlL+oOO+FhsjEhlN/mNqUWN3EGvrZn/EyP6P/k4RTHjKSeDKWDzRpbHcKjVJ8FdaMCcXctdPEqiI1B8MLYRZRBwJhMcyLIhh1V0GihTfEvAU6xr+oic7WpO9aMqg4S5Cvi7i9K3mW3QkahYtu1deyB4kE1oGyiYDx60FG2iSOUOykKyt3+Hx9xhU0DB1ZE6T9/9SK8+Ba5kHAALqeUeQGX+49wGvund76bNHmdOfzCSfnKa2enMValNQKNH/hKPFJheh7FO84u+tRXfsBz9SlPVPEsVDaNXgEIJCp+BwU9oYPe7i0SGfmOkBse7agzcLktLUydN8C+xK9xqI4l2vWnOkk5Z/Fj0jVC5+q8WrLhuCLee70RQnHCTQHxh4kDeAvWHjFslCHqSj7TAu7r9IVOvqSqGLFcwiWgoM79XyFf8l0TaXTfwz +GfMow5N7dSXGnk4mFX5ENNdLfimG1M2lzV6r3Eur0YpHibkk0p8/Ghz8HnwN5a4sAs7caGEm0msdtbLV4UphuVJoYNUsH2qML7umAnUTyvbgZ0PqV5h0Svw7XXpzYoH9JiHM8bthzKxNqBtftKRFiAIC76Z987WmU5zfflAONWD/LPQixbGndB0CqbubxoATkBzERDb1u5YmqajkfnqPJFuuzhVamfqrTA4UZUbAuwiL48QsABEuiUZdpDa/HVtIEJ+1wO9/OtAhLGOscoNlUJz1OCffSowJ32OUMpwJJHKBmOmVY9S2x+WHOHKE7psuB8DLoVSslsLKlhL76pdblTyMJL/g76GpEx7y/CIoUs0x7ayBsytwl6yCYineyd37PYpNZql+PJ0l9pcDr2gkI54ltxam5z3cWCKAIICfm//Y4R4nEk/GbLI2oXKHoHMskSRedYnIrqH64FUHj6KzcAn+S+uXw91OCaXPM4in0D2oc1C3SKUHtSHM27YQYC6a diff --git a/scripts/generate_signed_manifest.py b/scripts/generate_signed_manifest.py index 9e47fe4..3bb8dc9 100755 --- a/scripts/generate_signed_manifest.py +++ b/scripts/generate_signed_manifest.py @@ -28,7 +28,7 @@ MODULES = [ "th_05_persistence.js", "th_06_icon_parser.js", "th_07_shortcut.js", "th_08_content.js", "th_09_animation.js", "th_10_shell.js", "th_11_action.js", "th_12_rebuild.js", "th_13_panel_ui.js", - "th_14_panels.js", "th_15_extra.js", "th_16_entry.js", + "th_14_panels.js", "th_15_picker.js", "th_15_extra.js", "th_16_entry.js", ]