Files
ShortX_ToolHub/code/th_14_panels.js
Hermes f13f3268c3 fix(icon): 添\u52a0 getShortXIconDrawable 方法和占位符,\u4fee\u590d\u56fe\u6807\u4e0d\u663e\u793a
- 补\u5145\u7f3a\u5931的 getShortXIconDrawable() 方法
- 图\u6807\u83b7\u53d6\u5931\u8d25\u65f6\u663e\u793a\u6d45\u8272\u5360\u4f4d\u7b26
2026-04-20 17:59:33 +08:00

4525 lines
199 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @version 1.0.0
FloatBallAppWM.prototype.buildSettingsPanelView = function() {
this.beginEditConfig();
var isDark = this.isDarkTheme();
var C = this.ui.colors;
var bgColor = isDark ? C.bgDark : C.bgLight;
var cardColor = isDark ? C.cardDark : C.cardLight;
var textColor = isDark ? C.textPriDark : C.textPriLight;
var panel = this.ui.createStyledPanel(this, 16);
var header = this.ui.createStyledHeader(this, 8);
// 内存显示
var memTv = new android.widget.TextView(context);
memTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 10);
memTv.setTextColor(isDark ? C.textSecDark : C.textSecLight);
memTv.setPadding(0, 0, this.dp(8), 0);
function updateMem() {
try {
var rt = java.lang.Runtime.getRuntime();
var total = rt.totalMemory() / 1024 / 1024;
var free = rt.freeMemory() / 1024 / 1024;
var used = total - free;
var max = rt.maxMemory() / 1024 / 1024;
memTv.setText("Mem: " + used.toFixed(0) + "/" + max.toFixed(0) + "M");
} catch(e) { memTv.setText("Mem: ?"); }
}
updateMem();
memTv.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() { updateMem(); self.toast("内存已刷新"); }
}));
header.addView(memTv);
// 占位 View 顶替标题位置,让右侧按钮靠右
header.addView(this.ui.createSpacer(this));
var self = this;
// # 固定按钮:项目文档(放在第一位)
var btnDoc = this.ui.createFlatButton(this, "📖 文档", C.primary, function() {
try {
var intent = new android.content.Intent(android.content.Intent.ACTION_VIEW);
intent.setData(android.net.Uri.parse("https://xin-blog.com/114.html"));
intent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} catch(e) {
self.toast("无法打开文档链接");
}
});
btnDoc.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
header.addView(btnDoc);
// [恢复] 按钮管理
var btnMgr = this.ui.createFlatButton(this, "按钮管理", C.primary, function() {
self.hideSettingsPanel();
self.showPanelAvoidBall("btn_editor");
});
btnMgr.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
header.addView(btnMgr);
// 预览模式开关
var previewBox = new android.widget.LinearLayout(context);
previewBox.setOrientation(android.widget.LinearLayout.HORIZONTAL);
previewBox.setGravity(android.view.Gravity.CENTER_VERTICAL);
previewBox.setPadding(this.dp(8), this.dp(2), this.dp(4), this.dp(2));
previewBox.setBackground(self.ui.createRoundDrawable(self.withAlpha(C.accent, 0.1), self.dp(16))); // 浅色背景提示
var tvPreview = new android.widget.TextView(context);
tvPreview.setText("实时预览");
tvPreview.setTextColor(C.accent);
tvPreview.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tvPreview.setTypeface(null, android.graphics.Typeface.BOLD);
tvPreview.setPadding(0, 0, this.dp(4), 0);
previewBox.addView(tvPreview);
var switchPreview = new android.widget.Switch(context);
try { switchPreview.setTextOn(""); switchPreview.setTextOff(""); } catch (eT) {}
try {
var states = [[android.R.attr.state_checked], [-android.R.attr.state_checked]];
var thumbColors = [C.accent, isDark ? (0xFF555555 | 0) : (0xFFCCCCCC | 0)];
var trackColors = [self.withAlpha(C.accent, 0.5), self.withAlpha(isDark ? (0xFF555555 | 0) : (0xFFCCCCCC | 0), 0.5)];
switchPreview.setThumbTintList(new android.content.res.ColorStateList(states, thumbColors));
switchPreview.setTrackTintList(new android.content.res.ColorStateList(states, trackColors));
} catch(e) {}
switchPreview.setChecked(!!self.state.previewMode);
switchPreview.setOnCheckedChangeListener(new android.widget.CompoundButton.OnCheckedChangeListener({
onCheckedChanged: function(btn, checked) {
self.touchActivity();
self.state.previewMode = !!checked;
if (checked) {
self.toast("预览模式已开启:修改配置实时生效");
tvPreview.setTextColor(C.accent);
previewBox.setBackground(self.ui.createRoundDrawable(self.withAlpha(C.accent, 0.1), self.dp(16)));
self.refreshPreview();
} else {
self.toast("预览模式已关闭");
tvPreview.setTextColor(0xFF888888 | 0);
previewBox.setBackground(null);
if (self.state.addedPanel) self.hideMainPanel();
self.rebuildBallForNewSize(true);
}
}
}));
previewBox.addView(switchPreview);
header.addView(previewBox);
// [恢复] 保存按钮(放在最后一位)
var btnOk = this.ui.createSolidButton(this, "保存", C.primary, android.graphics.Color.WHITE, function() {
try {
self.touchActivity();
if (self.L) self.L.i("settings confirm click");
var r = self.commitPendingUserCfg();
self.state.previewMode = false;
if (self.state.addedPanel) self.hideMainPanel();
self.hideSettingsPanel();
if (r && r.ok) self.toast("已确认并生效");
else self.toast("确认失败: " + (r && r.reason ? r.reason : (r && r.err ? r.err : "unknown")));
} catch (e0) {
try { self.toast("确认异常: " + String(e0)); } catch (eT) {}
if (self.L) self.L.e("settings confirm err=" + String(e0));
}
});
btnOk.setPadding(self.dp(12), self.dp(6), self.dp(12), self.dp(6));
btnOk.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
header.addView(btnOk);
// 暴露 Header
panel.setTag(header);
panel.addView(header);
var scroll = new android.widget.ScrollView(context);
try { scroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch (eOS) {}
try { scroll.setVerticalScrollBarEnabled(false); } catch (eSB) {}
var box = new android.widget.LinearLayout(context);
box.setOrientation(android.widget.LinearLayout.VERTICAL);
box.setPadding(0, this.dp(4), 0, this.dp(4));
scroll.addView(box);
scroll.setOnTouchListener(new JavaAdapter(android.view.View.OnTouchListener, {
onTouch: function(v, e) { self.touchActivity(); return false; }
}));
var schema = this.getConfigSchema();
var currentCard = null;
function createCard() {
var c = new android.widget.LinearLayout(context);
c.setOrientation(android.widget.LinearLayout.VERTICAL);
c.setBackground(self.ui.createRoundDrawable(cardColor, self.dp(12)));
try { c.setElevation(self.dp(2)); } catch(e){}
try { c.setClipToOutline(true); } catch(e){}
var lp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(self.dp(2), self.dp(6), self.dp(2), self.dp(6));
c.setLayoutParams(lp);
// Remove padding to allow items to be full-width (for ripple)
c.setPadding(0, 0, 0, self.dp(4));
return c;
}
for (var i = 0; i < schema.length; i++) {
(function(item) {
if (item && String(item.type) === "section") {
currentCard = createCard();
box.addView(currentCard);
self.createSectionHeader(item, currentCard);
} else {
if (!currentCard) {
currentCard = createCard();
box.addView(currentCard);
}
var needDivider = (currentCard.getChildCount() > 0);
if (currentCard.getChildCount() === 1) needDivider = false;
self.createSettingItemView(item, currentCard, needDivider);
}
})(schema[i]);
}
panel.addView(scroll);
return panel;
};
// =======================【按钮编辑面板】======================
FloatBallAppWM.prototype.buildButtonEditorPanelView = function() {
var self = this;
// # 状态管理editingIndex (null=列表, -1=新增, >=0=编辑)
if (this.state.editingButtonIndex === undefined) {
this.state.editingButtonIndex = null;
}
// # 事务状态管理:确保操作的是临时副本
if (!this.state.keepBtnEditorState || !this.state.tempButtons) {
// 首次进入或非刷新状态,克隆一份配置作为临时状态
var current = ConfigManager.loadButtons();
this.state.tempButtons = JSON.parse(JSON.stringify(current));
}
this.state.keepBtnEditorState = false; // 重置标志
var buttons = this.state.tempButtons;
var isEditing = (this.state.editingButtonIndex !== null);
var isDark = this.isDarkTheme();
var C = this.ui.colors;
// 颜色配置
var bgColor = isDark ? C.bgDark : C.bgLight;
var cardColor = isDark ? C.cardDark : C.cardLight;
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 panel = this.ui.createStyledPanel(this, 16);
// --- 标题栏 ---
var header = this.ui.createStyledHeader(this, 12);
// # 列表滚动位置保持:用于在刷新按钮列表(排序/删除/切换启用)后,恢复到用户当前滚动位置
var __btnEditorListScroll = null;
// Title removed to avoid duplication with wrapper
// Placeholder to push buttons to the right
header.addView(this.ui.createSpacer(this));
// 刷新面板辅助函数
function refreshPanel() {
// # 列表滚动位置保持:刷新前记录当前 ScrollView 的 scrollY避免操作后回到第一页
try {
if (__btnEditorListScroll) self.state.btnEditorListScrollY = __btnEditorListScroll.getScrollY();
} catch(eSY) {}
// 标记为刷新操作,保留 tempButtons 状态
self.state.keepBtnEditorState = true;
// 关闭当前面板并重新打开
self.showPanelAvoidBall("btn_editor");
}
// --- 列表模式 ---
if (!isEditing) {
// 头部布局优化:[ 提示文字 ... 新增 ]
header.removeAllViews(); // 清除之前的 dummy
// 提示文字 (左侧)
var hintTv = new android.widget.TextView(context);
hintTv.setText("长按卡片排序");
hintTv.setTextColor(subTextColor);
hintTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
header.addView(hintTv);
// Spacer
header.addView(self.ui.createSpacer(self));
// 新增按钮 (右侧)
var btnAdd = self.ui.createSolidButton(self, "新增", C.primary, android.graphics.Color.WHITE, function() {
self.state.editingButtonIndex = -1;
refreshPanel();
});
// 调整新增按钮样式,使其更紧凑
btnAdd.setPadding(self.dp(12), self.dp(6), self.dp(12), self.dp(6));
btnAdd.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
header.addView(btnAdd);
panel.addView(header);
// 暴露 Header 给 DragListener
panel.setTag(header);
// 列表区域
var scroll = new android.widget.ScrollView(context);
__btnEditorListScroll = scroll; // # 列表滚动位置保持:让 refreshPanel 能拿到当前列表的滚动位置
try { scroll.setVerticalScrollBarEnabled(false); } catch(e){}
var list = new android.widget.LinearLayout(context);
list.setOrientation(android.widget.LinearLayout.VERTICAL);
list.setPadding(0, self.dp(2), 0, self.dp(2));
for (var i = 0; i < buttons.length; i++) {
(function(idx) {
var btnCfg = buttons[idx];
// # 启用/禁用状态:禁用项在按钮页不显示,但在管理页需要标识出来
var __enabled = true;
try { __enabled = (btnCfg && btnCfg.enabled === false) ? false : true; } catch(eEn) { __enabled = true; }
// 卡片容器
var card = new android.widget.LinearLayout(context);
card.setOrientation(android.widget.LinearLayout.HORIZONTAL);
card.setGravity(android.view.Gravity.CENTER_VERTICAL);
// 使用稍微不同的背景色以突出卡片
var cardBgColor = isDark ? self.withAlpha(C.cardDark, 0.8) : self.withAlpha(C.cardLight, 0.8);
card.setBackground(self.ui.createRoundDrawable(cardBgColor, self.dp(16)));
try { card.setElevation(self.dp(4)); } catch(e){}
var cardLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
cardLp.setMargins(self.dp(4), self.dp(4), self.dp(4), self.dp(4));
card.setLayoutParams(cardLp);
card.setPadding(self.dp(14), self.dp(10), self.dp(10), self.dp(10)); // # 行高优化:降低上下内边距,避免"按钮管理列表"单行过高
// # 视觉提示:禁用项整体变淡,方便一眼区分
if (!__enabled) {
try { card.setAlpha(0.55); } catch(eA) {}
} else {
try { card.setAlpha(1.0); } catch(eA2) {}
}
// 点击编辑
card.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.state.editingButtonIndex = idx;
refreshPanel();
}
}));
// # 左侧图标:按钮管理列表与按钮页一致显示其对应的 icon
// # 说明:复用 resolveIconDrawable支持 iconPath / app 图标 / shortcut 图标 / resId避免重复实现与性能浪费
var iconIv = new android.widget.ImageView(context);
try {
var dr0 = self.resolveIconDrawable(btnCfg);
if (dr0) iconIv.setImageDrawable(dr0);
} catch(eIcon0) {}
try { iconIv.setScaleType(android.widget.ImageView.ScaleType.CENTER_INSIDE); } catch(eScale){}
var iconSizeDp = 24;
try { iconSizeDp = Number(self.config.PANEL_ICON_SIZE_DP || 24); } catch(eSz){}
// # 管理页行高更紧凑:限制 icon 尺寸,避免挤占文字区域
if (!iconSizeDp || iconSizeDp < 18) iconSizeDp = 24;
if (iconSizeDp > 32) iconSizeDp = 32;
var iconLp = new android.widget.LinearLayout.LayoutParams(self.dp(iconSizeDp), self.dp(iconSizeDp));
iconLp.rightMargin = self.dp(10);
iconIv.setLayoutParams(iconLp);
card.addView(iconIv);
// 中间文本信息
var textContainer = new android.widget.LinearLayout(context);
textContainer.setOrientation(android.widget.LinearLayout.VERTICAL);
var textLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
textLp.weight = 1;
textContainer.setLayoutParams(textLp);
// 标题
var infoTv = new android.widget.TextView(context);
infoTv.setText(String(btnCfg.title || "无标题"));
infoTv.setTextColor(textColor);
infoTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 15);
infoTv.setTypeface(null, android.graphics.Typeface.BOLD);
textContainer.addView(infoTv);
// 类型/详情描述
var detailTv = new android.widget.TextView(context);
var desc = "Shell";
if(btnCfg.type === "app") desc = "应用: " + (btnCfg.pkg||"");
else if(btnCfg.type === "broadcast") desc = "广播: " + (btnCfg.action||"");
else if(btnCfg.type === "shortcut") desc = "快捷方式";
else desc = "命令: " + (btnCfg.cmd || "").substring(0, 20) + "...";
detailTv.setText(desc);
detailTv.setTextColor(subTextColor);
detailTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
detailTv.setSingleLine(true);
detailTv.setEllipsize(android.text.TextUtils.TruncateAt.END);
textContainer.addView(detailTv);
card.addView(textContainer);
// 右侧操作区
var actions = new android.widget.LinearLayout(context);
actions.setOrientation(android.widget.LinearLayout.HORIZONTAL);
actions.setGravity(android.view.Gravity.CENTER_VERTICAL); // 上移/下移(排序按钮布局优化:扩大点击面积,顶部/底部也占位,避免难点)
// 说明:原先 ▲▼ 太小且只有可用时才出现,手指很难点中;这里改为固定 2 个大按钮44dp不可用则置灰禁用。
var sortBox = new android.widget.LinearLayout(context);
sortBox.setOrientation(android.widget.LinearLayout.HORIZONTAL); // # 排序按钮改为横向排列,减少占高
sortBox.setGravity(android.view.Gravity.CENTER);
// 按钮公共样式
var mkSortBtn = function(txt, enabled, onClickFn) {
var tv = new android.widget.TextView(context);
tv.setText(txt);
tv.setGravity(android.view.Gravity.CENTER);
tv.setTextSize(12); // # 排序按钮适量缩小字号
tv.setMinWidth(self.dp(36)); // # 排序按钮适量缩小点击块
tv.setMinHeight(self.dp(36)); // # 排序按钮适量缩小点击块
tv.setPadding(0, 0, 0, 0);
// 轻量边框+圆角,提升"可点击"视觉
try {
var bg = new android.graphics.drawable.GradientDrawable();
bg.setColor(android.graphics.Color.TRANSPARENT);
bg.setCornerRadius(self.dp(8)); // # 小一点圆角更紧凑
bg.setStroke(self.dp(1), self.withAlpha(subTextColor, 0.22));
tv.setBackground(bg);
} catch (eBg) {}
if (!enabled) {
tv.setEnabled(false);
tv.setTextColor(self.withAlpha(subTextColor, 0.25));
} else {
tv.setEnabled(true);
tv.setTextColor(subTextColor);
tv.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
try { onClickFn(); } catch (eSort) {}
}
}));
}
return tv;
};
// 上移按钮(顶部也占位)
var canUp = (idx > 0);
var u = mkSortBtn("▲", canUp, function() {
var temp = buttons[idx];
buttons[idx] = buttons[idx - 1];
buttons[idx - 1] = temp;
refreshPanel();
});
sortBox.addView(u);
// 下移按钮(底部也占位)
var canDown = (idx < buttons.length - 1);
var d = mkSortBtn("▼", canDown, function() {
var temp = buttons[idx];
buttons[idx] = buttons[idx + 1];
buttons[idx + 1] = temp;
refreshPanel();
});
// 两按钮之间留一点间距,避免误触
try {
var lpD = new android.widget.LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT
);
lpD.leftMargin = self.dp(6); // # 横向排列:用左间距代替上间距
d.setLayoutParams(lpD);
} catch (eLp) {}
sortBox.addView(d);
actions.addView(sortBox);
// # 禁用/启用:管理页直接切换显示状态(禁用后按钮页不显示该按钮)
var btnToggle = new android.widget.TextView(context);
btnToggle.setText(__enabled ? "禁用" : "启用");
btnToggle.setGravity(android.view.Gravity.CENTER);
btnToggle.setTextSize(12);
btnToggle.setMinWidth(self.dp(44));
btnToggle.setMinHeight(self.dp(36));
btnToggle.setPadding(self.dp(6), 0, self.dp(6), 0);
try {
var tgBg = new android.graphics.drawable.GradientDrawable();
tgBg.setColor(android.graphics.Color.TRANSPARENT);
tgBg.setCornerRadius(self.dp(8));
tgBg.setStroke(self.dp(1), self.withAlpha(subTextColor, 0.22));
btnToggle.setBackground(tgBg);
} catch (eTgBg) {}
btnToggle.setTextColor(__enabled ? self.withAlpha(subTextColor, 0.9) : self.withAlpha(C.success, 0.9));
try {
var lpTg = new android.widget.LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT
);
lpTg.leftMargin = self.dp(6);
btnToggle.setLayoutParams(lpTg);
} catch(eLpTg) {}
btnToggle.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
try {
btnCfg.enabled = (btnCfg.enabled === false) ? true : false;
// # 立即持久化,避免面板关闭后丢失
ConfigManager.saveButtons(buttons);
} catch(eTg) {}
refreshPanel();
}
}));
actions.addView(btnToggle);
// 删除按钮
var btnDel = new android.widget.TextView(context);
btnDel.setText("✕"); // 垃圾桶图标更好但这里用X
btnDel.setTextColor(self.withAlpha(C.danger, 0.7));
btnDel.setTextSize(16);
btnDel.setPadding(self.dp(10), self.dp(10), self.dp(4), self.dp(10));
btnDel.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
// 确认删除
buttons.splice(idx, 1);
refreshPanel();
}
}));
actions.addView(btnDel);
card.addView(actions);
list.addView(card);
})(i);
}
// 空状态提示
if (buttons.length === 0) {
var emptyBox = new android.widget.LinearLayout(context);
emptyBox.setOrientation(android.widget.LinearLayout.VERTICAL);
emptyBox.setGravity(android.view.Gravity.CENTER);
emptyBox.setPadding(0, self.dp(48), 0, self.dp(48));
var emptyIcon = new android.widget.ImageView(context);
emptyIcon.setImageResource(android.R.drawable.ic_menu_add);
emptyIcon.setColorFilter(subTextColor);
var eiLp = new android.widget.LinearLayout.LayoutParams(self.dp(48), self.dp(48));
emptyIcon.setLayoutParams(eiLp);
emptyBox.addView(emptyIcon);
var emptyTv = new android.widget.TextView(context);
emptyTv.setText("暂无按钮,点击右上角新增");
emptyTv.setTextColor(subTextColor);
emptyTv.setPadding(0, self.dp(16), 0, 0);
emptyBox.addView(emptyTv);
list.addView(emptyBox);
}
scroll.addView(list);
// # 列表高度优化:限制"按钮管理列表"高度,避免列表区域过高导致底部操作区被顶到很下面
// # 说明:原先使用 weight=1 会占满剩余空间,在不同机型上会显得"列表太高";这里改为按屏幕高度自适应,并限制上下界
var scrollLp;
try {
var dm2 = context.getResources().getDisplayMetrics();
var hPx2 = dm2 ? dm2.heightPixels : 0;
var targetPx2 = hPx2 > 0 ? Math.floor(hPx2 * 0.55) : self.dp(360);
var minPx2 = self.dp(220);
var maxPx2 = self.dp(520);
if (targetPx2 < minPx2) targetPx2 = minPx2;
if (targetPx2 > maxPx2) targetPx2 = maxPx2;
scrollLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, targetPx2);
} catch(eScrollH) {
scrollLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(360));
}
scroll.setLayoutParams(scrollLp);
panel.addView(scroll);
// # 列表滚动位置保持:刷新/返回列表后,恢复到上一次滚动位置(避免回到第一页)
try {
var _y = (self.state.btnEditorListScrollY !== undefined && self.state.btnEditorListScrollY !== null) ? Number(self.state.btnEditorListScrollY) : 0;
if (_y > 0) {
scroll.post(new java.lang.Runnable({
run: function() {
try { scroll.scrollTo(0, _y); } catch(eSY2) {}
}
}));
}
} catch(eSY3) {}
// 底部按钮栏
var bottomBar = new android.widget.LinearLayout(context);
bottomBar.setOrientation(android.widget.LinearLayout.HORIZONTAL);
bottomBar.setGravity(android.view.Gravity.END | android.view.Gravity.CENTER_VERTICAL);
bottomBar.setPadding(0, self.dp(12), 0, 0);
var btnListCancel = self.ui.createFlatButton(self, "取消更改", subTextColor, function() {
self.state.tempButtons = null;
self.toast("已取消更改");
self.hideAllPanels();
});
bottomBar.addView(btnListCancel);
// 间隔
var space = new android.view.View(context);
space.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(12), 1));
bottomBar.addView(space);
var btnListSave = self.ui.createSolidButton(self, "保存所有", C.primary, android.graphics.Color.WHITE, function() {
try {
ConfigManager.saveButtons(buttons);
self.panels.main = buttons;
self.state.tempButtons = null;
self.toast("保存成功");
refreshPanel();
} catch(e) { self.toast("保存失败:" + e); }
});
bottomBar.addView(btnListSave);
panel.addView(bottomBar);
} else {
// --- 编辑模式 ---
// panel.addView(header); // Removed empty header
panel.setTag(header); // 暴露 Header
var editIdx = this.state.editingButtonIndex;
var targetBtn = (editIdx === -1) ? { type: "shell", title: "", cmd: "", iconResId: 0 } : JSON.parse(JSON.stringify(buttons[editIdx]));
var scroll = new android.widget.ScrollView(context);
try { scroll.setVerticalScrollBarEnabled(false); } catch(e){}
var form = new android.widget.LinearLayout(context);
form.setOrientation(android.widget.LinearLayout.VERTICAL);
form.setPadding(self.dp(4), self.dp(4), self.dp(4), self.dp(4));
// 操作提示
var editHint = new android.widget.TextView(context);
editHint.setText("提示:选择动作类型并填写相应参数,完成后点击底部暂存");
editHint.setTextColor(subTextColor);
editHint.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
editHint.setPadding(self.dp(4), 0, 0, self.dp(16));
form.addView(editHint);
// 1. 标题 (Title)
var topArea = new android.widget.LinearLayout(context);
topArea.setOrientation(android.widget.LinearLayout.HORIZONTAL);
topArea.setGravity(android.view.Gravity.TOP);
// 标题输入
var titleArea = new android.widget.LinearLayout(context);
titleArea.setOrientation(android.widget.LinearLayout.VERTICAL);
var titleLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
titleLp.weight = 1;
// titleLp.rightMargin = self.dp(16); // No need for margin if no icon area
titleArea.setLayoutParams(titleLp);
var inputTitle = self.ui.createInputGroup(self, "标题 (Title)", targetBtn.title, false, "按钮上显示的文字");
titleArea.addView(inputTitle.view);
topArea.addView(titleArea);
form.addView(topArea);
// 1.5 图标选择(文件路径 或 ShortX 内置图标 二选一)
var iconSelectWrap = new android.widget.LinearLayout(context);
iconSelectWrap.setOrientation(android.widget.LinearLayout.VERTICAL);
iconSelectWrap.setPadding(0, self.dp(8), 0, self.dp(8));
var iconSelectLabel = new android.widget.TextView(context);
iconSelectLabel.setText("图标来源");
iconSelectLabel.setTextColor(subTextColor);
iconSelectLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
iconSelectWrap.addView(iconSelectLabel);
var iconRadioGroup = new android.widget.RadioGroup(context);
iconRadioGroup.setOrientation(android.widget.LinearLayout.HORIZONTAL);
iconRadioGroup.setPadding(0, self.dp(4), 0, self.dp(8));
var rbIconFile = new android.widget.RadioButton(context);
rbIconFile.setText("文件路径");
rbIconFile.setTextColor(textColor);
rbIconFile.setTag("file");
var rbIconShortX = new android.widget.RadioButton(context);
rbIconShortX.setText("ShortX图标");
rbIconShortX.setTextColor(textColor);
rbIconShortX.setTag("shortx");
iconRadioGroup.addView(rbIconFile);
iconRadioGroup.addView(rbIconShortX);
iconSelectWrap.addView(iconRadioGroup);
form.addView(iconSelectWrap);
// 1.5a 图标路径(文件方式)
var inputIconPath = self.ui.createInputGroup(self, "图标路径 (绝对地址)", targetBtn.iconPath, false, "支持 PNG/JPG 绝对路径,自动安全加载");
form.addView(inputIconPath.view);
// 1.5b ShortX 内置图标选择(取消手动名称输入,改为预览 + 图标库选择)
var normalizedInitShortX = self.normalizeShortXIconName(targetBtn.iconResName, false);
var currentShortXIconName = normalizedInitShortX ? String(normalizedInitShortX) : "";
var shortxPickerState = {
expanded: false,
lastQuery: "",
iconList: null,
previewIv: null,
previewNameTv: null,
statusTv: null,
searchEt: null,
grid: null,
gridScroll: null,
pickerWrap: null,
toggleBtn: null,
clearBtn: null,
pageSize: 0,
currentPage: 0,
activeTab: "all",
tabButtons: {},
pageInfoTv: null,
prevBtn: null,
nextBtn: null,
pageCols: 0,
pageRows: 0,
cellMinWidthDp: 72,
cellWidthPx: 0,
cellHeightDp: 92,
cellMarginDp: 4,
lastMeasuredGridWidth: 0,
lastMeasuredGridHeight: 0
};
var tintPalettePanelKey = "button_editor_icon_tint_palette";
var tintSavedState = null;
try { tintSavedState = self.loadPanelState(tintPalettePanelKey); } catch(eTintState0) { tintSavedState = null; }
var tintPaletteState = {
expanded: false,
recentColors: [],
syncing: false,
currentBaseRgbHex: "",
wrap: null,
body: null,
toggleBtn: null,
previewDot: null,
previewTextTv: null,
alphaSeek: null,
alphaValueTv: null,
redSeek: null,
greenSeek: null,
blueSeek: null,
redValueTv: null,
greenValueTv: null,
blueValueTv: null,
recentGrid: null,
recentEmptyTv: null,
commonGrid: null,
commonCols: 0,
commonCellWidthPx: 0,
commonMinCellWidthDp: 72,
commonLastMeasuredWidth: 0
};
function getShortXPickerClosedLabel() {
return "展开图标库";
}
function getShortXPickerOpenedLabel() {
return "收起图标库";
}
function getTintPaletteClosedLabel() {
return "展开调色板";
}
function getTintPaletteOpenedLabel() {
return "收起调色板";
}
function normalizeTintColorValue(v, allowEmpty) {
var s = (v === undefined || v === null) ? "" : String(v);
s = s.replace(/^\s+|\s+$/g, "").toUpperCase();
if (!s) return allowEmpty ? "" : null;
if (s.charAt(0) !== "#") s = "#" + s;
if (/^#[0-9A-F]{6}$/.test(s)) return "#FF" + s.substring(1);
if (/^#[0-9A-F]{8}$/.test(s)) return s;
return null;
}
function extractTintAlphaByte(hex) {
var n = normalizeTintColorValue(hex, false);
if (!n) return 255;
return parseInt(n.substring(1, 3), 16);
}
function extractTintRgbHex(hex) {
var n = normalizeTintColorValue(hex, false);
if (!n) return "#FFFFFF";
return "#" + n.substring(3);
}
function buildArgbHex(alphaByte, rgbHex) {
var a = Number(alphaByte);
if (isNaN(a) || a < 0) a = 0;
if (a > 255) a = 255;
var rgb = String(rgbHex || "#FFFFFF").toUpperCase();
if (rgb.charAt(0) !== "#") rgb = "#" + rgb;
if (!/^#[0-9A-F]{6}$/.test(rgb)) rgb = "#FFFFFF";
var aHex = java.lang.Integer.toHexString(a & 255).toUpperCase();
if (aHex.length < 2) aHex = "0" + aHex;
return "#" + aHex + rgb.substring(1);
}
function getThemeTintHex() {
try {
return _th_hex(self.getMonetAccentForBall()).replace("0x", "#").toUpperCase();
} catch(eThemeHex0) {
return "#FF6366F1";
}
}
function saveTintPaletteState() {
try {
self.savePanelState(tintPalettePanelKey, {
expanded: !!tintPaletteState.expanded,
recentColors: tintPaletteState.recentColors || []
});
} catch(eTintSave0) {}
}
function pushRecentTintColor(hex) {
var normalized = normalizeTintColorValue(hex, false);
if (!normalized) return;
var next = [normalized];
var oldList = tintPaletteState.recentColors || [];
var i;
for (i = 0; i < oldList.length; i++) {
var item = normalizeTintColorValue(oldList[i], false);
if (!item || item === normalized) continue;
next.push(item);
if (next.length >= 5) break;
}
tintPaletteState.recentColors = next;
renderRecentTintGrid();
saveTintPaletteState();
}
function scrollShortXGridToTop() {
try {
if (shortxPickerState.gridScroll) {
shortxPickerState.gridScroll.post(new java.lang.Runnable({
run: function() {
try { shortxPickerState.gridScroll.fullScroll(android.view.View.FOCUS_UP); } catch(eScroll0) {}
try { shortxPickerState.gridScroll.scrollTo(0, 0); } catch(eScroll1) {}
}
}));
}
} catch(eScrollWrap) {}
}
function resolveShortXPickerPageSize() {
var fallbackWidth = self.dp(320);
var fallbackHeight = self.dp(520);
var rawWidth = 0;
var rawHeight = 0;
try { if (shortxPickerState.gridScroll) rawWidth = Number(shortxPickerState.gridScroll.getWidth() || 0); } catch(eW0) {}
try { if (shortxPickerState.gridScroll) rawHeight = Number(shortxPickerState.gridScroll.getHeight() || 0); } catch(eH0) {}
if (rawWidth <= 0) rawWidth = fallbackWidth;
if (rawHeight <= 0) rawHeight = fallbackHeight;
var marginPx = self.dp(Number(shortxPickerState.cellMarginDp || 4));
var minCellWidthPx = self.dp(Number(shortxPickerState.cellMinWidthDp || 72));
var cellOuterMinWidth = minCellWidthPx + marginPx * 2;
if (cellOuterMinWidth <= 0) cellOuterMinWidth = self.dp(80);
var innerWidth = rawWidth - self.dp(8);
if (innerWidth <= 0) innerWidth = rawWidth;
var cols = Math.max(1, Math.floor(innerWidth / cellOuterMinWidth));
var cellWidthPx = Math.floor(innerWidth / cols) - marginPx * 2;
if (cellWidthPx < self.dp(56)) cellWidthPx = self.dp(56);
var cellOuterHeight = self.dp(Number(shortxPickerState.cellHeightDp || 92)) + marginPx * 2;
if (cellOuterHeight <= 0) cellOuterHeight = self.dp(100);
var rows = Math.max(1, Math.floor(rawHeight / cellOuterHeight));
var size = Math.max(cols, rows * cols);
shortxPickerState.pageCols = cols;
shortxPickerState.pageRows = rows;
shortxPickerState.cellWidthPx = cellWidthPx;
shortxPickerState.lastMeasuredGridWidth = rawWidth;
shortxPickerState.lastMeasuredGridHeight = rawHeight;
shortxPickerState.pageSize = size;
return size;
}
function setShortXPickerExpanded(expanded, doRender) {
shortxPickerState.expanded = !!expanded;
if (shortxPickerState.pickerWrap) {
shortxPickerState.pickerWrap.setVisibility(shortxPickerState.expanded ? android.view.View.VISIBLE : android.view.View.GONE);
}
try {
if (shortxPickerState.toggleBtn) shortxPickerState.toggleBtn.setText(shortxPickerState.expanded ? getShortXPickerOpenedLabel() : getShortXPickerClosedLabel());
} catch(eToggleTxt) {}
if (shortxPickerState.expanded && doRender !== false) {
resolveShortXPickerPageSize();
renderShortXIconGrid();
}
}
var shortxQuickRow = new android.widget.LinearLayout(context);
shortxQuickRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
shortxQuickRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
shortxQuickRow.setPadding(0, 0, 0, self.dp(8));
form.addView(shortxQuickRow);
var shortxPreviewCard = new android.widget.LinearLayout(context);
shortxPreviewCard.setOrientation(android.widget.LinearLayout.HORIZONTAL);
shortxPreviewCard.setGravity(android.view.Gravity.CENTER_VERTICAL);
shortxPreviewCard.setPadding(self.dp(10), self.dp(8), self.dp(10), self.dp(8));
shortxPreviewCard.setBackground(self.ui.createRoundDrawable(self.withAlpha(C.primary, 0.08), self.dp(12)));
var shortxPreviewLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
shortxPreviewLp.weight = 1;
shortxQuickRow.addView(shortxPreviewCard, shortxPreviewLp);
var shortxPreviewIv = new android.widget.ImageView(context);
var shortxPreviewIvLp = new android.widget.LinearLayout.LayoutParams(self.dp(20), self.dp(20));
shortxPreviewIvLp.rightMargin = self.dp(8);
shortxPreviewIv.setLayoutParams(shortxPreviewIvLp);
shortxPreviewCard.addView(shortxPreviewIv);
shortxPickerState.previewIv = shortxPreviewIv;
var shortxPreviewNameTv = new android.widget.TextView(context);
shortxPreviewNameTv.setTextColor(textColor);
shortxPreviewNameTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
try { shortxPreviewNameTv.setSingleLine(true); shortxPreviewNameTv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(eEL0) {}
shortxPreviewCard.addView(shortxPreviewNameTv, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
shortxPickerState.previewNameTv = shortxPreviewNameTv;
var shortxBtnGap = new android.view.View(context);
shortxBtnGap.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(8), 1));
shortxQuickRow.addView(shortxBtnGap);
var btnBrowseShortXIcon = self.ui.createFlatButton(self, "选择图标", C.primary, function() {
self.touchActivity();
self.showShortXIconPickerPopup({
currentName: currentShortXIconName,
currentTint: (inputShortXIconTint && inputShortXIconTint.input) ? String(inputShortXIconTint.input.getText() || "") : "",
onSelect: function(name) {
currentShortXIconName = name;
if (inputShortXIconName && inputShortXIconName.input) inputShortXIconName.input.setText(name);
updateShortXIconPreview();
try { if (shortxPickerState.toggleBtn) shortxPickerState.toggleBtn.setText(name || "选择图标"); } catch(e) {}
}
});
});
shortxPickerState.toggleBtn = btnBrowseShortXIcon;
shortxQuickRow.addView(btnBrowseShortXIcon);
var shortxBtnGap2 = new android.view.View(context);
shortxBtnGap2.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(8), 1));
shortxQuickRow.addView(shortxBtnGap2);
var btnClearShortXIcon = self.ui.createFlatButton(self, "清空", subTextColor, function() {
self.touchActivity();
currentShortXIconName = "";
updateShortXIconPreview();
});
shortxPickerState.clearBtn = btnClearShortXIcon;
shortxQuickRow.addView(btnClearShortXIcon);
var shortxPickerWrap = new android.widget.LinearLayout(context);
shortxPickerWrap.setOrientation(android.widget.LinearLayout.VERTICAL);
shortxPickerWrap.setPadding(0, 0, 0, self.dp(8));
shortxPickerWrap.setVisibility(android.view.View.GONE);
shortxPickerWrap.setBackground(self.ui.createRoundDrawable(self.withAlpha(cardColor, 0.92), self.dp(14)));
// [Popup] ShortX icon picker moved to showShortXIconPickerPopup() — no longer embedded
// form.addView(shortxPickerWrap);
shortxPickerState.pickerWrap = shortxPickerWrap;
var shortxPickerHead = new android.widget.TextView(context);
shortxPickerHead.setText("ShortX 图标库(分页模式,按宽度自动排列,支持搜索 / 分类 / 点击即选中)");
shortxPickerHead.setTextColor(subTextColor);
shortxPickerHead.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
shortxPickerHead.setPadding(self.dp(12), self.dp(10), self.dp(12), self.dp(6));
shortxPickerWrap.addView(shortxPickerHead);
var shortxSearchEt = new android.widget.EditText(context);
shortxSearchEt.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
shortxSearchEt.setTextColor(textColor);
try { shortxSearchEt.setHintTextColor(subTextColor); } catch(eHintColor) {}
shortxSearchEt.setHint("搜索图标名,如 share / home / save");
shortxSearchEt.setSingleLine(true);
shortxSearchEt.setPadding(self.dp(10), self.dp(8), self.dp(10), self.dp(8));
shortxSearchEt.setBackground(self.ui.createStrokeDrawable(isDark ? self.ui.colors.inputBgDark : self.ui.colors.inputBgLight, isDark ? self.ui.colors.dividerDark : self.ui.colors.dividerLight, self.dp(1), self.dp(10)));
var shortxSearchLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
shortxSearchLp.setMargins(self.dp(12), 0, self.dp(12), self.dp(6));
shortxPickerWrap.addView(shortxSearchEt, shortxSearchLp);
shortxPickerState.searchEt = shortxSearchEt;
var shortxStatusTv = new android.widget.TextView(context);
shortxStatusTv.setTextColor(subTextColor);
shortxStatusTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
shortxStatusTv.setPadding(self.dp(12), 0, self.dp(12), self.dp(6));
shortxPickerWrap.addView(shortxStatusTv);
shortxPickerState.statusTv = shortxStatusTv;
var shortxTabsScroll = new android.widget.HorizontalScrollView(context);
try { shortxTabsScroll.setHorizontalScrollBarEnabled(false); } catch(eTabSb) {}
try { shortxTabsScroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch(eTabOs) {}
var shortxTabsLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
shortxTabsLp.setMargins(self.dp(8), 0, self.dp(8), self.dp(6));
shortxPickerWrap.addView(shortxTabsScroll, shortxTabsLp);
var shortxTabsRow = new android.widget.LinearLayout(context);
shortxTabsRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
shortxTabsRow.setPadding(self.dp(4), 0, self.dp(4), 0);
shortxTabsScroll.addView(shortxTabsRow);
var shortxPageBar = new android.widget.LinearLayout(context);
shortxPageBar.setOrientation(android.widget.LinearLayout.HORIZONTAL);
shortxPageBar.setGravity(android.view.Gravity.CENTER_VERTICAL);
shortxPageBar.setPadding(self.dp(8), 0, self.dp(8), self.dp(6));
shortxPickerWrap.addView(shortxPageBar);
function getShortXIconCategory(item) {
var name = String((item && item.shortName) ? item.shortName : "").toLowerCase();
if (!name) return "other";
if (name.indexOf("arrow") >= 0 || name.indexOf("left") >= 0 || name.indexOf("right") >= 0 || name.indexOf("up") >= 0 || name.indexOf("down") >= 0) return "direction";
if (name.indexOf("play") >= 0 || name.indexOf("pause") >= 0 || name.indexOf("music") >= 0 || name.indexOf("video") >= 0 || name.indexOf("sound") >= 0 || name.indexOf("volume") >= 0 || name.indexOf("camera") >= 0 || name.indexOf("image") >= 0) return "media";
if (name.indexOf("home") >= 0 || name.indexOf("setting") >= 0 || name.indexOf("search") >= 0 || name.indexOf("user") >= 0 || name.indexOf("app") >= 0 || name.indexOf("menu") >= 0 || name.indexOf("notification") >= 0) return "system";
if (name.indexOf("edit") >= 0 || name.indexOf("delete") >= 0 || name.indexOf("add") >= 0 || name.indexOf("save") >= 0 || name.indexOf("copy") >= 0 || name.indexOf("share") >= 0 || name.indexOf("download") >= 0 || name.indexOf("upload") >= 0) return "action";
if (name.indexOf("wechat") >= 0 || name.indexOf("qq") >= 0 || name.indexOf("weibo") >= 0 || name.indexOf("douyin") >= 0 || name.indexOf("tiktok") >= 0 || name.indexOf("alipay") >= 0 || name.indexOf("github") >= 0 || name.indexOf("telegram") >= 0 || name.indexOf("youtube") >= 0 || name.indexOf("google") >= 0) return "brand";
return "other";
}
var shortxTabDefs = [
{ key: "all", label: "全部" },
{ key: "system", label: "系统" },
{ key: "action", label: "操作" },
{ key: "direction", label: "方向" },
{ key: "media", label: "媒体" },
{ key: "brand", label: "品牌" },
{ key: "other", label: "其他" }
];
function applyShortXTabStyles() {
var i;
for (i = 0; i < shortxTabDefs.length; i++) {
var def = shortxTabDefs[i];
var btn = shortxPickerState.tabButtons[def.key];
if (!btn) continue;
var active = shortxPickerState.activeTab === def.key;
try {
btn.setTextColor(active ? android.graphics.Color.WHITE : textColor);
btn.setBackground(self.ui.createRoundDrawable(active ? C.primary : self.withAlpha(cardColor, 0.96), self.dp(16)));
} catch(eTabStyle) {}
}
}
function goShortXPage(delta) {
shortxPickerState.currentPage = Math.max(0, Number(shortxPickerState.currentPage || 0) + Number(delta || 0));
scrollShortXGridToTop();
renderShortXIconGrid();
}
var btnPrevPage = self.ui.createFlatButton(self, "上一页", subTextColor, function() {
self.touchActivity();
goShortXPage(-1);
});
shortxPageBar.addView(btnPrevPage);
shortxPickerState.prevBtn = btnPrevPage;
var shortxPageInfo = new android.widget.TextView(context);
shortxPageInfo.setTextColor(textColor);
shortxPageInfo.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
shortxPageInfo.setGravity(android.view.Gravity.CENTER);
shortxPageInfo.setPadding(self.dp(12), 0, self.dp(12), 0);
shortxPageBar.addView(shortxPageInfo, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
shortxPickerState.pageInfoTv = shortxPageInfo;
var btnNextPage = self.ui.createFlatButton(self, "下一页", C.primary, function() {
self.touchActivity();
goShortXPage(1);
});
shortxPageBar.addView(btnNextPage);
shortxPickerState.nextBtn = btnNextPage;
var iTab;
for (iTab = 0; iTab < shortxTabDefs.length; iTab++) {
(function(def) {
var tabBtn = self.ui.createFlatButton(self, def.label, textColor, function() {
self.touchActivity();
shortxPickerState.activeTab = def.key;
shortxPickerState.currentPage = 0;
applyShortXTabStyles();
scrollShortXGridToTop();
renderShortXIconGrid();
});
tabBtn.setPadding(self.dp(10), self.dp(4), self.dp(10), self.dp(4));
var lpTab = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
lpTab.rightMargin = self.dp(6);
shortxTabsRow.addView(tabBtn, lpTab);
shortxPickerState.tabButtons[def.key] = tabBtn;
})(shortxTabDefs[iTab]);
}
applyShortXTabStyles();
var shortxGridScroll = new android.widget.ScrollView(context);
try { shortxGridScroll.setVerticalScrollBarEnabled(false); } catch(eSG0) {}
try { shortxGridScroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch(eSG1) {}
var shortxGridScrollLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, self.dp(520));
shortxGridScrollLp.setMargins(self.dp(8), 0, self.dp(8), self.dp(8));
shortxPickerWrap.addView(shortxGridScroll, shortxGridScrollLp);
shortxPickerState.gridScroll = shortxGridScroll;
var shortxGrid = new android.widget.GridLayout(context);
try { shortxGrid.setColumnCount(Math.max(1, Number(shortxPickerState.pageCols || 1))); } catch(eGC0) {}
shortxGrid.setPadding(self.dp(4), self.dp(4), self.dp(4), self.dp(4));
shortxGridScroll.addView(shortxGrid);
shortxPickerState.grid = shortxGrid;
function updateShortXIconPreview() {
try {
var normalizedShort = currentShortXIconName ? self.normalizeShortXIconName(currentShortXIconName, false) : "";
var tintHex = String(inputShortXIconTint.getValue() || "");
if (shortxPickerState.previewNameTv) {
shortxPickerState.previewNameTv.setText(normalizedShort ? ("已选: " + normalizedShort) : "未选择图标");
}
if (shortxPickerState.previewIv) {
var drPreview = normalizedShort ? self.resolveShortXDrawable(normalizedShort, tintHex) : null;
if (drPreview) {
shortxPickerState.previewIv.setImageDrawable(drPreview);
try { shortxPickerState.previewIv.setAlpha(1.0); } catch(eA1) {}
} else {
shortxPickerState.previewIv.setImageDrawable(null);
try { shortxPickerState.previewIv.setAlpha(0.35); } catch(eA2) {}
}
}
} catch(ePreview) {}
}
function renderShortXIconGrid() {
try {
if (!shortxPickerState.grid) return;
shortxPickerState.grid.removeAllViews();
try { shortxPickerState.grid.setColumnCount(Math.max(1, Number(shortxPickerState.pageCols || 1))); } catch(eColSet) {}
var icons = self.getShortXIconCatalog();
shortxPickerState.iconList = icons;
var query = "";
try { query = String(shortxPickerState.searchEt.getText() || "").replace(/^\s+|\s+$/g, "").toLowerCase(); } catch(eQ0) {}
shortxPickerState.lastQuery = query;
var filtered = [];
var totalMatch = 0;
var i;
for (i = 0; i < icons.length; i++) {
var item = icons[i];
var tabOk = (shortxPickerState.activeTab === "all") || (getShortXIconCategory(item) === shortxPickerState.activeTab);
if (!tabOk) continue;
var n1 = String(item.shortName || "").toLowerCase();
var n2 = String(item.name || "").toLowerCase();
if (!query || n1.indexOf(query) >= 0 || n2.indexOf(query) >= 0) {
totalMatch++;
filtered.push(item);
}
}
var pageSize = resolveShortXPickerPageSize();
if (pageSize < 1) pageSize = 20;
var totalPages = filtered.length > 0 ? Math.ceil(filtered.length / pageSize) : 1;
if (shortxPickerState.currentPage >= totalPages) shortxPickerState.currentPage = totalPages - 1;
if (shortxPickerState.currentPage < 0) shortxPickerState.currentPage = 0;
var start = shortxPickerState.currentPage * pageSize;
var end = Math.min(start + pageSize, filtered.length);
var result = filtered.slice(start, end);
if (shortxPickerState.statusTv) {
if (!icons || icons.length === 0) {
var errMsg = self._shortxIconCatalogError ? String(self._shortxIconCatalogError) : "未知原因";
shortxPickerState.statusTv.setText("ShortX 图标反射失败/为空:" + errMsg);
} else if (!query) {
shortxPickerState.statusTv.setText("分类[" + shortxPickerState.activeTab + "] 共 " + filtered.length + " 个,按宽度自动排成 " + shortxPickerState.pageCols + " 列,每页 " + pageSize + " 个(" + shortxPickerState.pageRows + " 行),当前第 " + (shortxPickerState.currentPage + 1) + "/" + totalPages + " 页。");
} else {
shortxPickerState.statusTv.setText("分类[" + shortxPickerState.activeTab + "] 搜索 [" + query + "] 命中 " + totalMatch + " 个,当前按宽度自动排成 " + shortxPickerState.pageCols + " 列,每页 " + pageSize + " 个,当前第 " + (shortxPickerState.currentPage + 1) + "/" + totalPages + " 页。");
}
}
if (shortxPickerState.pageInfoTv) {
shortxPickerState.pageInfoTv.setText((filtered.length > 0 ? (shortxPickerState.currentPage + 1) : 0) + " / " + totalPages + " · " + filtered.length + "项 · " + shortxPickerState.pageCols + "列 · 每页" + pageSize + "个");
}
try { shortxPickerState.prevBtn.setEnabled(shortxPickerState.currentPage > 0); } catch(ePrev) {}
try { shortxPickerState.nextBtn.setEnabled(shortxPickerState.currentPage < totalPages - 1); } catch(eNext) {}
applyShortXTabStyles();
var tintHex = String(inputShortXIconTint.getValue() || "");
var selectedShort = currentShortXIconName ? self.normalizeShortXIconName(currentShortXIconName, false) : "";
for (i = 0; i < result.length; i++) {
(function(entry) {
var cell = new android.widget.LinearLayout(context);
cell.setOrientation(android.widget.LinearLayout.VERTICAL);
cell.setGravity(android.view.Gravity.CENTER_HORIZONTAL);
cell.setPadding(self.dp(8), self.dp(8), self.dp(8), self.dp(8));
var lp = new android.widget.GridLayout.LayoutParams();
lp.width = Number(shortxPickerState.cellWidthPx || self.dp(Number(shortxPickerState.cellMinWidthDp || 72)));
lp.height = self.dp(Number(shortxPickerState.cellHeightDp || 92));
lp.setMargins(self.dp(Number(shortxPickerState.cellMarginDp || 4)), self.dp(Number(shortxPickerState.cellMarginDp || 4)), self.dp(Number(shortxPickerState.cellMarginDp || 4)), self.dp(Number(shortxPickerState.cellMarginDp || 4)));
cell.setLayoutParams(lp);
var isSelected = selectedShort && selectedShort === String(entry.shortName);
cell.setBackground(self.ui.createRoundDrawable(self.withAlpha(isSelected ? C.primary : cardColor, isSelected ? 0.18 : 0.96), self.dp(12)));
var iv = new android.widget.ImageView(context);
var ivLp = new android.widget.LinearLayout.LayoutParams(self.dp(24), self.dp(24));
ivLp.bottomMargin = self.dp(6);
iv.setLayoutParams(ivLp);
try {
var drIcon = self.resolveShortXDrawable(entry.name, tintHex);
if (drIcon) iv.setImageDrawable(drIcon);
} catch(eIconDraw) {}
cell.addView(iv);
var tv = new android.widget.TextView(context);
tv.setText(String(entry.shortName));
tv.setTextColor(isSelected ? C.primary : textColor);
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 10);
tv.setGravity(android.view.Gravity.CENTER);
try { tv.setLines(2); tv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(eLines0) {}
cell.addView(tv, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT));
cell.setClickable(true);
cell.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
currentShortXIconName = String(entry.shortName || "");
updateShortXIconPreview();
scrollShortXGridToTop();
setShortXPickerExpanded(false, false);
}
}));
shortxPickerState.grid.addView(cell);
})(result[i]);
}
} catch(eRenderIcons) {
try { if (shortxPickerState.statusTv) shortxPickerState.statusTv.setText("图标库加载失败: " + eRenderIcons); } catch(eStatus0) {}
}
}
try {
shortxSearchEt.addTextChangedListener(new JavaAdapter(android.text.TextWatcher, {
afterTextChanged: function(s) {
shortxPickerState.currentPage = 0;
scrollShortXGridToTop();
renderShortXIconGrid();
},
beforeTextChanged: function(s, st, c, a) {},
onTextChanged: function(s, st, b, c) {}
}));
} catch(eTwIcon0) {}
try {
shortxGridScroll.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener({
onGlobalLayout: function() {
if (!shortxPickerState.expanded) return;
var oldSize = Number(shortxPickerState.pageSize || 0);
var oldCols = Number(shortxPickerState.pageCols || 0);
var oldWidth = Number(shortxPickerState.lastMeasuredGridWidth || 0);
var newSize = resolveShortXPickerPageSize();
if (newSize > 0 && (newSize !== oldSize || oldCols !== Number(shortxPickerState.pageCols || 0) || oldWidth !== Number(shortxPickerState.lastMeasuredGridWidth || 0))) {
shortxPickerState.currentPage = 0;
renderShortXIconGrid();
}
}
}));
} catch(eGridLayoutWatch) {}
// # ShortX 图标颜色(留空跟随主题)
var defaultTint = targetBtn.iconTint ? String(targetBtn.iconTint) : "";
var inputShortXIconTint = self.ui.createInputGroup(self, "图标颜色 (留空跟随主题)", defaultTint, false, "支持 #RRGGBB / #AARRGGBB下方可展开完整调色板");
form.addView(inputShortXIconTint.view);
function updateTintPaletteToggleText() {
try {
if (tintPaletteState.toggleBtn) tintPaletteState.toggleBtn.setText(tintPaletteState.expanded ? getTintPaletteOpenedLabel() : getTintPaletteClosedLabel());
} catch(eTintToggle0) {}
}
function setTintPaletteExpanded(expanded) {
tintPaletteState.expanded = !!expanded;
try {
if (tintPaletteState.body) tintPaletteState.body.setVisibility(tintPaletteState.expanded ? android.view.View.VISIBLE : android.view.View.GONE);
} catch(eTintBody0) {}
updateTintPaletteToggleText();
saveTintPaletteState();
}
function setTintSeekProgress(progress) {
try {
if (tintPaletteState.alphaSeek) tintPaletteState.alphaSeek.setProgress(Number(progress || 0));
} catch(eTintSeek0) {}
}
function setRgbSeekProgress(which, value) {
var v = Number(value || 0);
if (isNaN(v) || v < 0) v = 0;
if (v > 255) v = 255;
try {
if (which === "r" && tintPaletteState.redSeek) tintPaletteState.redSeek.setProgress(v);
if (which === "g" && tintPaletteState.greenSeek) tintPaletteState.greenSeek.setProgress(v);
if (which === "b" && tintPaletteState.blueSeek) tintPaletteState.blueSeek.setProgress(v);
} catch(eTintRgbSeek0) {}
}
function updateTintAlphaLabel(alphaByte) {
var a = Number(alphaByte || 0);
if (isNaN(a) || a < 0) a = 0;
if (a > 255) a = 255;
var pct = Math.round((a / 255) * 100);
try {
if (tintPaletteState.alphaValueTv) tintPaletteState.alphaValueTv.setText("透明度 " + pct + "%" + a + "/255");
} catch(eTintAlpha0) {}
}
function updateRgbValueLabel(which, value) {
var v = Number(value || 0);
if (isNaN(v) || v < 0) v = 0;
if (v > 255) v = 255;
try {
if (which === "r" && tintPaletteState.redValueTv) tintPaletteState.redValueTv.setText("R " + v);
if (which === "g" && tintPaletteState.greenValueTv) tintPaletteState.greenValueTv.setText("G " + v);
if (which === "b" && tintPaletteState.blueValueTv) tintPaletteState.blueValueTv.setText("B " + v);
} catch(eTintRgbLbl0) {}
}
function updateRgbLabelsFromHex(rgbHex) {
var rgb = String(rgbHex || "#FFFFFF").toUpperCase();
if (rgb.charAt(0) !== "#") rgb = "#" + rgb;
if (!/^#[0-9A-F]{6}$/.test(rgb)) rgb = "#FFFFFF";
updateRgbValueLabel("r", parseInt(rgb.substring(1, 3), 16));
updateRgbValueLabel("g", parseInt(rgb.substring(3, 5), 16));
updateRgbValueLabel("b", parseInt(rgb.substring(5, 7), 16));
}
function updateTintPalettePreviewText(normalizedHex, isThemeFollow, invalidRaw) {
var effectiveHex = normalizedHex || getThemeTintHex();
var colorInt = 0;
try { colorInt = android.graphics.Color.parseColor(effectiveHex); } catch(eColor0) { colorInt = C.primary; }
try {
if (tintPaletteState.previewDot) tintPaletteState.previewDot.setBackground(self.ui.createRoundDrawable(colorInt, self.dp(12)));
} catch(eTintPrevDot0) {}
var msg = "";
if (invalidRaw) {
msg = "当前:" + invalidRaw + "(格式无效)";
} else if (isThemeFollow) {
msg = "当前:跟随主题(" + effectiveHex + "";
} else {
var pct = Math.round((extractTintAlphaByte(effectiveHex) / 255) * 100);
msg = "当前:" + effectiveHex + " · 透明度 " + pct + "%";
}
try {
if (tintPaletteState.previewTextTv) tintPaletteState.previewTextTv.setText(msg);
} catch(eTintPrevTv0) {}
}
function syncTintUiFromInput(pushRecent) {
var raw = "";
try { raw = String(inputShortXIconTint.getValue() || "").replace(/^\s+|\s+$/g, ""); } catch(eTintRaw0) {}
var normalized = normalizeTintColorValue(raw, true);
if (raw && !normalized) {
updateTintAlphaLabel(0);
updateTintPalettePreviewText("", false, raw);
updateShortXIconPreview();
if (shortxPickerState.expanded) renderShortXIconGrid();
return;
}
var effectiveHex = normalized || getThemeTintHex();
var alphaByte = extractTintAlphaByte(effectiveHex);
tintPaletteState.currentBaseRgbHex = extractTintRgbHex(effectiveHex);
tintPaletteState.syncing = true;
setTintSeekProgress(alphaByte);
setRgbSeekProgress("r", parseInt(tintPaletteState.currentBaseRgbHex.substring(1, 3), 16));
setRgbSeekProgress("g", parseInt(tintPaletteState.currentBaseRgbHex.substring(3, 5), 16));
setRgbSeekProgress("b", parseInt(tintPaletteState.currentBaseRgbHex.substring(5, 7), 16));
tintPaletteState.syncing = false;
updateTintAlphaLabel(alphaByte);
updateRgbLabelsFromHex(tintPaletteState.currentBaseRgbHex);
updateTintPalettePreviewText(normalized, !normalized, null);
updateShortXIconPreview();
if (shortxPickerState.expanded) renderShortXIconGrid();
if (pushRecent && normalized) pushRecentTintColor(normalized);
}
function applyTintHexValue(hexValue, pushRecent) {
try {
tintPaletteState.syncing = true;
inputShortXIconTint.input.setText(String(hexValue || ""));
} catch(eSetTint0) {}
tintPaletteState.syncing = false;
syncTintUiFromInput(!!pushRecent);
}
function applyTintFromCurrentBase(pushRecent) {
var baseRgb = tintPaletteState.currentBaseRgbHex || extractTintRgbHex(getThemeTintHex());
var alphaByte = 255;
try { alphaByte = Number(tintPaletteState.alphaSeek.getProgress() || 0); } catch(eTintAlpha1) { alphaByte = 255; }
updateRgbLabelsFromHex(baseRgb);
applyTintHexValue(buildArgbHex(alphaByte, baseRgb), !!pushRecent);
}
function applyTintFromRgbSeekbars(pushRecent) {
var r = 255, g = 255, b = 255, alphaByte = 255;
try { r = Number(tintPaletteState.redSeek.getProgress() || 0); } catch(eTintR0) { r = 255; }
try { g = Number(tintPaletteState.greenSeek.getProgress() || 0); } catch(eTintG0) { g = 255; }
try { b = Number(tintPaletteState.blueSeek.getProgress() || 0); } catch(eTintB0) { b = 255; }
try { alphaByte = Number(tintPaletteState.alphaSeek.getProgress() || 0); } catch(eTintA0) { alphaByte = 255; }
var rgbHex = "#";
var parts = [r, g, b];
var i;
for (i = 0; i < parts.length; i++) {
var hx = java.lang.Integer.toHexString(parts[i] & 255).toUpperCase();
if (hx.length < 2) hx = "0" + hx;
rgbHex += hx;
}
tintPaletteState.currentBaseRgbHex = rgbHex;
updateRgbLabelsFromHex(rgbHex);
applyTintHexValue(buildArgbHex(alphaByte, rgbHex), !!pushRecent);
}
function createTintSwatchCell(label, hexValue, isFollowTheme, cellWidthPx) {
var wrap = new android.widget.LinearLayout(context);
wrap.setOrientation(android.widget.LinearLayout.VERTICAL);
wrap.setGravity(android.view.Gravity.CENTER_HORIZONTAL);
wrap.setPadding(self.dp(6), self.dp(6), self.dp(6), self.dp(6));
var lp = new android.widget.GridLayout.LayoutParams();
if (cellWidthPx && Number(cellWidthPx) > 0) lp.width = Number(cellWidthPx);
lp.setMargins(self.dp(4), self.dp(4), self.dp(4), self.dp(4));
wrap.setLayoutParams(lp);
try { wrap.setBackground(self.ui.createRoundDrawable(self.withAlpha(cardColor, 0.96), self.dp(10))); } catch(eTintSwBg0) {}
var effectiveHex = isFollowTheme ? getThemeTintHex() : normalizeTintColorValue(hexValue, false);
var dot = new android.view.View(context);
var dotLp = new android.widget.LinearLayout.LayoutParams(self.dp(24), self.dp(24));
dotLp.bottomMargin = self.dp(4);
try {
var dotColorInt = effectiveHex ? android.graphics.Color.parseColor(effectiveHex) : self.withAlpha(C.primary, 0.18);
dot.setBackground(self.ui.createRoundDrawable(dotColorInt, self.dp(12)));
} catch(eTintSwDot0) {}
wrap.addView(dot, dotLp);
var tv = new android.widget.TextView(context);
tv.setText(String(label));
tv.setTextColor(textColor);
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 10);
tv.setGravity(android.view.Gravity.CENTER);
wrap.addView(tv);
wrap.setClickable(true);
wrap.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
if (isFollowTheme) {
applyTintHexValue("", false);
} else {
var alphaByte = 255;
try { alphaByte = Number(tintPaletteState.alphaSeek.getProgress() || 0); } catch(eTintSwAlpha0) { alphaByte = 255; }
tintPaletteState.currentBaseRgbHex = extractTintRgbHex(hexValue);
applyTintHexValue(buildArgbHex(alphaByte, tintPaletteState.currentBaseRgbHex), true);
}
}
}));
return wrap;
}
function renderRecentTintGrid() {
if (!tintPaletteState.recentGrid) return;
try { tintPaletteState.recentGrid.removeAllViews(); } catch(eTintRecent0) {}
var list = tintPaletteState.recentColors || [];
var i;
if (!list.length) {
try { if (tintPaletteState.recentEmptyTv) tintPaletteState.recentEmptyTv.setVisibility(android.view.View.VISIBLE); } catch(eTintRecent1) {}
return;
}
try { if (tintPaletteState.recentEmptyTv) tintPaletteState.recentEmptyTv.setVisibility(android.view.View.GONE); } catch(eTintRecent2) {}
for (i = 0; i < list.length && i < 5; i++) {
tintPaletteState.recentGrid.addView(createTintSwatchCell("最近" + (i + 1), list[i], false, 0));
}
}
if (tintSavedState && tintSavedState.recentColors && tintSavedState.recentColors.length) {
var tintRecentIn = tintSavedState.recentColors;
var tintRi;
for (tintRi = 0; tintRi < tintRecentIn.length && tintPaletteState.recentColors.length < 5; tintRi++) {
var tintNorm = normalizeTintColorValue(tintRecentIn[tintRi], false);
if (tintNorm) tintPaletteState.recentColors.push(tintNorm);
}
}
var tintPaletteWrap = new android.widget.LinearLayout(context);
tintPaletteWrap.setOrientation(android.widget.LinearLayout.VERTICAL);
tintPaletteWrap.setPadding(0, 0, 0, self.dp(12));
tintPaletteWrap.setBackground(self.ui.createRoundDrawable(self.withAlpha(cardColor, 0.92), self.dp(14)));
// [Popup] tint palette moved to showColorPickerPopup() — no longer embedded
// form.addView(tintPaletteWrap);
tintPaletteState.pickerWrap = tintPaletteWrap;
var tintPaletteHead = new android.widget.LinearLayout(context);
tintPaletteHead.setOrientation(android.widget.LinearLayout.HORIZONTAL);
tintPaletteHead.setGravity(android.view.Gravity.CENTER_VERTICAL);
tintPaletteHead.setPadding(self.dp(12), self.dp(10), self.dp(12), self.dp(10));
tintPaletteWrap.addView(tintPaletteHead);
var tintHeadTitle = new android.widget.TextView(context);
tintHeadTitle.setText("完整调色板");
tintHeadTitle.setTextColor(textColor);
tintHeadTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
tintPaletteHead.addView(tintHeadTitle, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
var tintToggleBtn = self.ui.createFlatButton(self, "\u9009\u62e9\u989c\u8272", C.primary, function() {
self.touchActivity();
var currentTint = (inputShortXIconTint && inputShortXIconTint.input) ? String(inputShortXIconTint.input.getText() || "") : "";
self.showColorPickerPopup({
currentColor: currentTint,
currentIconName: currentShortXIconName,
onSelect: function(colorHex) {
if (inputShortXIconTint && inputShortXIconTint.input) {
inputShortXIconTint.input.setText(colorHex);
}
try { if (tintPaletteState.toggleBtn) tintPaletteState.toggleBtn.setText(colorHex || "\u9009\u62e9\u989c\u8272"); } catch(e) {}
}
});
});
tintPaletteHead.addView(tintToggleBtn);
tintPaletteState.toggleBtn = tintToggleBtn;
var tintPaletteBody = new android.widget.LinearLayout(context);
tintPaletteBody.setOrientation(android.widget.LinearLayout.VERTICAL);
tintPaletteBody.setPadding(self.dp(12), 0, self.dp(12), self.dp(12));
tintPaletteWrap.addView(tintPaletteBody);
tintPaletteState.body = tintPaletteBody;
tintPaletteBody.setVisibility(android.view.View.GONE);
var tintPreviewCard = new android.widget.LinearLayout(context);
tintPreviewCard.setOrientation(android.widget.LinearLayout.HORIZONTAL);
tintPreviewCard.setGravity(android.view.Gravity.CENTER_VERTICAL);
tintPreviewCard.setPadding(self.dp(10), self.dp(8), self.dp(10), self.dp(8));
try { tintPreviewCard.setBackground(self.ui.createRoundDrawable(self.withAlpha(C.primary, 0.08), self.dp(12))); } catch(eTintPrevCard0) {}
tintPaletteBody.addView(tintPreviewCard);
var tintPalettePreviewDot = new android.view.View(context);
var tintPalettePreviewDotLp = new android.widget.LinearLayout.LayoutParams(self.dp(24), self.dp(24));
tintPalettePreviewDotLp.rightMargin = self.dp(8);
tintPreviewCard.addView(tintPalettePreviewDot, tintPalettePreviewDotLp);
tintPaletteState.previewDot = tintPalettePreviewDot;
var tintPalettePreviewText = new android.widget.TextView(context);
tintPalettePreviewText.setTextColor(textColor);
tintPalettePreviewText.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tintPreviewCard.addView(tintPalettePreviewText, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
tintPaletteState.previewTextTv = tintPalettePreviewText;
var tintAlphaRow = new android.widget.LinearLayout(context);
tintAlphaRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
tintAlphaRow.setGravity(android.view.Gravity.CENTER_VERTICAL);
tintAlphaRow.setPadding(0, self.dp(10), 0, self.dp(6));
tintPaletteBody.addView(tintAlphaRow);
var tintAlphaTitle = new android.widget.TextView(context);
tintAlphaTitle.setText("透明度");
tintAlphaTitle.setTextColor(subTextColor);
tintAlphaTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tintAlphaRow.addView(tintAlphaTitle);
var tintAlphaValue = new android.widget.TextView(context);
tintAlphaValue.setTextColor(textColor);
tintAlphaValue.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tintAlphaValue.setPadding(self.dp(8), 0, 0, 0);
tintAlphaRow.addView(tintAlphaValue);
tintPaletteState.alphaValueTv = tintAlphaValue;
var tintSeek = new android.widget.SeekBar(context);
try { tintSeek.setMax(255); } catch(eTintSeekMax0) {}
var tintSeekLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
tintSeekLp.setMargins(0, 0, 0, self.dp(8));
tintPaletteBody.addView(tintSeek, tintSeekLp);
tintPaletteState.alphaSeek = tintSeek;
try {
tintSeek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({
onProgressChanged: function(seekBar, progress, fromUser) {
updateTintAlphaLabel(progress);
if (tintPaletteState.syncing) return;
if (fromUser) applyTintFromCurrentBase(false);
},
onStartTrackingTouch: function(seekBar) {},
onStopTrackingTouch: function(seekBar) {
if (tintPaletteState.syncing) return;
applyTintFromCurrentBase(true);
}
}));
} catch(eTintSeekListener0) {}
function createRgbControlRow(label, key, accentHex) {
var row = new android.widget.LinearLayout(context);
row.setOrientation(android.widget.LinearLayout.HORIZONTAL);
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
row.setPadding(0, self.dp(4), 0, self.dp(2));
var title = new android.widget.TextView(context);
title.setText(String(label));
title.setTextColor(subTextColor);
title.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
title.setPadding(0, 0, self.dp(8), 0);
row.addView(title);
var valueTv = new android.widget.TextView(context);
valueTv.setTextColor(textColor);
valueTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
valueTv.setPadding(0, 0, self.dp(8), 0);
row.addView(valueTv);
var seek = new android.widget.SeekBar(context);
try { seek.setMax(255); } catch(eTintRgbMax0) {}
row.addView(seek, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1));
if (key === "r") { tintPaletteState.redSeek = seek; tintPaletteState.redValueTv = valueTv; }
else if (key === "g") { tintPaletteState.greenSeek = seek; tintPaletteState.greenValueTv = valueTv; }
else if (key === "b") { tintPaletteState.blueSeek = seek; tintPaletteState.blueValueTv = valueTv; }
updateRgbValueLabel(key, 255);
try {
seek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({
onProgressChanged: function(seekBar, progress, fromUser) {
updateRgbValueLabel(key, progress);
if (tintPaletteState.syncing) return;
if (fromUser) applyTintFromRgbSeekbars(false);
},
onStartTrackingTouch: function(seekBar) {},
onStopTrackingTouch: function(seekBar) {
if (tintPaletteState.syncing) return;
applyTintFromRgbSeekbars(true);
}
}));
} catch(eTintRgbListener0) {}
return row;
}
var tintRgbTitle = new android.widget.TextView(context);
tintRgbTitle.setText("RGB 调色器0-255");
tintRgbTitle.setTextColor(subTextColor);
tintRgbTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tintRgbTitle.setPadding(0, 0, 0, self.dp(4));
tintPaletteBody.addView(tintRgbTitle);
tintPaletteBody.addView(createRgbControlRow("红", "r", "#FFE53935"));
tintPaletteBody.addView(createRgbControlRow("绿", "g", "#FF43A047"));
tintPaletteBody.addView(createRgbControlRow("蓝", "b", "#FF1E88E5"));
var tintRecentTitle = new android.widget.TextView(context);
tintRecentTitle.setText("最近使用(最多 5 个)");
tintRecentTitle.setTextColor(subTextColor);
tintRecentTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tintRecentTitle.setPadding(0, 0, 0, self.dp(4));
tintPaletteBody.addView(tintRecentTitle);
var tintRecentEmptyTv = new android.widget.TextView(context);
tintRecentEmptyTv.setText("暂无最近颜色");
tintRecentEmptyTv.setTextColor(subTextColor);
tintRecentEmptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
tintRecentEmptyTv.setPadding(0, 0, 0, self.dp(4));
tintPaletteBody.addView(tintRecentEmptyTv);
tintPaletteState.recentEmptyTv = tintRecentEmptyTv;
var tintRecentGrid = new android.widget.GridLayout(context);
try { tintRecentGrid.setColumnCount(5); } catch(eTintRecentCols0) {}
tintPaletteBody.addView(tintRecentGrid);
tintPaletteState.recentGrid = tintRecentGrid;
var tintCommonTitle = new android.widget.TextView(context);
tintCommonTitle.setText("常用颜色");
tintCommonTitle.setTextColor(subTextColor);
tintCommonTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
tintCommonTitle.setPadding(0, self.dp(8), 0, self.dp(4));
tintPaletteBody.addView(tintCommonTitle);
var tintCommonGrid = new android.widget.GridLayout(context);
try { tintCommonGrid.setColumnCount(1); } catch(eTintCommonCols0) {}
tintPaletteBody.addView(tintCommonGrid);
tintPaletteState.commonGrid = tintCommonGrid;
function resolveTintCommonGridLayout() {
var rawWidth = 0;
try { if (tintPaletteState.commonGrid) rawWidth = Number(tintPaletteState.commonGrid.getWidth() || 0); } catch(eTintCommonW0) {}
if (rawWidth <= 0) {
try { if (tintPaletteState.body) rawWidth = Number(tintPaletteState.body.getWidth() || 0); } catch(eTintCommonW1) {}
}
if (rawWidth <= 0) rawWidth = self.dp(320);
var marginPx = self.dp(4);
var minCellWidthPx = self.dp(Number(tintPaletteState.commonMinCellWidthDp || 72));
var cellOuterMinWidth = minCellWidthPx + marginPx * 2;
var innerWidth = rawWidth - self.dp(4);
if (innerWidth <= 0) innerWidth = rawWidth;
var cols = Math.max(1, Math.floor(innerWidth / cellOuterMinWidth));
var cellWidthPx = Math.floor(innerWidth / cols) - marginPx * 2;
if (cellWidthPx < self.dp(56)) cellWidthPx = self.dp(56);
tintPaletteState.commonCols = cols;
tintPaletteState.commonCellWidthPx = cellWidthPx;
tintPaletteState.commonLastMeasuredWidth = rawWidth;
return { cols: cols, cellWidthPx: cellWidthPx };
}
function getTintSortInfo(hexValue) {
var normalized = normalizeTintColorValue(hexValue, false);
if (!normalized) return { hue: 999, sat: -1, val: -1 };
var rgb = normalized.substring(3);
var r = parseInt(rgb.substring(0, 2), 16) / 255.0;
var g = parseInt(rgb.substring(2, 4), 16) / 255.0;
var b = parseInt(rgb.substring(4, 6), 16) / 255.0;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var d = max - min;
var h = 0;
if (d === 0) {
h = 999;
} else if (max === r) {
h = ((g - b) / d) % 6;
} else if (max === g) {
h = ((b - r) / d) + 2;
} else {
h = ((r - g) / d) + 4;
}
if (h !== 999) {
h = h * 60;
if (h < 0) h += 360;
}
var s = max === 0 ? 0 : d / max;
var v = max;
return { hue: h, sat: s, val: v };
}
function sortTintCommonDefs(defs) {
var fixed = [];
var auto = [];
var i;
for (i = 0; i < defs.length; i++) {
var item = defs[i];
if (!item) continue;
if (item.followTheme) fixed.push(item);
else {
var sortInfo = getTintSortInfo(item.hex);
item.__sortHue = sortInfo.hue;
item.__sortSat = sortInfo.sat;
item.__sortVal = sortInfo.val;
auto.push(item);
}
}
auto.sort(function(a, b) {
if (a.__sortHue !== b.__sortHue) return a.__sortHue - b.__sortHue;
if (a.__sortSat !== b.__sortSat) return b.__sortSat - a.__sortSat;
return b.__sortVal - a.__sortVal;
});
return fixed.concat(auto);
}
var tintCommonDefs = sortTintCommonDefs([
{ label: "跟随主题", hex: "", followTheme: true },
{ label: "白色", hex: "#FFFFFFFF" },
{ label: "黑色", hex: "#FF000000" },
{ label: "灰色", hex: "#FF6B7280" },
{ label: "红色", hex: "#FFE53935" },
{ label: "橙色", hex: "#FFFB8C00" },
{ label: "琥珀", hex: "#FFF59E0B" },
{ label: "黄色", hex: "#FFFDD835" },
{ label: "黄绿", hex: "#FFA3E635" },
{ label: "绿色", hex: "#FF43A047" },
{ label: "青绿", hex: "#FF10B981" },
{ label: "青色", hex: "#FF00ACC1" },
{ label: "天蓝", hex: "#FF38BDF8" },
{ label: "蓝色", hex: "#FF1E88E5" },
{ label: "靛蓝", hex: "#FF4F46E5" },
{ label: "紫色", hex: "#FF8E24AA" },
{ label: "洋红", hex: "#FFC026D3" },
{ label: "粉色", hex: "#FFD81B60" },
{ label: "棕色", hex: "#FF8D6E63" },
{ label: "银灰", hex: "#FFCBD5E1" }
]);
function renderTintCommonGrid() {
if (!tintPaletteState.commonGrid) return;
var layoutInfo = resolveTintCommonGridLayout();
try {
tintPaletteState.commonGrid.removeAllViews();
tintPaletteState.commonGrid.setColumnCount(Math.max(1, Number(layoutInfo.cols || 1)));
} catch(eTintCommonRender0) {}
var tintCi;
for (tintCi = 0; tintCi < tintCommonDefs.length; tintCi++) {
tintPaletteState.commonGrid.addView(createTintSwatchCell(tintCommonDefs[tintCi].label, tintCommonDefs[tintCi].hex, !!tintCommonDefs[tintCi].followTheme, layoutInfo.cellWidthPx));
}
}
renderRecentTintGrid();
renderTintCommonGrid();
setTintPaletteExpanded(tintPaletteState.expanded);
syncTintUiFromInput(false);
try {
inputShortXIconTint.input.addTextChangedListener(new JavaAdapter(android.text.TextWatcher, {
afterTextChanged: function(s) {
if (tintPaletteState.syncing) return;
syncTintUiFromInput(false);
},
beforeTextChanged: function(s, st, c, a) {},
onTextChanged: function(s, st, b, c) {}
}));
} catch(eTwIcon2) {}
try {
tintCommonGrid.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener({
onGlobalLayout: function() {
if (!tintPaletteState.expanded) return;
var oldCols = Number(tintPaletteState.commonCols || 0);
var oldWidth = Number(tintPaletteState.commonLastMeasuredWidth || 0);
var info = resolveTintCommonGridLayout();
if (Number(info.cols || 0) !== oldCols || Number(tintPaletteState.commonLastMeasuredWidth || 0) !== oldWidth) {
renderTintCommonGrid();
}
}
}));
} catch(eTintCommonLayout0) {}
// 图标类型切换函数
function updateIconInputs(type) {
if (type === "file") {
inputIconPath.view.setVisibility(android.view.View.VISIBLE);
shortxQuickRow.setVisibility(android.view.View.GONE);
shortxPickerWrap.setVisibility(android.view.View.GONE);
inputShortXIconTint.view.setVisibility(android.view.View.GONE);
tintPaletteWrap.setVisibility(android.view.View.GONE);
shortxPickerState.expanded = false;
try { if (shortxPickerState.toggleBtn) shortxPickerState.toggleBtn.setText(getShortXPickerClosedLabel()); } catch(eBt0) {}
currentShortXIconName = "";
inputShortXIconTint.input.setText("");
} else if (type === "shortx") {
inputIconPath.view.setVisibility(android.view.View.GONE);
shortxQuickRow.setVisibility(android.view.View.VISIBLE);
inputShortXIconTint.view.setVisibility(android.view.View.VISIBLE);
tintPaletteWrap.setVisibility(android.view.View.VISIBLE);
inputIconPath.input.setText("");
syncTintUiFromInput(false);
updateShortXIconPreview();
}
}
updateShortXIconPreview();
// 根据初始值设置选中状态
var hasIconPath = targetBtn.iconPath && String(targetBtn.iconPath).length > 0;
var hasShortXIcon = normalizedInitShortX && String(normalizedInitShortX).length > 0;
if (hasShortXIcon) {
rbIconShortX.setChecked(true);
updateIconInputs("shortx");
} else {
// 默认选中文件路径(或都不选)
rbIconFile.setChecked(true);
updateIconInputs("file");
}
// 监听切换
iconRadioGroup.setOnCheckedChangeListener(new android.widget.RadioGroup.OnCheckedChangeListener({
onCheckedChanged: function(group, checkedId) {
var checkedRb = group.findViewById(checkedId);
if (checkedRb) {
var tag = String(checkedRb.getTag());
updateIconInputs(tag);
}
}
}));
// 2. 动作类型(自动换行:用 GridLayout 稳定实现)
// 这段代码的主要内容/用途把「Shell/App/广播/Intent/快捷方式」做成会自动换行的单选框区域。
// 说明:之前用"多行 LinearLayout + 手工测量"在部分 ROM/布局时序下会出现宽度为 0 或不渲染,导致"单选框看不见"。
// 改为 GridLayout先计算列数(1~3),再按固定单元宽度填充,保证必定可见且可换行。
var typeWrap = new android.widget.LinearLayout(context);
typeWrap.setOrientation(android.widget.LinearLayout.VERTICAL);
typeWrap.setPadding(0, self.dp(8), 0, self.dp(16));
try {
var _lpTW = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
typeWrap.setLayoutParams(_lpTW);
} catch (eLpTW) {}
var typeLbl = new android.widget.TextView(context);
typeLbl.setText("动作类型 (Action Type)");
typeLbl.setTextColor(subTextColor);
typeLbl.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
typeWrap.addView(typeLbl);
// Grid 容器
var typeGrid = new android.widget.GridLayout(context);
try {
typeGrid.setOrientation(android.widget.GridLayout.HORIZONTAL);
} catch (eOri) {}
try {
var _lpTG = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
typeGrid.setLayoutParams(_lpTG);
} catch (eLpTG) {}
typeGrid.setPadding(0, self.dp(6), 0, 0);
typeWrap.addView(typeGrid);
// 动作类型单选按钮列表(用于互斥选择)
var typeRbList = [];
var selectedTypeVal = "shell";
var _typeChanging = false;
var types = [
{ id: 1, val: "shell", txt: "Shell" },
{ id: 2, val: "app", txt: "App" },
{ id: 3, val: "broadcast", txt: "发送广播" },
{ id: 4, val: "shortcut", txt: "快捷方式" }
];
// 初始化选中值
try {
for (var ti0 = 0; ti0 < types.length; ti0++) {
if (targetBtn.type === types[ti0].val) {
selectedTypeVal = types[ti0].val;
break;
}
}
} catch (eSel0) {}
function applySelectedType(val) {
// 这段代码的主要内容/用途:更新选中值并刷新动态输入区可见性。
try {
if (!val) val = "shell";
selectedTypeVal = String(val);
updateVisibility(selectedTypeVal);
} catch (e) {}
}
// 创建 RadioButton只创建一次
for (var i = 0; i < types.length; i++) {
var rb = new android.widget.RadioButton(context);
rb.setText(types[i].txt);
rb.setTextColor(textColor);
rb.setTag(types[i].val);
try { rb.setChecked(types[i].val === selectedTypeVal); } catch (eC0) {}
try { rb.setSingleLine(true); } catch (eSL) {}
try { rb.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch (eEl) {}
try { rb.setMinWidth(0); } catch (eMW) {}
try { rb.setMinHeight(self.dp(40)); } catch (eMH) {}
rb.setPadding(self.dp(8), self.dp(6), self.dp(8), self.dp(6));
// 互斥选择
try {
rb.setOnCheckedChangeListener(new android.widget.CompoundButton.OnCheckedChangeListener({
onCheckedChanged: function (buttonView, isChecked) {
try {
if (_typeChanging) return;
if (!isChecked) return;
_typeChanging = true;
var v = null;
try { v = buttonView.getTag(); } catch (eTag) { v = null; }
if (v != null) selectedTypeVal = String(v);
for (var k = 0; k < typeRbList.length; k++) {
var other = typeRbList[k];
if (other && other !== buttonView) {
try { other.setChecked(false); } catch (eOff) {}
}
}
_typeChanging = false;
applySelectedType(selectedTypeVal);
} catch (eChg) {
_typeChanging = false;
}
}
}));
} catch (eLis) {
// 兜底:如果某些 ROM 不接受接口对象,至少不影响渲染
}
typeRbList.push(rb);
}
function rebuildTypeGrid() {
// 这段代码的主要内容/用途:按当前宽度计算列数(1~3),重建 GridLayout实现自动换行。
try { typeGrid.removeAllViews(); } catch (e0) {}
var availW = 0;
try { availW = typeWrap.getWidth() - self.dp(8); } catch (e1) { availW = 0; }
// 这段代码的主要内容/用途:宽度未量出时,用屏幕宽度兜底,避免首次渲染列数/单元宽度异常导致看不见或布局挤压。
// 说明:部分 ROM/时序下 typeWrap.getWidth() 在首次调用时可能为 0此时用 DisplayMetrics 保证可见。
if (!availW || availW <= 0) {
try {
var dm = context.getResources().getDisplayMetrics();
availW = (dm && dm.widthPixels) ? (dm.widthPixels - self.dp(48)) : self.dp(320);
} catch (eDm) {
availW = self.dp(320);
}
}
// 宽度未量出时:先按 2 列兜底(保证能看见)
var cols = 2;
if (availW && availW > self.dp(240)) {
var minCell = self.dp(120);
cols = Math.floor(availW / minCell);
if (cols < 1) cols = 1;
if (cols > 3) cols = 3;
}
try { typeGrid.setColumnCount(cols); } catch (eC) {}
// 单元宽度:按列数均分
var cellW = 0;
try {
var gap = self.dp(8);
cellW = Math.floor((availW - gap * (cols - 1)) / cols);
if (!cellW || cellW < self.dp(90)) cellW = self.dp(140);
} catch (eW) {
cellW = self.dp(140);
}
for (var i = 0; i < typeRbList.length; i++) {
var rb = typeRbList[i];
if (!rb) continue;
try {
var lp = new android.widget.GridLayout.LayoutParams();
lp.width = cellW;
lp.height = android.widget.LinearLayout.LayoutParams.WRAP_CONTENT;
lp.setMargins(0, self.dp(6), self.dp(8), 0);
rb.setLayoutParams(lp);
} catch (eLP) {}
try { typeGrid.addView(rb); } catch (eAdd) {}
}
}
// 首次:先渲染一次(保证立即可见)
try { rebuildTypeGrid(); } catch (eR0) {}
// 布局变化时:重新计算列数(旋转/宽度变化/首次测量完成)
try {
typeWrap.addOnLayoutChangeListener(new android.view.View.OnLayoutChangeListener({
onLayoutChange: function (v, l, t, r, b, ol, ot, orr, ob) {
try {
if ((r - l) !== (orr - ol)) {
rebuildTypeGrid();
}
} catch (eL) {}
}
}));
} catch (eLC) {}
form.addView(typeWrap);
// 3. 动态输入区
var dynamicContainer = new android.widget.LinearLayout(context);
dynamicContainer.setOrientation(android.widget.LinearLayout.VERTICAL);
form.addView(dynamicContainer);
// --- Shell ---
var shellWrap = new android.widget.LinearLayout(context);
shellWrap.setOrientation(android.widget.LinearLayout.VERTICAL);
var initCmd = targetBtn.cmd || "";
if (targetBtn.cmd_b64) initCmd = decodeBase64Utf8(targetBtn.cmd_b64) || initCmd;
var inputShell = self.ui.createInputGroup(self, "Shell 命令", initCmd, true, "支持常规 Shell 命令 (input, am, pm...)");
shellWrap.addView(inputShell.view);
// # Root 开关已移除:广播桥接收端默认以 root 执行,开关无意义
dynamicContainer.addView(shellWrap);
// --- App ---
var appWrap = new android.widget.LinearLayout(context);
appWrap.setOrientation(android.widget.LinearLayout.VERTICAL);
var inputPkg = self.ui.createInputGroup(self, "应用包名 (Package)", targetBtn.pkg, false, "例如: com.tencent.mm");
appWrap.addView(inputPkg.view);
// # 启动用户(主应用/分身应用)
// 这段代码的主要内容/用途:为"启动应用"提供跨用户启动选择。主用户一般为 0分身/工作资料用户因 ROM 不同可能是 10/999 等。
// 说明:这里只做"手动指定",避免在 system_server 里做复杂探测导致不稳定。
var inputAppLaunchUser = self.ui.createInputGroup(self, "启动用户ID (主=0 / 分身=10/999 等)", (targetBtn.launchUserId != null ? String(targetBtn.launchUserId) : ""), false, "留空默认 0主用户");
appWrap.addView(inputAppLaunchUser.view);
dynamicContainer.addView(appWrap);
// --- Broadcast ---
var bcWrap = new android.widget.LinearLayout(context);
bcWrap.setOrientation(android.widget.LinearLayout.VERTICAL);
var inputAction = self.ui.createInputGroup(self, "广播 Action", targetBtn.action, false, "例如: com.example.TEST_ACTION");
bcWrap.addView(inputAction.view);
var initExtras = "";
if (targetBtn.extras) initExtras = JSON.stringify(targetBtn.extras);
else if (targetBtn.extra) initExtras = JSON.stringify(targetBtn.extra);
var inputExtras = self.ui.createInputGroup(self, "Extras (JSON, 选填)", initExtras, true, "例如: {\"key\": \"value\"}");
bcWrap.addView(inputExtras.view);
dynamicContainer.addView(bcWrap);
// --- Shortcut ---
// 新增:启动系统/应用快捷方式Launcher Shortcuts
// 字段说明:
// - pkg: 目标应用包名
// - shortcutId: 快捷方式 ID可从 LauncherApps/Shortcuts 列表中获取)
var shortcutWrap = new android.widget.LinearLayout(context);
shortcutWrap.setOrientation(android.widget.LinearLayout.VERTICAL);
var inputScPkg = self.ui.createInputGroup(self, "快捷方式包名 (Package)", targetBtn.pkg, false, "例如: com.tencent.mm");
// # 选择快捷方式后用于保存的启动参数intentUri/userId
var scSelectedIntentUri = targetBtn.intentUri ? String(targetBtn.intentUri) : "";
var scSelectedUserId = (targetBtn.userId != null) ? parseInt(String(targetBtn.userId), 10) : 0;
shortcutWrap.addView(inputScPkg.view);
var inputScId = self.ui.createInputGroup(self, "快捷方式 ID (shortcutId)", targetBtn.shortcutId, false, "例如: com.tencent.mm:shortcut_xxx 或内部ID以实际查询结果为准");
shortcutWrap.addView(inputScId.view);
// # UI 优化:快捷方式类型下,包名与 shortcutId 属于"内部字段",不再在界面上占空间显示
// # 需求当按钮类型选中快捷方式时UI 不显示包名/ID改为显示快捷方式的启动命令am start intent
// # 说明:仍然保留 pkg/shortcutId 用于数据保存与图标解析,但将输入框隐藏。
try { inputScPkg.view.setVisibility(android.view.View.GONE); } catch(eHidePkg) {}
try { inputScId.view.setVisibility(android.view.View.GONE); } catch(eHideId) {}
// # 显示:快捷方式启动命令(只读展示,方便复制/核对)
function __scBuildLaunchCmd() {
try {
var u = (scSelectedUserId != null) ? parseInt(String(scSelectedUserId), 10) : 0;
if (isNaN(u)) u = 0;
var iu = scSelectedIntentUri ? String(scSelectedIntentUri) : "";
if (!iu || iu.length === 0) return "(未选择快捷方式)";
// # 注意intentUri 中可能包含 ; 等字符,使用单引号包裹更安全
return "am start --user " + String(u) + " '" + iu + "'";
} catch(eCmd) {
return "(命令生成失败)";
}
}
var inputScCmd = self.ui.createInputGroup(self, "快捷方式启动命令 (am start)", __scBuildLaunchCmd(), false, "选择快捷方式后自动生成,可复制到 Termux 验证");
shortcutWrap.addView(inputScCmd.view);
// # 需求:快捷方式只使用 JavaScript 执行,取消 Shell因此隐藏 am start 命令框
try { inputScCmd.view.setVisibility(android.view.View.GONE); } catch(eHideScCmd) {}
try {
// # 命令框可编辑:允许你在配置时手动指定/微调启动命令(例如锁定分身/主微信)
// # 注意:选择快捷方式后仍会自动刷新该字段;如需保留手动内容,可在选择后再修改。
inputScCmd.input.setEnabled(true);
inputScCmd.input.setFocusable(true);
inputScCmd.input.setFocusableInTouchMode(true);
try { inputScCmd.input.setSingleLine(false); } catch(eSL) {}
try { inputScCmd.input.setMinLines(2); } catch(eML) {}
try { inputScCmd.input.setHorizontallyScrolling(false); } catch(eHS) {}
try { inputScCmd.input.setTextIsSelectable(true); } catch(eSel) {}
} catch(eRO) {}
// # 快捷方式 JS 启动代码(自动生成,可手动微调)
// 这段代码的主要内容/用途:为"快捷方式按钮"提供可执行的 JS 启动脚本(默认走 startIntentAsUserByUri用于精确指定主/分身 userId 并避免弹选择器。
// 说明:
// 1) 选择快捷方式后会自动生成并回填;
// 2) 保存按钮会把该脚本写入按钮配置字段 shortcutJsCode
// 3) 运行时优先执行该脚本,失败才回退 Shell am start保证桌面移除后仍可启动。
function __scBuildDefaultJsCode() {
// # 这段代码的主要内容/用途:为"快捷方式按钮"生成"自包含"的 JavaScript 启动脚本(严格按用户成功示例写法),并确保字符串安全转义
try {
var u0 = (scSelectedUserId != null) ? parseInt(String(scSelectedUserId), 10) : 0;
if (isNaN(u0)) u0 = 0;
// # 优先使用 launchUserId你在按钮配置里锁定主/分身时用),否则回退 userId
try {
if (targetBtn && targetBtn.launchUserId != null && String(targetBtn.launchUserId).length > 0) {
var lu = parseInt(String(targetBtn.launchUserId), 10);
if (!isNaN(lu)) u0 = lu;
}
} catch (eLuSc) {}
var iu0 = scSelectedIntentUri ? String(scSelectedIntentUri) : "";
if (!iu0 || iu0.length === 0) {
return "// # 这段代码的主要内容/用途:未选择快捷方式,暂无可生成的启动脚本\n\n'err_no_shortcut'\n";
}
// # 用 JSON.stringify 做安全转义,避免 intentUri 中包含引号/反斜杠/换行导致脚本语法错误
var sIntent = JSON.stringify(iu0);
var sUser = String(u0);
// # 生成"自包含"脚本严格按用户成功示例Intent.parseUri + UserHandle.of + context.startActivityAsUser
var out = "";
out += "// # 这段代码的主要内容/用途:在指定用户(UserHandle)下启动快捷方式(自动生成,自包含,可复制到独立 JS 任务中运行)\n";
out += "importClass(android.content.Intent);\n";
out += "importClass(android.os.UserHandle);\n\n";
out += "var r = 'ok';\n";
out += "try {\n";
out += " var myIntent = Intent.parseUri(" + sIntent + ", 0);\n";
out += " myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n";
out += " var userHandle = UserHandle.of(" + sUser + ");\n";
out += " context.startActivityAsUser(myIntent, userHandle);\n";
out += " r = 'ok_user_' + String(" + sUser + ");\n";
out += "} catch (e) {\n";
out += " r = 'err_' + e;\n";
out += "}\n";
out += "r\n";
return out;
} catch (eBuildJs) {
return "// # 这段代码的主要内容/用途:生成快捷方式启动脚本失败\n\n'err_build_js'\n";
}
}
// # 安全回填 JS 编辑框:集中处理,避免重复代码与空指针崩溃:集中处理,避免重复代码与空指针崩溃
function __scUpdateJsCodeSafe() {
try {
if (inputScJsCode && inputScJsCode.input) {
inputScJsCode.input.setText(String(__scBuildDefaultJsCode()));
}
} catch(eUpJs) {}
}
var inputScJsCode = self.ui.createInputGroup(self, "快捷方式 JS 启动代码 (startActivityAsUser)", (targetBtn.shortcutJsCode ? String(targetBtn.shortcutJsCode) : ""), false, "选择快捷方式后自动生成;你也可以手动改 userId 或其他参数");
shortcutWrap.addView(inputScJsCode.view);
try {
// # JS 编辑框尺寸:与 Shell 命令框一致,避免过高占屏(用户可滚动查看完整内容)
inputScJsCode.input.setEnabled(true);
inputScJsCode.input.setFocusable(true);
inputScJsCode.input.setFocusableInTouchMode(true);
try { inputScJsCode.input.setSingleLine(false); } catch(eJsSL) {}
try { inputScJsCode.input.setMinLines(2); } catch(eJsML) {}
try { inputScJsCode.input.setMaxLines(4); } catch(eJsMXL) {}
try { inputScJsCode.input.setHorizontallyScrolling(false); } catch(eJsHS) {}
try { inputScJsCode.input.setTextIsSelectable(true); } catch(eJsSel) {}
} catch(eJsBox) {}
// # 快捷方式选择器(内联折叠版):在"新增/编辑按钮页"内部展开/收起列表,并回填 pkg/shortcutId
// 这段代码的主要内容/用途:把原先"弹出选择器窗口"的方式改为"折叠展开在本页下方显示",避免上下层遮挡问题。
// 设计要点:
// 1) 不再依赖 WindowManager 叠层,直接作为本页 UI 的一部分渲染;
// 2) 数据加载放到子线程,避免卡住 UI
// 3) 关闭/收起只是隐藏列表,不会触发频繁 add/remove View 的不稳定路径。
var scInlineState = {
expanded: false,
loading: false,
loaded: false,
// # 新增:记录上次加载时间,用于判断是否需要刷新(例如你刚把微信小程序"添加到桌面"后)
loadedTs: 0,
// # 新增:手动触发强制刷新(点击"刷新"按钮)
forceReload: false,
allItems: [],
lastQuery: ""
};
// # 图标缓存与队列(避免每次重渲染都重复取 icon减少卡顿
// 这段代码的主要内容/用途:为内联列表提供轻量级 icon 缓存与串行加载队列,避免一次性开太多线程。
var scIconCache = {};
var scIconQueue = [];
var scIconWorkerRunning = false;
function __scIconKey(it) {
try { return __scStr(it.pkg) + '|' + __scStr(it.shortcutId) + '|' + __scStr(it.userId); } catch(e) { return ''; }
}
function __scLoadIconForItem(it) {
// 这段代码的主要内容/用途:优先取快捷方式图标,失败则回退到应用图标。
try {
if (!it) return null;
if (it.shortcutInfo) {
try {
var la = context.getSystemService(android.content.Context.LAUNCHER_APPS_SERVICE);
if (la) {
var dr = la.getShortcutIconDrawable(it.shortcutInfo, 0);
if (dr) return dr;
}
} catch(eS0) {}
}
try {
var pm = context.getPackageManager();
return pm.getApplicationIcon(__scStr(it.pkg));
} catch(eA0) {}
} catch(eAll0) {}
return null;
}
function __scEnqueueIconLoad(it, iv) {
try {
var key = __scIconKey(it);
if (!key) return;
if (scIconCache[key]) {
try { iv.setImageDrawable(scIconCache[key]); } catch(eSet0) {}
return;
}
// # 记录 tag防止滚动/重绘后错位
try { iv.setTag(key); } catch(eTag0) {}
scIconQueue.push({ key: key, it: it, iv: iv });
if (!scIconWorkerRunning) {
scIconWorkerRunning = true;
new java.lang.Thread(new java.lang.Runnable({
run: function() {
while (true) {
var job = null;
try { if (scIconQueue.length > 0) job = scIconQueue.shift(); } catch(eQ0) { job = null; }
if (!job) break;
var dr = null;
try { dr = __scLoadIconForItem(job.it); } catch(eLd0) { dr = null; }
if (dr) scIconCache[job.key] = dr;
try {
self.runOnUiThreadSafe(function() {
try {
if (!job || !job.iv) return;
var cur = null;
try { cur = job.iv.getTag(); } catch(eTg0) { cur = null; }
if (cur && String(cur) === String(job.key) && dr) {
job.iv.setImageDrawable(dr);
}
} catch(eUi0) {}
});
} catch(ePost0) {}
}
scIconWorkerRunning = false;
}
})).start();
}
} catch(eEnq0) {}
}
// # 折叠头部(点击展开/收起)
var scHeader = new android.widget.LinearLayout(context);
scHeader.setOrientation(android.widget.LinearLayout.HORIZONTAL);
scHeader.setGravity(android.view.Gravity.CENTER_VERTICAL);
scHeader.setPadding(self.dp(10), self.dp(10), self.dp(10), self.dp(10));
var scHeaderTv = new android.widget.TextView(context);
scHeaderTv.setText("选择快捷方式(点击展开)");
scHeaderTv.setTextColor(textColor);
scHeaderTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
var scHeaderTvLp = new android.widget.LinearLayout.LayoutParams(0, -2);
scHeaderTvLp.weight = 1;
scHeaderTv.setLayoutParams(scHeaderTvLp);
scHeader.addView(scHeaderTv);
var scRefreshTv = new android.widget.TextView(context);
// 这段代码的主要内容/用途:手动刷新快捷方式列表(用于你"添加到桌面"后立即重新加载)
scRefreshTv.setText("刷新");
scRefreshTv.setTextColor(subTextColor);
scRefreshTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
scRefreshTv.setPadding(self.dp(10), self.dp(6), self.dp(10), self.dp(6));
scRefreshTv.setClickable(true);
scRefreshTv.setOnClickListener(new android.view.View.OnClickListener({
onClick: function(v) {
try {
scInlineState.forceReload = true;
scInlineState.loaded = false;
// 清空 icon 缓存,避免旧图标占用内存且影响新列表显示
try { scIconCache = {}; } catch(eC0) {}
try { scIconQueue = []; } catch(eC1) {}
try { scIconWorkerRunning = false; } catch(eC2) {}
// 若当前已展开,立即触发重新加载与渲染
if (scInlineState.expanded) __scEnsureLoadedAndRender();
} catch(eR) {}
}
}));
scHeader.addView(scRefreshTv);
var scArrowTv = new android.widget.TextView(context);
scArrowTv.setText("▼");
scArrowTv.setTextColor(subTextColor);
scArrowTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
scHeader.addView(scArrowTv);
// # 折叠内容容器(默认隐藏)
var scBody = new android.widget.LinearLayout(context);
scBody.setOrientation(android.widget.LinearLayout.VERTICAL);
scBody.setVisibility(android.view.View.GONE);
scBody.setPadding(self.dp(10), 0, self.dp(10), self.dp(10));
// # 搜索框(内联)
var scSearchWrap = self.ui.createInputGroup(self, "搜索", "", false, "输入关键词过滤:名称/包名/ID");
scBody.addView(scSearchWrap.view);
// # 状态提示
var scHint = new android.widget.TextView(context);
scHint.setText("");
scHint.setTextColor(subTextColor);
scHint.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
scHint.setPadding(0, self.dp(6), 0, self.dp(6));
scBody.addView(scHint);
// # 列表框容器(内部纵向滚动)
// 这段代码的主要内容/用途:在"新增/编辑按钮页"内部提供一个固定高度的列表框,让列表在框内纵向滚动,避免把整页撑得很长。
// # 列表框ListView 方案):用 ListView 代替 ScrollView+LinearLayout避免父级 ScrollView 抢手势导致"滚不动/卡住"
// 这段代码的主要内容/用途:把快捷方式列表渲染成真正可滚动的列表控件,滚动只发生在列表框内部。
var scListBox = new android.widget.FrameLayout(context);
try {
// # 高度智能自适应:取屏幕高度的 45%,并限制在 [180dp, 420dp] 区间
var dm = context.getResources().getDisplayMetrics();
var hPx = dm ? dm.heightPixels : 0;
var targetPx = hPx > 0 ? Math.floor(hPx * 0.45) : self.dp(260);
var minPx = self.dp(180);
var maxPx = self.dp(420);
if (targetPx < minPx) targetPx = minPx;
if (targetPx > maxPx) targetPx = maxPx;
var lpBox = new android.widget.LinearLayout.LayoutParams(-1, targetPx);
scListBox.setLayoutParams(lpBox);
} catch(eH0) {}
try {
// # 列表框描边+圆角
var gdBox = new android.graphics.drawable.GradientDrawable();
gdBox.setShape(android.graphics.drawable.GradientDrawable.RECTANGLE);
gdBox.setCornerRadius(self.dp(10));
var _isDark0 = self.isDarkTheme();
gdBox.setColor(_isDark0 ? C.cardDark : C.cardLight);
gdBox.setStroke(self.dp(1), _isDark0 ? C.dividerDark : C.dividerLight);
scListBox.setBackground(gdBox);
scListBox.setPadding(self.dp(6), self.dp(6), self.dp(6), self.dp(6));
} catch(eBg0) {}
// # ListView真正的列表控件支持框内纵向滚动
var scList = new android.widget.ListView(context);
try { scList.setDivider(null); } catch(eDiv0) {}
try { scList.setVerticalScrollBarEnabled(true); } catch(eSb0) {}
try { scList.setOverScrollMode(android.view.View.OVER_SCROLL_IF_CONTENT_SCROLLS); } catch(eOver0) {}
try { scList.setCacheColorHint(android.graphics.Color.TRANSPARENT); } catch(eCch0) {}
try {
// # 关键列表内滑动时禁止父容器拦截触摸事件DOWN/MOVE 都做)
scList.setOnTouchListener(new android.view.View.OnTouchListener({
onTouch: function(v, ev) {
try {
var act = ev.getActionMasked ? ev.getActionMasked() : ev.getAction();
if (act === android.view.MotionEvent.ACTION_DOWN || act === android.view.MotionEvent.ACTION_MOVE) {
try {
// # 向上递归,避免多层父布局抢事件
var p = v.getParent();
while (p != null) {
try { p.requestDisallowInterceptTouchEvent(true); } catch(eReq) {}
try { p = p.getParent(); } catch(eUp) { p = null; }
}
} catch(ePar0) {}
}
} catch(eTouch0) {}
// # 返回 false让 ListView 自己处理滚动
return false;
}
}));
} catch(eT0) {}
scListBox.addView(scList, new android.widget.FrameLayout.LayoutParams(-1, -1));
// # 放入折叠内容区
scBody.addView(scListBox);
// # 图标缓存(简单 LRU
var __scIconCache = {};
var __scIconKeys = [];
var __scIconMax = 120;
// # 图标异步加载器(单例,限制并发,避免在 getView 里同步 Binder 调用造成卡顿)
// 这段代码的主要内容/用途:把 shortcut icon 的获取放到后台线程UI 线程只设置占位/回退图标,加载完成后仅更新对应行的 ImageView。
var __scIconInFlight = {};
var __scIconLoader = (function() {
try {
if (self.__scIconLoaderSingleton) return self.__scIconLoaderSingleton;
} catch(eS) {}
var obj = { ht: null, h: null };
try {
var HandlerThread = android.os.HandlerThread;
var Handler = android.os.Handler;
obj.ht = new HandlerThread("sx-toolhub-scicon-loader");
obj.ht.start();
obj.h = new Handler(obj.ht.getLooper());
} catch(eT) {
obj.ht = null;
obj.h = null;
}
try { self.__scIconLoaderSingleton = obj; } catch(eSet) {}
return obj;
})();
function __scPostIconLoad(fn) {
try {
if (__scIconLoader && __scIconLoader.h) {
__scIconLoader.h.post(new java.lang.Runnable({ run: function() { try { fn(); } catch(e) {} } }));
return true;
}
} catch(eP) {}
return false;
}
function __scRequestIcon(it, imageView) {
try {
if (!it || !imageView) return;
var key = String(it.pkg) + "|" + String(it.shortcutId);
// 绑定 tag后续回调时校验避免复用行导致串图
try { imageView.setTag(key); } catch(eTag0) {}
// 命中缓存:直接显示
var hit = __scGetIcon(key);
if (hit) {
try { imageView.setImageDrawable(hit); } catch(eSet0) {}
return;
}
// 立即回退:先用 app icon更快
try {
if (__scPm) {
var appDr = __scPm.getApplicationIcon(String(it.pkg));
if (appDr) {
try { imageView.setImageDrawable(appDr); } catch(eSet1) {}
}
}
} catch(eApp0) {}
// 已在加载中:不重复排队
if (__scIconInFlight[key]) return;
__scIconInFlight[key] = 1;
// 后台加载 shortcut icon成功则写入缓存并只更新当前 tag 匹配的 ImageView
__scPostIconLoad(function() {
var dr = null;
try {
if (__scLauncherApps && it.shortcutInfo) {
dr = __scLauncherApps.getShortcutIconDrawable(it.shortcutInfo, 0);
}
} catch(eIc0) { dr = null; }
if (dr) __scPutIcon(key, dr);
try { delete __scIconInFlight[key]; } catch(eDel0) {}
try {
// 回到 UI只更新 tag 仍然匹配的行
scList.post(new java.lang.Runnable({ run: function() {
try {
if (!dr) return;
var tag = null;
try { tag = imageView.getTag(); } catch(eTg) { tag = null; }
if (String(tag) === String(key)) {
try { imageView.setImageDrawable(dr); } catch(eSet2) {}
}
} catch(eUi0) {}
}}));
} catch(eUi1) {}
});
} catch(eR) {}
}
function __scPutIcon(k, d) {
try {
if (!k) return;
if (__scIconCache[k]) return;
__scIconCache[k] = d;
__scIconKeys.push(k);
if (__scIconKeys.length > __scIconMax) {
var old = __scIconKeys.shift();
try { delete __scIconCache[old]; } catch(eDel) {}
}
} catch(ePut) {}
}
function __scGetIcon(k) {
try { return __scIconCache[k] || null; } catch(eGet) { return null; }
}
// # ListView 适配器数据
var __scData = [];
var __scLauncherApps = null;
var __scPm = null;
try { __scLauncherApps = context.getSystemService(android.content.Context.LAUNCHER_APPS_SERVICE); } catch(eLa0) {}
try { __scPm = context.getPackageManager(); } catch(ePm0) {}
// # 新增:应用名缓存
// 这段代码的主要内容/用途把包名解析成应用名ApplicationLabel并做缓存避免列表滚动时频繁调用 PackageManager。
var __scAppLabelCache = {};
var __scAppLabelCacheKeys = [];
var __scAppLabelCacheMax = 200;
function __scGetAppLabel(pkg) {
try {
var p = String(pkg || "");
if (!p) return "";
if (__scAppLabelCache[p]) return __scAppLabelCache[p];
if (!__scPm) {
__scAppLabelCache[p] = p;
return p;
}
var ai = null;
try { ai = __scPm.getApplicationInfo(p, 0); } catch(eAi) { ai = null; }
if (!ai) {
__scAppLabelCache[p] = p;
return p;
}
var lb = "";
try { lb = String(__scPm.getApplicationLabel(ai)); } catch(eLb) { lb = ""; }
if (!lb) lb = p;
// # 缓存写入,带 LRU 清理
__scAppLabelCache[p] = lb;
__scAppLabelCacheKeys.push(p);
if (__scAppLabelCacheKeys.length > __scAppLabelCacheMax) {
var old = __scAppLabelCacheKeys.shift();
try { delete __scAppLabelCache[old]; } catch(eDel) {}
}
return lb;
} catch(e0) {
try { return String(pkg || ""); } catch(e1) { return ""; }
}
}
var __scAdapter = new android.widget.BaseAdapter({
getCount: function() { try { return __scData.length; } catch(e) { return 0; } },
getItem: function(pos) { try { return __scData[pos]; } catch(e) { return null; } },
getItemId: function(pos) { return pos; },
getView: function(pos, convertView, parent) {
var row = convertView;
var holder = null;
try {
if (row == null) {
row = new android.widget.LinearLayout(context);
row.setOrientation(android.widget.LinearLayout.HORIZONTAL);
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
row.setPadding(self.dp(10), self.dp(8), self.dp(10), self.dp(8));
try {
var _isDark1 = self.isDarkTheme();
var bg = self.ui.createRoundDrawable(_isDark1 ? C.cardDark : C.cardLight, self.dp(10));
row.setBackground(bg);
} catch(eBg1) {}
var iv = new android.widget.ImageView(context);
var lpIv = new android.widget.LinearLayout.LayoutParams(self.dp(36), self.dp(36));
lpIv.rightMargin = self.dp(10);
iv.setLayoutParams(lpIv);
row.addView(iv);
var vv = new android.widget.LinearLayout(context);
vv.setOrientation(android.widget.LinearLayout.VERTICAL);
var lpVv = new android.widget.LinearLayout.LayoutParams(0, -2);
lpVv.weight = 1;
vv.setLayoutParams(lpVv);
var t1 = new android.widget.TextView(context);
t1.setTextColor(textColor);
t1.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
vv.addView(t1);
var t2 = new android.widget.TextView(context);
t2.setTextColor(subTextColor);
t2.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
t2.setPadding(0, self.dp(2), 0, 0);
vv.addView(t2);
row.addView(vv);
holder = { iv: iv, t1: t1, t2: t2 };
row.setTag(holder);
} else {
holder = row.getTag();
}
} catch(eRow0) {}
try {
var it = __scData[pos];
if (it && holder) {
// # 优化:显示为「应用名/快捷方式名」,例如「微信/扫一扫」
// 这段代码的主要内容/用途:让列表更容易辨认来源应用,并与用户期望一致。
var appName = "";
try { appName = __scGetAppLabel(it.pkg); } catch(eApp0) { appName = ""; }
var scName = String(it.label || "(无名称)");
if (appName && appName.length > 0) holder.t1.setText(String(appName) + "/" + scName);
else holder.t1.setText(scName);
holder.t2.setText("pkg=" + String(it.pkg) + " id=" + String(it.shortcutId) + " u=" + String(it.userId));
// # icon异步加载 shortcut iconUI 线程先回退 app icon避免滚动/渲染卡顿
// 这段代码的主要内容/用途:把耗时的 shortcut icon 获取移到后台线程列表滚动更顺滑、system_server 占用更稳。
try { __scRequestIcon(it, holder.iv); } catch(eIconReq) {}
}
} catch(eBind) {}
return row;
}
});
try { scList.setAdapter(__scAdapter); } catch(eAd0) {}
try {
scList.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener({
onItemClick: function(parent, view, position, id) {
try {
var obj = __scData[position];
if (!obj) return;
try { inputScPkg.input.setText(String(obj.pkg)); } catch(eSet1) {}
try { inputScId.input.setText(String(obj.shortcutId)); } catch(eSet2) {}
// # 回填:用于"Shell am start"启动的 intentUri + userId桌面移除后仍可启动
try { scSelectedIntentUri = obj.intentUri ? String(obj.intentUri) : ""; } catch(eSetIU) { scSelectedIntentUri = ""; }
try { scSelectedUserId = (obj.userId != null) ? parseInt(String(obj.userId), 10) : 0; } catch(eSetUID) { scSelectedUserId = 0; }
// # 同步刷新:启动命令展示框
try { if (inputScCmd && inputScCmd.input) inputScCmd.input.setText(String(__scBuildLaunchCmd())); } catch(eUpCmd) {}
// # 同步刷新JS 启动代码(选择快捷方式后自动生成并回填)
__scUpdateJsCodeSafe();
// # 回填:同时把快捷方式 icon 导出到 shortcut_icons并把图标路径写入"图标地址"编辑框
// 这段代码的主要内容/用途:确保微信小程序等快捷方式在从桌面移除后,按钮页/按钮管理页仍能显示对应图标。
try {
var __scIconPath = __scEnsureShortcutIconFile(obj);
if (__scIconPath) {
try { inputIconPath.input.setText(String(__scIconPath)); } catch(eSetIP) {}
}
} catch(eExp0) {}
// 可选:标题为空时自动填 label
try {
var curTitle = String(inputTitle.getValue() || "");
if ((!curTitle || curTitle.trim().length === 0) && obj.label) {
inputTitle.input.setText(String(obj.label));
}
} catch(eSet3) {}
try { self.state.pendingDirty = true; } catch(eDirty) {}
// # 收起列表
try {
scInlineState.expanded = false;
scBody.setVisibility(android.view.View.GONE);
scArrowTv.setText("▼");
// # 优化:标题同样显示为「应用名/快捷方式名」
// 这段代码的主要内容/用途:避免选中后标题只显示快捷方式名,导致不清楚来自哪个应用。
var _app = "";
try { _app = __scGetAppLabel(obj.pkg); } catch(eApp1) { _app = ""; }
var _nm = String(obj.label || obj.shortcutId || "快捷方式");
if (_app && _app.length > 0) scHeaderTv.setText("已选择:" + String(_app) + "/" + _nm + "(点击展开)");
else scHeaderTv.setText("已选择:" + _nm + "(点击展开)");
} catch(eFold) {}
} catch(eCb) {
try { self.toast("回填失败: " + eCb); } catch(eT) {}
}
}
}));
} catch(eClk0) {}
// # 工具函数:安全字符串
function __scStr(v) { try { return String(v == null ? "" : v); } catch(e) { return ""; } }
function __scLower(v) { try { return __scStr(v).toLowerCase(); } catch(e) { return ""; } }
// # 工具函数:导出快捷方式图标到 shortcut_icons 并返回路径
function __scEnsureShortcutIconFile(item) {
// 这段代码的主要内容/用途:把 Launcher Shortcuts 的图标在"可见时"持久化为 PNG后续按钮页直接读文件显示避免桌面移除后图标退化/缺失。
try {
if (!item) return "";
var pkg = __scStr(item.pkg);
var sid = __scStr(item.shortcutId);
var uid = (item.userId != null) ? __scStr(item.userId) : "0";
if (!pkg || !sid) return "";
var dir = String(APP_ROOT_DIR) + "/shortcut_icons";
try {
var d = new java.io.File(dir);
if (!d.exists()) d.mkdirs();
} catch(eMk) {}
// # 与主程序同名规则pkg__sid__u{uid}.png
var fn = __scSanitizeFileName(pkg) + "__" + __scSanitizeFileName(sid) + "__u" + __scSanitizeFileName(uid) + ".png";
var outPath = dir + "/" + fn;
// # 已存在则直接复用
try {
var f = new java.io.File(outPath);
if (f.exists() && f.isFile() && f.length() > 0) return outPath;
} catch(eEx) {}
// # 获取 Drawable优先 shortcut icon失败则回退 app icon
var dr = null;
try {
if (__scLauncherApps && item.shortcutInfo) {
try { dr = __scLauncherApps.getShortcutIconDrawable(item.shortcutInfo, 0); } catch(eD0) { dr = null; }
}
} catch(eLA) { dr = null; }
if (!dr) {
try {
var pm = context.getPackageManager();
dr = pm.getApplicationIcon(pkg);
} catch(eApp) { dr = null; }
}
if (!dr) return "";
// # Drawable -> Bitmap -> PNG
var bmp = null;
try { bmp = __scDrawableToBitmap(dr, 192); } catch(eBmp) { bmp = null; }
if (!bmp) return "";
var fos = null;
try {
fos = new java.io.FileOutputStream(outPath);
bmp.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, fos);
try { fos.flush(); } catch(eFl) {}
try { fos.close(); } catch(eCl) {}
fos = null;
} catch(eW) {
try { if (fos) fos.close(); } catch(eCl2) {}
fos = null;
return "";
}
return outPath;
} catch(eAll) {
return "";
}
}
function __scSanitizeFileName(s) {
try {
var t = __scStr(s);
t = t.replace(/[^a-zA-Z0-9._-]+/g, "_");
if (t.length > 120) t = t.substring(0, 120);
return t;
} catch(e) { return ""; }
}
function __scDrawableToBitmap(drawable, targetPx) {
// 这段代码的主要内容/用途:把任意 Drawable 安全绘制成 Bitmap便于持久化保存 PNG。
try {
if (!drawable) return null;
// BitmapDrawable 直接取
try {
if (drawable instanceof android.graphics.drawable.BitmapDrawable) {
var b = drawable.getBitmap();
if (b) return b;
}
} catch(eBD) {}
var w = 0, h = 0;
try { w = drawable.getIntrinsicWidth(); } catch(eW) { w = 0; }
try { h = drawable.getIntrinsicHeight(); } catch(eH) { h = 0; }
if (w <= 0 || h <= 0) {
w = targetPx || 192;
h = targetPx || 192;
}
// # 过大的直接缩到 targetPx 附近,避免 PNG 体积过大
var maxSide = targetPx || 192;
var side = Math.max(w, h);
var scale = side > maxSide ? (maxSide / side) : 1.0;
var bw = Math.max(1, Math.round(w * scale));
var bh = Math.max(1, Math.round(h * scale));
var bmp = android.graphics.Bitmap.createBitmap(bw, bh, android.graphics.Bitmap.Config.ARGB_8888);
var canvas = new android.graphics.Canvas(bmp);
drawable.setBounds(0, 0, bw, bh);
drawable.draw(canvas);
return bmp;
} catch(eAll) {
return null;
}
}
// # 数据拉取:枚举 userId + packages + LauncherApps.getShortcuts
function __scLoadAllItems() {
// 这段代码的主要内容/用途:尽力枚举系统已知快捷方式,失败则返回空数组(不影响主流程)。
var items = [];
try {
// # userId 来自 /data/system_ce和 shortcuts.js 同源思路)
var users = [];
try {
// # 使用动态获取的系统用户目录(适配不同 ROM
var basePath = "/data/system_ce";
try {
// 优先使用已定义的 getSystemUserDir如果在同一作用域
if (typeof getSystemUserDir === "function") {
basePath = getSystemUserDir();
}
} catch(eGSD) {}
var base = new java.io.File(basePath);
if (base.exists() && base.isDirectory()) {
var arr = base.listFiles();
if (arr) {
for (var i = 0; i < arr.length; i++) {
var f = arr[i];
if (!f || !f.isDirectory()) continue;
var nm = __scStr(f.getName());
if (!nm) continue;
var ok = true;
for (var j = 0; j < nm.length; j++) {
var c = nm.charCodeAt(j);
if (c < 48 || c > 57) { ok = false; break; }
}
if (ok) users.push(parseInt(nm, 10));
}
}
}
} catch(eU0) {}
if (users.length === 0) users.push(0);
// # 修复:与 shortcuts.js 一致,优先使用 shortcut 系统服务直连获取(可见性更全,能覆盖"微信小程序添加到桌面"这类入口)
// # 说明LauncherApps.getShortcuts 在部分 ROM/桌面上返回不全,因此这里先走 IShortcutService.getShortcuts。
var shortcutSvc = null;
try { shortcutSvc = android.os.ServiceManager.getService("shortcut"); } catch(eSvc0) { shortcutSvc = null; }
var CFG_MATCH_ALL = 0x0000000F;
var la = context.getSystemService(android.content.Context.LAUNCHER_APPS_SERVICE);
// # 说明la 作为兜底来源;若 la 也不可用则返回空数组,不影响新增按钮页其它功能。
if (!la && !shortcutSvc) return items;
var ShortcutQuery = android.content.pm.LauncherApps.ShortcutQuery;
var UserHandle = android.os.UserHandle;
// # 逐用户扫描 shortcut_service 的包名列表(快速筛)
for (var ui = 0; ui < users.length; ui++) {
var uid = users[ui];
var pkgs = [];
try {
// # 使用动态获取的系统用户目录
var basePath2 = "/data/system_ce";
try {
if (typeof getSystemUserDir === "function") {
basePath2 = getSystemUserDir();
}
} catch(eGSD3) {}
var dir = new java.io.File(basePath2 + "/" + String(uid) + "/shortcut_service/packages");
if (dir.exists() && dir.isDirectory()) {
var fs = dir.listFiles();
if (fs) {
for (var pi = 0; pi < fs.length; pi++) {
var pf = fs[pi];
if (!pf || !pf.isFile()) continue;
var pn = __scStr(pf.getName());
if (pn && pn.indexOf(".xml") > 0) {
var pkg = pn.substring(0, pn.length - 4);
if (pkg) pkgs.push(pkg);
}
}
}
}
} catch(eP0) {}
for (var p = 0; p < pkgs.length; p++) {
var pkgName = pkgs[p];
if (!pkgName) continue;
// # 优先走 shortcut 服务直连
if (shortcutSvc) {
try {
var slice = null;
try { slice = shortcutSvc.getShortcuts(String(pkgName), CFG_MATCH_ALL, parseInt(String(uid), 10)); } catch(eS0) { slice = null; }
if (slice) {
var listObj = null;
try { listObj = slice.getList(); } catch(eS1) { listObj = null; }
if (listObj) {
try {
var sz = listObj.size();
for (var si0 = 0; si0 < sz; si0++) {
var info0 = null;
try { info0 = listObj.get(si0); } catch(eS2) { info0 = null; }
if (!info0) continue;
var sid0 = "";
var slb0 = "";
try { sid0 = __scStr(info0.getId()); } catch(eId0) { sid0 = ""; }
try { slb0 = __scStr(info0.getShortLabel()); } catch(eLb0) { slb0 = ""; }
// # 取启动 Intent用于 Shell am start 方案,桌面移除后仍可启动)
var iu0 = "";
try {
var it0 = info0.getIntent ? info0.getIntent() : null;
if (it0) {
var s0 = String(it0.toUri(0));
iu0 = (s0 && s0.indexOf("#Intent") === 0) ? ("intent:" + s0) : s0;
}
} catch(eIU0) { iu0 = ""; }
items.push({
userId: uid,
pkg: __scStr(pkgName),
shortcutId: sid0,
label: slb0,
intentUri: iu0,
shortcutInfo: info0
});
// # 安全阈值:避免极端情况下数据量过大导致 UI/内存压力
if (items.length > 5000) return items;
}
} catch(eS3) {}
// # 已拿到则直接处理下一个包,避免重复从 LauncherApps 再取一遍
continue;
}
}
} catch(eSvc1) {
// ignore and fallback to LauncherApps
}
}
// # 兜底:若没有 LauncherApps 服务,则无法走 fallback直接跳过上面 shortcutSvc 已尽力)
if (!la) continue;
var q = new ShortcutQuery();
try { q.setPackage(pkgName); } catch(eQ0) {}
// # QueryFlags尽量全拿逐个 try避免 ROM 缺字段)
try {
var qFlags = 0;
try { qFlags = qFlags | ShortcutQuery.FLAG_MATCH_DYNAMIC; } catch(eF1) {}
try { qFlags = qFlags | ShortcutQuery.FLAG_MATCH_PINNED; } catch(eF2) {}
try { qFlags = qFlags | ShortcutQuery.FLAG_MATCH_MANIFEST; } catch(eF3) {}
try { qFlags = qFlags | ShortcutQuery.FLAG_MATCH_CACHED; } catch(eF4) {}
try { q.setQueryFlags(qFlags); } catch(eF5) {}
} catch(eF0) {}
var uh = null;
try { uh = UserHandle.of(parseInt(String(uid), 10)); } catch(eUH) { uh = null; }
var list = null;
try { list = la.getShortcuts(q, uh ? uh : android.os.Process.myUserHandle()); } catch(eGS) { list = null; }
if (!list) continue;
for (var si = 0; si < list.size(); si++) {
var info = null;
try { info = list.get(si); } catch(eG1) { info = null; }
if (!info) continue;
var sid = "";
var slb = "";
try { sid = __scStr(info.getId()); } catch(eId) { sid = ""; }
try { slb = __scStr(info.getShortLabel()); } catch(eLb) { slb = ""; }
// # 取启动 Intent用于 Shell am start 方案,桌面移除后仍可启动)
var iu = "";
try {
var it1 = info.getIntent ? info.getIntent() : null;
if (it1) {
var s1 = String(it1.toUri(0));
iu = (s1 && s1.indexOf("#Intent") === 0) ? ("intent:" + s1) : s1;
}
} catch(eIU1) { iu = ""; }
items.push({
userId: uid,
pkg: __scStr(pkgName),
shortcutId: sid,
label: slb,
// # 新增:保留 ShortcutInfo用于取快捷方式 icon
shortcutInfo: info
});
// # 安全阈值:避免极端情况下数据量过大导致 UI/内存压力
if (items.length > 5000) return items;
}
}
}
} catch(eAll) {}
return items;
}
// =======================【快捷方式选择列表:渲染去抖】======================
// # 这段代码的主要内容/用途对搜索输入触发的列表刷新做轻量去抖50ms 合并一次),减少频繁 notifyDataSetChanged 与重复过滤带来的卡顿/内存抖动。
// # 注意:只优化刷新时机,不改变任何数据/显示逻辑,确保功能与 UI 观感完全一致。
var __scRenderHandler = null;
var __scRenderRunnable = null;
function __scRenderListNow(query) {
// 这段代码的主要内容/用途:根据搜索关键字刷新 ListView 数据(不再手工 addView确保列表框内可稳定纵向滚动。
try {
var q = __scLower(__scStr(query));
// # 性能优化:相同查询不重复刷新(减少 notifyDataSetChanged 频率)
try {
// # 修复:首次展开时列表可能还未完成首帧渲染,不能因为"查询相同"而跳过 notifyDataSetChanged
// # 说明:避免出现"展开后空白,需要触摸滑动才显示"的现象(功能/UI不变只保证首帧必渲染
var hasRendered = false;
try { hasRendered = !!scInlineState.__scHasRendered; } catch(eHr0) { hasRendered = false; }
if (hasRendered && scInlineState.__scLastQuery === q && scInlineState.__scLastSrcSize === ((scInlineState.allItems || []).length)) {
return;
}
} catch(eSame0) {}
scInlineState.__scLastQuery = q;
try { scInlineState.__scLastSrcSize = (scInlineState.allItems || []).length; } catch(eSz0) { scInlineState.__scLastSrcSize = 0; }
var src = scInlineState.allItems || [];
var out = [];
if (!q) {
for (var i = 0; i < src.length; i++) out.push(src[i]);
} else {
for (var i = 0; i < src.length; i++) {
var it = src[i];
if (!it) continue;
var hit = false;
try {
if (__scLower(it.label).indexOf(q) >= 0) hit = true;
else if (__scLower(it.pkg).indexOf(q) >= 0) hit = true;
else if (__scLower(__scGetAppLabel(it.pkg)).indexOf(q) >= 0) hit = true;
else if (__scLower(it.shortcutId).indexOf(q) >= 0) hit = true;
} catch(eM) {}
if (hit) out.push(it);
// # 性能保护:搜索时最多显示 300 条,避免 system_server 过载
if (out.length >= 300) break;
}
}
// 更新适配器数据
__scData = out;
try { __scAdapter.notifyDataSetChanged(); } catch(eN0) {}
// # 修复:确保首次展开时 ListView 立即刷新布局,不依赖用户触摸触发重绘
try { scInlineState.__scHasRendered = true; } catch(eHr1) {}
try {
scList.post(new java.lang.Runnable({
run: function() {
try { scList.invalidateViews(); } catch(eInv0) {}
try { scList.requestLayout(); } catch(eReq0) {}
}
}));
} catch(ePostInv0) {
try { scList.invalidateViews(); } catch(eInv1) {}
}
// 提示信息
try {
if (out.length === 0) scHint.setText("无匹配结果(共 " + src.length + " 条)");
else scHint.setText("共 " + src.length + " 条,显示 " + out.length + " 条(在框内滑动)");
} catch(eH1) {}
} catch(e0) {
try { scHint.setText("渲染失败: " + e0); } catch(e1) {}
}
}
function __scRenderList(query) {
// # 这段代码的主要内容/用途:渲染去抖入口(合并 50ms 内的多次刷新请求)
try { scInlineState.__scPendingQuery = query; } catch(ePQ0) {}
// # 初始化 Handler主线程
try {
if (!__scRenderHandler) {
__scRenderHandler = new android.os.Handler(android.os.Looper.getMainLooper());
}
} catch(eH0) { __scRenderHandler = null; }
// # 取消上一次未执行的刷新
try {
if (__scRenderHandler && __scRenderRunnable) {
__scRenderHandler.removeCallbacks(__scRenderRunnable);
}
} catch(eRm0) {}
// # 创建新的 runnable始终使用最新 query
__scRenderRunnable = new java.lang.Runnable({
run: function() {
var q0 = "";
try { q0 = scInlineState.__scPendingQuery; } catch(ePQ1) { q0 = ""; }
__scRenderListNow(q0);
}
});
// # 延迟合并50ms输入法连击/快速打字不会重复做过滤与刷新
try {
if (__scRenderHandler) __scRenderHandler.postDelayed(__scRenderRunnable, 50);
else __scRenderListNow(query);
} catch(ePost0) {
__scRenderListNow(query);
}
}
function __scEnsureLoadedAndRender() {
// 这段代码的主要内容/用途:首次展开时异步加载数据,加载完成后渲染;再次展开直接渲染。
if (scInlineState.loading) return;
if (scInlineState.loaded) {
// # 新增:自动刷新策略(不影响原功能)
// 说明:你把"微信小程序添加到桌面"后,旧缓存不会自动出现;这里用时间阈值+手动强刷解决。
var nowTs = 0;
try { nowTs = new Date().getTime(); } catch(eT0) { nowTs = 0; }
var stale = false;
try { stale = (scInlineState.loadedTs > 0) && (nowTs > 0) && ((nowTs - scInlineState.loadedTs) > 8000); } catch(eSt) { stale = false; }
if (!scInlineState.forceReload && !stale) {
// # 优化:展开时立即渲染一次(不走 50ms 去抖),避免出现"展开空白,触摸后才显示"
__scRenderListNow(scSearchWrap.getValue());
return;
}
// 需要刷新:标记为未加载并继续走下面的加载流程
scInlineState.forceReload = false;
scInlineState.loaded = false;
}
scInlineState.loading = true;
try { scHint.setText("正在加载快捷方式列表..."); } catch(eH0) {}
new java.lang.Thread(new java.lang.Runnable({
run: function() {
var arr = [];
try { arr = __scLoadAllItems(); } catch(eL0) { arr = []; }
// 回到 UI 线程:此处运行在 system_server 的 WM/UI 线程不一定有 Looper因此用 viewerPanel 的 handler 托管更稳。
try {
self.runOnUiThreadSafe(function() {
scInlineState.loading = false;
scInlineState.loaded = true;
try { scInlineState.loadedTs = new Date().getTime(); } catch(eTs1) { scInlineState.loadedTs = 0; }
try { scInlineState.loadedTs = new Date().getTime(); } catch(eTs0) { scInlineState.loadedTs = 0; }
scInlineState.allItems = arr || [];
// # 优化:数据加载完成后立即渲染首帧,避免首次展开空白
__scRenderListNow(scSearchWrap.getValue());
});
} catch(eUi) {
// # 兜底:如果 self.runOnUiThreadSafe 不存在/不可用,使用 View.post 回到当前面板的 UI 线程刷新
try {
scInlineState.loading = false;
scInlineState.loaded = true;
scInlineState.allItems = arr || [];
} catch(eUi2) {}
// # 关键修复:首次展开时也要触发渲染,否则会一直停留在"正在加载..."
try {
// 优先用 ListView.post保证在拥有 Looper 的 UI 线程执行
scList.post(new java.lang.Runnable({
run: function() {
try { __scRenderListNow(scSearchWrap.getValue()); } catch(eR0) {}
}
}));
} catch(ePost0) {
// 再兜底:直接调用(若当前线程本就有 Looper 也能工作)
try { __scRenderList(scSearchWrap.getValue()); } catch(eR1) {}
}
}
}
})).start();
}
// # 折叠点击逻辑
scHeader.setClickable(true);
scHeader.setOnClickListener(new android.view.View.OnClickListener({
onClick: function(v) {
try {
scInlineState.expanded = !scInlineState.expanded;
if (scInlineState.expanded) {
scBody.setVisibility(android.view.View.VISIBLE);
// # 优化:展开时主动触发布局与重绘,避免需要触摸滑动才显示列表
try {
scList.post(new java.lang.Runnable({
run: function() {
try { scList.requestLayout(); } catch(eRq0) {}
try { scList.invalidateViews(); } catch(eIv0) {}
}
}));
} catch(ePostRq0) {}
scArrowTv.setText("▲");
__scEnsureLoadedAndRender();
} else {
scBody.setVisibility(android.view.View.GONE);
scArrowTv.setText("▼");
}
} catch(eTg) {}
}
}));
// # 搜索变化即刷新(内联列表最多 120 条,搜索可快速定位)
try {
scSearchWrap.input.addTextChangedListener(new android.text.TextWatcher({
beforeTextChanged: function() {},
onTextChanged: function() {},
afterTextChanged: function(s) {
// 这段代码的主要内容/用途搜索输入做去抖debounce避免每敲一个字就全量过滤导致卡顿与高占用。
try {
if (!scInlineState.loaded) return;
var q = __scStr(s);
// # 取消上一次排队的渲染
try {
if (scInlineState.__scSearchRunnable) {
scList.removeCallbacks(scInlineState.__scSearchRunnable);
}
} catch(eRm) {}
scInlineState.__scSearchRunnable = new java.lang.Runnable({
run: function() {
try {
// # 再次确认当前查询未变化(防抖期间用户继续输入)
__scRenderList(q);
} catch(eRun) {}
}
});
// # 180ms 防抖:既跟手又不抖 CPU
try { scList.postDelayed(scInlineState.__scSearchRunnable, 180); } catch(ePost) { __scRenderList(q); }
} catch(eW) {}
}
}));
} catch(eTw) {}
// # 组装到 shortcutWrap
shortcutWrap.addView(scHeader);
shortcutWrap.addView(scBody);
dynamicContainer.addView(shortcutWrap);
// 联动逻辑
function updateVisibility(typeVal) {
shellWrap.setVisibility(typeVal === "shell" ? android.view.View.VISIBLE : android.view.View.GONE);
appWrap.setVisibility(typeVal === "app" ? android.view.View.VISIBLE : android.view.View.GONE);
bcWrap.setVisibility(typeVal === "broadcast" ? android.view.View.VISIBLE : android.view.View.GONE);
shortcutWrap.setVisibility(typeVal === "shortcut" ? android.view.View.VISIBLE : android.view.View.GONE);
}
// 动作类型初始刷新:首次进入编辑页时立刻根据默认选中项显示对应输入区
applySelectedType(selectedTypeVal);
scroll.addView(form);
var scrollLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0);
scrollLp.weight = 1;
scroll.setLayoutParams(scrollLp);
panel.addView(scroll);
// 底部
var bottomBar = new android.widget.LinearLayout(context);
bottomBar.setOrientation(android.widget.LinearLayout.HORIZONTAL);
bottomBar.setGravity(android.view.Gravity.END | android.view.Gravity.CENTER_VERTICAL);
bottomBar.setPadding(0, self.dp(12), 0, 0);
var btnCancel = self.ui.createFlatButton(self, "取消", subTextColor, function() {
self.state.editingButtonIndex = null;
refreshPanel();
});
bottomBar.addView(btnCancel);
var space = new android.view.View(context);
space.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(12), 1));
bottomBar.addView(space);
var btnSave = self.ui.createSolidButton(self, "暂存修改", C.primary, android.graphics.Color.WHITE, function() {
try {
var newBtn = targetBtn;
newBtn.title = inputTitle.getValue();
// # 根据选中的图标类型保存对应的值(二选一)
var iconTypeSelected = rbIconShortX.isChecked() ? "shortx" : "file";
if (iconTypeSelected === "file") {
var ip = inputIconPath.getValue();
if (ip) newBtn.iconPath = ip; else delete newBtn.iconPath;
delete newBtn.iconResName; // 清除 ShortX 图标
} else {
var sxIcon = self.normalizeShortXIconName(currentShortXIconName, false);
if (sxIcon) newBtn.iconResName = sxIcon; else delete newBtn.iconResName;
// 保存 ShortX 图标颜色
var sxTint = inputShortXIconTint.getValue();
if (sxTint && sxTint.length > 0) newBtn.iconTint = sxTint; else delete newBtn.iconTint;
delete newBtn.iconPath; // 清除文件路径
}
newBtn.type = selectedTypeVal || "shell";
// 清理旧数据
delete newBtn.cmd; delete newBtn.cmd_b64; delete newBtn.root;
delete newBtn.pkg;
delete newBtn.action; delete newBtn.extras; delete newBtn.extra;
delete newBtn.uri;
delete newBtn.shortcutId;
delete newBtn.launchUserId;
var isValid = true;
if (newBtn.type === "shell") {
var c = inputShell.getValue();
if (!c) { inputShell.setError("请输入命令"); isValid=false; }
else { inputShell.setError(null); newBtn.cmd = c; newBtn.cmd_b64 = encodeBase64Utf8(c); newBtn.root = true; }
} else if (newBtn.type === "app") {
var p = inputPkg.getValue();
if (!p) { inputPkg.setError("请输入包名"); isValid=false; }
else { inputPkg.setError(null); newBtn.pkg = p; }// # 保存启动用户ID可选
try {
var au = inputAppLaunchUser.getValue();
au = (au != null) ? String(au).trim() : "";
if (au && au.length > 0) {
var aui = parseInt(au, 10);
if (!isNaN(aui)) newBtn.launchUserId = aui;
}
} catch(eAU) {}
} else if (newBtn.type === "broadcast") {
var a = inputAction.getValue();
if (!a) { inputAction.setError("请输入 Action"); isValid=false; }
else { inputAction.setError(null); newBtn.action = a; }
var ex = inputExtras.getValue();
if (ex) {
try { newBtn.extras = JSON.parse(ex); inputExtras.setError(null); }
catch(e) { inputExtras.setError("JSON 格式错误"); isValid=false; }
}
} else if (newBtn.type === "shortcut") {
var sp = inputScPkg.getValue();
var sid = inputScId.getValue();
if (!sp) { inputScPkg.setError("请输入包名"); isValid=false; }
else { inputScPkg.setError(null); newBtn.pkg = sp; }
if (!sid) { inputScId.setError("请输入 shortcutId"); isValid=false; }
else { inputScId.setError(null); newBtn.shortcutId = sid; }
// # 保存:同时保存 intentUri/userId供 JavaScript(startActivityAsUser) 脚本使用(锁定主/分身)
try { if (scSelectedIntentUri && scSelectedIntentUri.length > 0) newBtn.intentUri = String(scSelectedIntentUri); } catch(eSIU2) {}
try { newBtn.userId = scSelectedUserId; } catch(eSUID2) { newBtn.userId = 0; }
// # 保存:快捷方式 JS 启动代码(自动生成/可手动编辑)
try { if (inputScJsCode) newBtn.shortcutJsCode = String(inputScJsCode.getValue()); } catch(eSaveJs) {}
}
// # 保存:快捷方式仅使用 JavaScript 执行(取消 Shell/兜底)
newBtn.shortcutRunMode = "js";
if (!isValid) return;
if (editIdx === -1) {
buttons.push(newBtn);
} else {
buttons[editIdx] = newBtn;
}
self.state.editingButtonIndex = null;
refreshPanel();
self.toast("已暂存,请在列表页点击保存");
} catch (e) {
self.toast("暂存失败: " + e);
}
});
bottomBar.addView(btnSave);
panel.addView(bottomBar);
}
return panel;
};
// =======================【Schema 编辑面板】======================
FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() {
var self = this;
if (this.state.editingSchemaIndex === undefined) {
this.state.editingSchemaIndex = null;
}
if (!this.state.keepSchemaEditorState || !this.state.tempSchema) {
var current = ConfigManager.loadSchema();
this.state.tempSchema = JSON.parse(JSON.stringify(current));
}
this.state.keepSchemaEditorState = false;
var schema = this.state.tempSchema;
var isEditing = (this.state.editingSchemaIndex !== null);
var isDark = this.isDarkTheme();
var C = this.ui.colors;
var bgColor = isDark ? C.bgDark : C.bgLight;
var cardColor = isDark ? C.cardDark : C.cardLight;
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 panel = this.ui.createStyledPanel(this, 16);
// --- Header ---
var header = this.ui.createStyledHeader(this, 12);
// Title removed to avoid redundancy with Wrapper Title
// var titleTv = new android.widget.TextView(context);
// titleTv.setText(isEditing ? (this.state.editingSchemaIndex === -1 ? "新增项" : "编辑项") : "设置布局管理");
// ...
// header.addView(titleTv);
// Placeholder to push buttons to right
header.addView(this.ui.createSpacer(this));
function refreshPanel() {
self.state.keepSchemaEditorState = true;
self.showPanelAvoidBall("schema_editor");
}
if (!isEditing) {
// List Mode
header.addView(self.ui.createFlatButton(self, "重置", C.danger, function() {
ConfigManager.resetSchema();
self.state.tempSchema = null;
self.toast("已重置为默认布局");
refreshPanel();
}));
header.addView(self.ui.createFlatButton(self, "新增", C.primary, function() {
self.state.editingSchemaIndex = -1;
refreshPanel();
}));
var btnClose = self.ui.createFlatButton(self, "✕", C.textSecLight, function() {
self.state.tempSchema = null;
self.hideAllPanels();
});
header.addView(btnClose);
panel.addView(header);
panel.setTag(header); // 暴露 Header
// Save Button
var btnSaveAll = self.ui.createSolidButton(self, "保存生效", C.primary, android.graphics.Color.WHITE, function() {
ConfigManager.saveSchema(schema);
self.state.tempSchema = null;
self.toast("布局已保存");
self.hideAllPanels();
self.showPanelAvoidBall("settings");
});
var saveLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
saveLp.setMargins(0, 0, 0, self.dp(8));
btnSaveAll.setLayoutParams(saveLp);
panel.addView(btnSaveAll);
// List
var scroll = new android.widget.ScrollView(context);
try { scroll.setVerticalScrollBarEnabled(false); } catch(e){}
var list = new android.widget.LinearLayout(context);
list.setOrientation(android.widget.LinearLayout.VERTICAL);
list.setPadding(0, self.dp(4), 0, self.dp(4));
for (var i = 0; i < schema.length; i++) {
(function(idx) {
var item = schema[idx];
var card = new android.widget.LinearLayout(context);
card.setOrientation(android.widget.LinearLayout.HORIZONTAL);
card.setGravity(android.view.Gravity.CENTER_VERTICAL);
card.setBackground(self.ui.createRoundDrawable(cardColor, self.dp(8)));
try { card.setElevation(self.dp(2)); } catch(e){}
var cardLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
cardLp.setMargins(self.dp(2), self.dp(4), self.dp(2), self.dp(4));
card.setLayoutParams(cardLp);
card.setPadding(self.dp(12), self.dp(12), self.dp(8), self.dp(12));
// Info
var infoBox = new android.widget.LinearLayout(context);
infoBox.setOrientation(android.widget.LinearLayout.VERTICAL);
var infoLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
infoLp.weight = 1;
infoBox.setLayoutParams(infoLp);
var nameTv = new android.widget.TextView(context);
nameTv.setText(String(item.name || item.key || "未命名"));
nameTv.setTextColor(textColor);
nameTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
if (item.type === "section") nameTv.setTypeface(null, android.graphics.Typeface.BOLD);
infoBox.addView(nameTv);
var typeTv = new android.widget.TextView(context);
var typeTxt = item.type;
if (item.type !== "section") typeTxt += " (" + (item.key || "?") + ")";
typeTv.setText(String(typeTxt));
typeTv.setTextColor(subTextColor);
typeTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
infoBox.addView(typeTv);
card.addView(infoBox);
// Actions
var actions = new android.widget.LinearLayout(context);
actions.setOrientation(android.widget.LinearLayout.HORIZONTAL);
if (idx > 0) {
actions.addView(self.ui.createFlatButton(self, "↑", subTextColor, function() {
var temp = schema[idx];
schema[idx] = schema[idx - 1];
schema[idx - 1] = temp;
refreshPanel();
}));
}
if (idx < schema.length - 1) {
actions.addView(self.ui.createFlatButton(self, "↓", subTextColor, function() {
var temp = schema[idx];
schema[idx] = schema[idx + 1];
schema[idx + 1] = temp;
refreshPanel();
}));
}
actions.addView(self.ui.createFlatButton(self, "✎", C.primary, function() {
self.state.editingSchemaIndex = idx;
refreshPanel();
}));
actions.addView(self.ui.createFlatButton(self, "✕", C.danger, function() {
schema.splice(idx, 1);
refreshPanel();
}));
card.addView(actions);
list.addView(card);
})(i);
}
scroll.addView(list);
panel.addView(scroll);
} else {
// Edit Mode
var editIdx = this.state.editingSchemaIndex;
var editItem = (editIdx === -1) ? { type: "bool", name: "", key: "" } : JSON.parse(JSON.stringify(schema[editIdx]));
var btnBack = self.ui.createFlatButton(self, "返回", C.textSecLight, function() {
self.state.editingSchemaIndex = null;
refreshPanel();
});
header.addView(btnBack);
panel.addView(header);
panel.setTag(header); // 暴露 Header
var scroll = new android.widget.ScrollView(context);
var form = new android.widget.LinearLayout(context);
form.setOrientation(android.widget.LinearLayout.VERTICAL);
scroll.addView(form);
function createInput(label, key, inputType, hint) {
var box = new android.widget.LinearLayout(context);
box.setOrientation(android.widget.LinearLayout.VERTICAL);
box.setPadding(0, 0, 0, self.dp(12));
var lb = new android.widget.TextView(context);
lb.setText(label);
lb.setTextColor(subTextColor);
lb.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
box.addView(lb);
var et = new android.widget.EditText(context);
et.setText(String(editItem[key] !== undefined ? editItem[key] : ""));
et.setTextColor(textColor);
et.setHint(hint || "");
et.setHintTextColor(self.withAlpha(subTextColor, 0.5));
et.setBackground(self.ui.createRoundDrawable(inputBgColor, self.dp(8)));
et.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8));
if (inputType) et.setInputType(inputType);
box.addView(et);
form.addView(box);
return {
getValue: function() { return String(et.getText()); },
getView: function() { return box; }
};
}
// Type Selector
var typeBox = new android.widget.LinearLayout(context);
typeBox.setOrientation(android.widget.LinearLayout.HORIZONTAL);
typeBox.setGravity(android.view.Gravity.CENTER_VERTICAL);
typeBox.setPadding(0, 0, 0, self.dp(12));
var typeLb = new android.widget.TextView(context);
typeLb.setText("类型: ");
typeLb.setTextColor(textColor);
typeBox.addView(typeLb);
var types = ["section", "bool", "int", "float", "text", "action"];
var typeBtn = self.ui.createSolidButton(self, editItem.type, C.accent, android.graphics.Color.WHITE, function(v) {
var currIdx = types.indexOf(editItem.type);
var nextIdx = (currIdx + 1) % types.length;
editItem.type = types[nextIdx];
refreshPanel();
});
typeBox.addView(typeBtn);
form.addView(typeBox);
var inputName = createInput("显示名称 (Name)", "name", android.text.InputType.TYPE_CLASS_TEXT, "例如:悬浮球大小");
var inputKey = null;
var inputMin = null;
var inputMax = null;
var inputStep = null;
var inputAction = null;
if (editItem.type !== "section") {
inputKey = createInput("配置键名 (Key)", "key", android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS, "例如BALL_SIZE_DP");
}
if (editItem.type === "int" || editItem.type === "float") {
inputMin = createInput("最小值 (Min)", "min", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED);
inputMax = createInput("最大值 (Max)", "max", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED);
inputStep = createInput("步进 (Step)", "step", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL);
}
if (editItem.type === "action") {
inputAction = createInput("动作ID (Action)", "action", android.text.InputType.TYPE_CLASS_TEXT, "例如open_btn_mgr");
}
panel.addView(scroll);
// Save Button
var btnSave = self.ui.createSolidButton(self, "暂存", C.primary, android.graphics.Color.WHITE, function() {
try {
editItem.name = inputName.getValue();
if (inputKey) editItem.key = inputKey.getValue();
if (inputMin) editItem.min = Number(inputMin.getValue());
if (inputMax) editItem.max = Number(inputMax.getValue());
if (inputStep) editItem.step = Number(inputStep.getValue());
if (inputAction) editItem.action = inputAction.getValue();
// Clean up properties based on type
if (editItem.type === "section") { delete editItem.key; delete editItem.min; delete editItem.max; delete editItem.step; delete editItem.action; }
else if (editItem.type === "bool") { delete editItem.min; delete editItem.max; delete editItem.step; delete editItem.action; }
else if (editItem.type === "int" || editItem.type === "float") { delete editItem.action; }
else if (editItem.type === "action") { delete editItem.min; delete editItem.max; delete editItem.step; }
if (editIdx === -1) {
schema.push(editItem);
} else {
schema[editIdx] = editItem;
}
self.state.editingSchemaIndex = null;
refreshPanel();
self.toast("已暂存,请在列表页点击保存生效");
} catch (e) {
self.toast("暂存失败: " + e);
}
});
var bottomBar = new android.widget.LinearLayout(context);
bottomBar.setOrientation(android.widget.LinearLayout.VERTICAL);
bottomBar.setPadding(0, self.dp(8), 0, 0);
bottomBar.addView(btnSave);
panel.addView(bottomBar);
}
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 isDark = this.isDarkTheme();
var C = this.ui.colors;
var wm = this.state.wm;
var root = new android.widget.FrameLayout(context);
root.setBackgroundColor(self.withAlpha(isDark ? 0xFF000000 : 0xFFFFFFFF, 0.55));
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.createRoundDrawable(isDark ? C.cardDark : C.cardLight, self.dp(16)));
card.setPadding(self.dp(12), self.dp(12), self.dp(12), self.dp(12));
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(isDark ? C.textPriDark : C.textPriLight);
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, "关闭", C.primary, function() {
closePopup();
});
header.addView(closeBtn);
card.addView(header);
var content = new android.widget.LinearLayout(context);
content.setOrientation(android.widget.LinearLayout.VERTICAL);
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 = 0.5;
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_RESIZE;
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) {}
if (typeof onDismiss === "function") {
try { onDismiss(); } catch(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 onDismiss = (typeof opt.onDismiss === "function") ? opt.onDismiss : null;
var isDark = this.isDarkTheme();
var C = this.ui.colors;
var textColor = isDark ? C.textPriDark : C.textPriLight;
var subTextColor = isDark ? C.textSecDark : C.textSecLight;
var catalog = [];
try { catalog = self.getShortXIconCatalog() || []; } catch(e) {}
if (!catalog.length) {
var errMsg = "\u56fe\u6807\u5e93\u672a\u52a0\u8f7d";
try { errMsg = self._shortxIconCatalogError || errMsg; } catch(e) {}
self.toast("\u56fe\u6807\u5e93\u672a\u52a0\u8f7d: " + errMsg);
// 尝试强制重新加载
try {
catalog = self.getShortXIconCatalog(true) || [];
if (!catalog.length) {
try { errMsg = self._shortxIconCatalogError || "\u91cd\u8bd5\u4ecd\u5931\u8d25"; } catch(e) {}
self.toast("\u56fe\u6807\u5e93\u91cd\u8bd5\u5931\u8d25: " + errMsg);
}
} catch(eRetry) {}
}
// 如\u679c\u8fd8\u662f\u7a7a\u663e\u793a\u8bca\u65ad\u4fe1\u606f
if (!catalog.length) {
var diagPaths = [];
try { diagPaths = self.getShortXApkPaths() || []; } catch(e) {}
var diagInfo = "\u5305\u540d: " + CONST_SHORTX_PACKAGE + "\nAPK\u8def\u5f84: " + (diagPaths.join(", ") || "\u65e0") + "\n\u9519\u8bef: " + (self._shortxIconCatalogError || "\u672a\u77e5");
// 备\u7528\u56fe\u6807\u5217\u8868\u5f53\u6240\u6709\u81ea\u52a8\u83b7\u53d6\u5931\u8d25\u65f6\uff0c\u81f3\u5c11\u663e\u793a\u4e00\u4e9b\u5e38\u7528\u56fe\u6807\uff09
catalog = [
{name: "ic_remix_home", shortName: "home"},
{name: "ic_remix_settings", shortName: "settings"},
{name: "ic_remix_share", shortName: "share"},
{name: "ic_remix_save", shortName: "save"},
{name: "ic_remix_delete", shortName: "delete"},
{name: "ic_remix_edit", shortName: "edit"},
{name: "ic_remix_search", shortName: "search"},
{name: "ic_remix_refresh", shortName: "refresh"},
{name: "ic_remix_add", shortName: "add"},
{name: "ic_remix_close", shortName: "close"},
{name: "ic_remix_play", shortName: "play"},
{name: "ic_remix_pause", shortName: "pause"},
{name: "ic_remix_camera", shortName: "camera"},
{name: "ic_remix_image", shortName: "image"},
{name: "ic_remix_folder", shortName: "folder"},
{name: "ic_remix_file", shortName: "file"},
{name: "ic_remix_link", shortName: "link"},
{name: "ic_remix_bluetooth", shortName: "bluetooth"},
{name: "ic_remix_wifi", shortName: "wifi"},
{name: "ic_remix_flashlight", shortName: "flashlight"}
];
}
var selectedName = currentName;
var popupState = {
currentPage: 0,
activeTab: "all",
pageSize: 0,
pageCols: 0,
pageRows: 0,
cellMinWidthDp: 72,
cellWidthPx: 0,
cellHeightDp: 92,
cellMarginDp: 4,
lastMeasuredGridWidth: 0,
lastMeasuredGridHeight: 0,
lastQuery: ""
};
var tabDefs = [
{ key: "all", label: "全部" },
{ key: "system", label: "系统" },
{ key: "common", label: "常用" },
{ key: "media", label: "媒体" },
{ key: "comm", label: "通讯" },
{ key: "device", label: "设备" },
{ key: "action", label: "动作" }
];
function filterCatalog(q, tab) {
var qLower = String(q || "").toLowerCase();
var out = [];
var i;
for (i = 0; i < catalog.length; i++) {
var entry = catalog[i];
if (!entry) continue;
if (qLower) {
var n = String(entry.shortName || entry.name).toLowerCase();
if (n.indexOf(qLower) < 0) continue;
}
if (tab && tab !== "all") {
var t = String(entry.category || "all").toLowerCase();
if (t !== tab) continue;
}
out.push(entry);
}
return out;
}
function resolvePageSize(gridWidth, gridHeight) {
var rawWidth = 0, rawHeight = 0;
try { rawWidth = Number(gridWidth || 0); } catch(e) {}
try { rawHeight = Number(gridHeight || 0); } catch(e) {}
if (rawWidth <= 0) rawWidth = self.dp(300);
if (rawHeight <= 0) rawHeight = self.dp(360);
var marginPx = self.dp(Number(popupState.cellMarginDp || 4));
var minCellWidthPx = self.dp(Number(popupState.cellMinWidthDp || 72));
var availW = rawWidth - marginPx * 2;
var cols = Math.max(1, Math.floor(availW / (minCellWidthPx + marginPx * 2)));
var cellWidthPx = Math.floor((availW - (cols - 1) * marginPx) / cols);
var availH = rawHeight - marginPx * 2;
var cellOuterHeight = self.dp(Number(popupState.cellHeightDp || 92)) + marginPx * 2;
var rows = Math.max(1, Math.floor(availH / cellOuterHeight));
var size = cols * rows;
popupState.pageCols = cols;
popupState.pageRows = rows;
popupState.cellWidthPx = cellWidthPx;
popupState.pageSize = size;
popupState.lastMeasuredGridWidth = rawWidth;
popupState.lastMeasuredGridHeight = rawHeight;
return size;
}
var popupResult = self.showPopupOverlay({
title: "选择 ShortX 图标",
onDismiss: onDismiss,
builder: function(content, closePopup) {
var searchEt = new android.widget.EditText(context);
searchEt.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
searchEt.setTextColor(textColor);
try { searchEt.setHintTextColor(subTextColor); } catch(e) {}
searchEt.setHint("搜索图标名,如 share / home / save");
searchEt.setSingleLine(true);
searchEt.setPadding(self.dp(10), self.dp(8), self.dp(10), self.dp(8));
searchEt.setBackground(self.ui.createStrokeDrawable(isDark ? self.ui.colors.inputBgDark : self.ui.colors.inputBgLight, isDark ? self.ui.colors.dividerDark : self.ui.colors.dividerLight, self.dp(1), self.dp(10)));
content.addView(searchEt);
var tabsScroll = new android.widget.HorizontalScrollView(context);
tabsScroll.setHorizontalScrollBarEnabled(false);
var tabsRow = new android.widget.LinearLayout(context);
tabsRow.setOrientation(android.widget.LinearLayout.HORIZONTAL);
tabsRow.setPadding(self.dp(8), self.dp(8), self.dp(8), self.dp(8));
tabsScroll.addView(tabsRow);
content.addView(tabsScroll);
var tabButtons = {};
var ti;
for (ti = 0; ti < tabDefs.length; ti++) {
(function(def) {
var btn = new android.widget.TextView(context);
btn.setText(String(def.label));
btn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
btn.setPadding(self.dp(12), self.dp(6), self.dp(12), self.dp(6));
var btnLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
btnLp.setMargins(0, 0, self.dp(6), 0);
btn.setLayoutParams(btnLp);
btn.setClickable(true);
btn.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
popupState.activeTab = def.key;
popupState.currentPage = 0;
refreshTabs();
renderGrid();
}
}));
tabsRow.addView(btn);
tabButtons[def.key] = btn;
})(tabDefs[ti]);
}
function refreshTabs() {
var k;
for (k in tabButtons) {
if (!tabButtons.hasOwnProperty(k)) continue;
var btn = tabButtons[k];
var active = popupState.activeTab === k;
btn.setTextColor(active ? C.primary : subTextColor);
try {
btn.setBackground(active ? self.ui.createRoundDrawable(self.withAlpha(C.primary, 0.15), self.dp(16)) : null);
} catch(e) {}
}
}
refreshTabs();
var statusTv = new android.widget.TextView(context);
statusTv.setTextColor(subTextColor);
statusTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11);
statusTv.setPadding(self.dp(8), 0, self.dp(8), self.dp(6));
content.addView(statusTv);
var pageBar = new android.widget.LinearLayout(context);
pageBar.setOrientation(android.widget.LinearLayout.HORIZONTAL);
pageBar.setGravity(android.view.Gravity.CENTER_VERTICAL);
pageBar.setPadding(self.dp(8), 0, self.dp(8), self.dp(6));
var btnPrev = self.ui.createFlatButton(self, "上一页", subTextColor, function() {
if (popupState.currentPage > 0) {
popupState.currentPage--;
renderGrid();
}
});
pageBar.addView(btnPrev);
var pageInfo = new android.widget.TextView(context);
pageInfo.setTextColor(textColor);
pageInfo.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12);
pageInfo.setGravity(android.view.Gravity.CENTER);
pageInfo.setPadding(self.dp(8), 0, self.dp(8), 0);
var pageInfoLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1);
pageInfo.setLayoutParams(pageInfoLp);
pageBar.addView(pageInfo);
var btnNext = self.ui.createFlatButton(self, "下一页", C.primary, function() {
popupState.currentPage++;
renderGrid();
});
pageBar.addView(btnNext);
content.addView(pageBar);
var gridScroll = new android.widget.ScrollView(context);
var gridScrollLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0);
gridScrollLp.weight = 1;
gridScroll.setLayoutParams(gridScrollLp);
content.addView(gridScroll);
var grid = new android.widget.GridLayout(context);
grid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(4));
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(8), self.dp(8), self.dp(8), 0);
var selectPreview = new android.widget.ImageView(context);
selectPreview.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(28), self.dp(28)));
selectPreview.setScaleType(android.widget.ImageView.ScaleType.FIT_CENTER);
selectRow.addView(selectPreview);
var selectNameTv = new android.widget.TextView(context);
selectNameTv.setTextColor(textColor);
selectNameTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 13);
selectNameTv.setPadding(self.dp(8), 0, 0, 0);
selectRow.addView(selectNameTv);
var selectConfirm = self.ui.createSolidButton(self, "确定", C.primary, android.graphics.Color.WHITE, function() {
self.touchActivity();
if (typeof onSelect === "function") onSelect(selectedName);
closePopup();
});
selectRow.addView(selectConfirm);
content.addView(selectRow);
function updateSelectPreview() {
try {
if (selectedName) {
selectNameTv.setText(selectedName);
var dr = null;
try { dr = self.getShortXIconDrawable(selectedName); } catch(e) {}
if (dr) selectPreview.setImageDrawable(dr);
else selectPreview.setImageDrawable(null);
} else {
selectNameTv.setText("未选择");
selectPreview.setImageDrawable(null);
}
} catch(e) {}
}
updateSelectPreview();
function renderGrid() {
try {
grid.removeAllViews();
var q = String(searchEt.getText() || "");
var matched = filterCatalog(q, popupState.activeTab);
var size = popupState.pageSize;
if (size <= 0) {
grid.post(new java.lang.Runnable({ run: function() {
resolvePageSize(grid.getWidth(), grid.getHeight());
renderGrid();
}}));
statusTv.setText("正在计算布局...");
return;
}
var totalPages = Math.max(1, Math.ceil(matched.length / size));
if (popupState.currentPage >= totalPages) popupState.currentPage = totalPages - 1;
if (popupState.currentPage < 0) popupState.currentPage = 0;
var start = popupState.currentPage * size;
var pageItems = matched.slice(start, start + size);
statusTv.setText("共 " + matched.length + " 个,第 " + (popupState.currentPage + 1) + "/" + totalPages + " 页,每页 " + size + " 个");
pageInfo.setText((popupState.currentPage + 1) + " / " + totalPages);
if (pageItems.length === 0) {
var emptyTv = new android.widget.TextView(context);
emptyTv.setText("未找到匹配的图标");
emptyTv.setTextColor(subTextColor);
emptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14);
emptyTv.setGravity(android.view.Gravity.CENTER);
emptyTv.setPadding(0, self.dp(40), 0, self.dp(40));
grid.addView(emptyTv);
return;
}
var cols = popupState.pageCols;
grid.setColumnCount(cols);
var idx;
for (idx = 0; idx < pageItems.length; idx++) {
(function(item) {
var cell = new android.widget.LinearLayout(context);
cell.setOrientation(android.widget.LinearLayout.VERTICAL);
cell.setGravity(android.view.Gravity.CENTER_HORIZONTAL);
cell.setPadding(self.dp(4), self.dp(6), self.dp(4), self.dp(6));
cell.setClickable(true);
cell.setBackground(self.ui.createRipple(C.primary, self.dp(10)));
var iv = new android.widget.ImageView(context);
iv.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(28), self.dp(28)));
iv.setScaleType(android.widget.ImageView.ScaleType.FIT_CENTER);
try {
var dr = self.getShortXIconDrawable(item.name);
if (dr) {
iv.setImageDrawable(dr);
} else {
// 图\u6807\u83b7\u53d6\u5931\u8d25\u663e\u793a\u6587\u5b57\u5360\u4f4d\u7b26
try {
var placeholder = new android.graphics.drawable.GradientDrawable();
placeholder.setColor(self.withAlpha(subTextColor, 0.1));
placeholder.setCornerRadius(self.dp(6));
iv.setBackground(placeholder);
} catch(e) {}
}
} catch(e) {}
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, 10);
tv.setGravity(android.view.Gravity.CENTER);
tv.setMaxLines(1);
try { tv.setEllipsize(android.text.TextUtils.TruncateAt.END); } catch(e) {}
tv.setPadding(self.dp(2), self.dp(2), self.dp(2), 0);
cell.addView(tv);
if (selectedName === item.name) {
try { cell.setBackground(self.ui.createRoundDrawable(self.withAlpha(C.primary, 0.2), self.dp(10))); } catch(e) {}
try { tv.setTextColor(C.primary); } catch(e) {}
} else {
try { tv.setTextColor(subTextColor); } catch(e) {}
}
cell.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
selectedName = item.name;
updateSelectPreview();
renderGrid();
}
}));
var cellLp = new android.widget.GridLayout.LayoutParams();
cellLp.width = popupState.cellWidthPx || self.dp(72);
cellLp.height = android.widget.GridLayout.LayoutParams.WRAP_CONTENT;
cell.setLayoutParams(cellLp);
grid.addView(cell);
})(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() {}
}));
grid.post(new java.lang.Runnable({
run: function() {
try {
var w = grid.getWidth();
var h = grid.getHeight();
if (w > 0 && h > 0) {
resolvePageSize(w, h);
renderGrid();
} else {
grid.postDelayed(new java.lang.Runnable({ run: function() {
resolvePageSize(grid.getWidth(), grid.getHeight());
renderGrid();
}}), 100);
}
} catch(ePost) { safeLog(self.L, 'e', "iconPicker post err=" + String(ePost)); }
}
}));
}
});
return popupResult;
};
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 isDark = this.isDarkTheme();
var C = this.ui.colors;
var textColor = isDark ? C.textPriDark : C.textPriLight;
var subTextColor = 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) {}
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 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(8), self.dp(12), self.dp(8));
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) {}
}
if (dr) {
if (!isFollowTheme && selectedColor) {
try {
var parsed = android.graphics.Color.parseColor(selectedColor);
dr.setColorFilter(parsed, android.graphics.PorterDuff.Mode.SRC_IN);
} catch(e) {}
} else {
try { dr.clearColorFilter(); } catch(e) {}
}
previewIv.setImageDrawable(dr);
} else {
previewIv.setImageDrawable(null);
}
} catch(e) {}
}
updatePreview();
// 21 色快捷网格
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);
cell.setLayoutParams(new android.widget.GridLayout.LayoutParams(self.dp(32), self.dp(32)));
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) {}
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) {}
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), C.primary);
cell.setForeground(border);
} catch(e) {}
}
cell.setOnClickListener(new android.view.View.OnClickListener({
onClick: function() {
self.touchActivity();
isFollowTheme = false;
selectedColor = hex;
currentBaseRgbHex = extractTintRgbHex(hex);
currentAlphaByte = extractTintAlphaByte(hex);
updatePreview();
updateValueTv();
refreshCommonGrid();
}
}));
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) {}
}
// 找到匹配的子项设置边框
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), C.primary);
matchedCell.setForeground(border);
} catch(e) {}
}
}
} catch(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 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);
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();
refreshCommonGrid();
},
onStartTrackingTouch: function() {},
onStopTrackingTouch: function() {}
}));
content.addView(row);
})(ri);
}
// 初始化 RGB 滑块值
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);
} catch(e) {}
// 透明度滑块
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();
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, "清空", subTextColor, function() {
self.touchActivity();
isFollowTheme = true;
selectedColor = "";
updatePreview();
updateValueTv();
refreshCommonGrid();
});
actionRow.addView(btnClear);
var btnOk = self.ui.createSolidButton(self, "确定", C.primary, android.graphics.Color.WHITE, function() {
self.touchActivity();
var finalColor = isFollowTheme ? "" : selectedColor;
if (typeof onSelect === "function") onSelect(finalColor);
closePopup();
});
actionRow.addView(btnOk);
content.addView(actionRow);
}
});
return popupResult;
};
// =======================【查看器面板UI】======================