Files
ShortX_ToolHub/code/th_05_persistence.js
Hermes f2c80112c1 feat(settings): 设置面板改造,悬浮球图标支持 ShortX 选择器与调色板
- 从设置页面移除“执行与查看器”、“悬浮球文字/大小/颜色”设置项
- BALL_ICON_RES_NAME 改为 ball_shortx_icon 类型,支持弹窗列表+搜索选择 ShortX 图标
- BALL_ICON_TINT_HEX 改为 ball_color 类型,支持调色板选色+手动输入
- 移除无用的 BALL_ICON_PKG 通用文本输入(仅保留 file 路径输入作为兜底)
- 同步更新 README Changelog
2026-04-20 16:10:17 +08:00

299 lines
10 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.savePanelState = function(key, state) {
if (!key || !state) return;
try {
if (!this.config.PANEL_STATES) this.config.PANEL_STATES = {};
this.config.PANEL_STATES[key] = state;
// 节流或立即保存? 面板拖动结束通常不频繁,立即保存即可
// 但为了避免连续事件,还是可以复用 savePos 的节流逻辑,或者直接保存
ConfigManager.saveSettings(this.config);
} catch (e) {}
};
FloatBallAppWM.prototype.loadPanelState = function(key) {
if (!key || !this.config.PANEL_STATES) return null;
return this.config.PANEL_STATES[key];
};
// =======================【工具:位置持久化】======================
FloatBallAppWM.prototype.savePos = function(x, y) {
try {
this.config.BALL_INIT_X = Math.floor(x);
this.config.BALL_INIT_Y_DP = Math.floor(y / this.state.density);
// # 节流保存
return ConfigManager.saveSettings(this.config);
} catch (e) { return false; }
};
FloatBallAppWM.prototype.loadSavedPos = function() {
// # 直接从 config 返回,因为 config 已经是持久化的
var x = Number(this.config.BALL_INIT_X || 0);
var y = this.dp(Number(this.config.BALL_INIT_Y_DP || 100));
return { x: x, y: y };
};
FloatBallAppWM.prototype.trySavePosThrottled = function(x, y) {
var t = this.now();
if (t - this.state.lastSaveTs < this.config.SAVE_THROTTLE_MS) return false;
this.state.lastSaveTs = t;
return this.savePos(x, y);
};
// =======================【工具:配置持久化】======================
FloatBallAppWM.prototype.saveConfig = function(obj) {
try {
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
this.config[k] = obj[k];
}
}
if (this.L) this.L.updateConfig(this.config);
return ConfigManager.saveSettings(this.config);
} catch (e) { return false; }
};
// =======================【设置面板schema】======================
FloatBallAppWM.prototype.getConfigSchema = function() {
return ConfigManager.loadSchema();
};
// =======================【设置面板:临时编辑缓存】======================
FloatBallAppWM.prototype.beginEditConfig = function() {
try {
var schema = this.getConfigSchema();
var p = {};
var i;
for (i = 0; i < schema.length; i++) {
if (!schema[i] || !schema[i].key) continue;
var k = String(schema[i].key);
p[k] = this.config[k];
}
this.state.pendingUserCfg = p;
this.state.pendingDirty = false;
return true;
} catch (e0) {
this.state.pendingUserCfg = null;
this.state.pendingDirty = false;
return false;
}
};
FloatBallAppWM.prototype.getPendingValue = function(k) {
if (this.state.pendingUserCfg && this.state.pendingUserCfg.hasOwnProperty(k)) return this.state.pendingUserCfg[k];
return this.config[k];
};
FloatBallAppWM.prototype.setPendingValue = function(k, v) {
if (!this.state.pendingUserCfg) this.beginEditConfig();
this.state.pendingUserCfg[k] = v;
this.state.pendingDirty = true;
if (this.state.previewMode) {
this.refreshPreview(k);
}
};
FloatBallAppWM.prototype.getEffectiveConfig = function() {
if (!this.state.previewMode || !this.state.pendingUserCfg) return this.config;
var cfg = {};
for (var k in this.config) { cfg[k] = this.config[k]; }
for (var k in this.state.pendingUserCfg) { cfg[k] = this.state.pendingUserCfg[k]; }
return cfg;
};
FloatBallAppWM.prototype.refreshPreview = function(changedKey) {
if (this.state.closing) return;
var self = this;
// Post to next tick to avoid destroying view during event dispatch (fixes crash on switch toggle)
if (this.state.h) {
this.state.h.post(new JavaAdapter(java.lang.Runnable, {
run: function() { self._refreshPreviewInternal(changedKey); }
}));
} else {
self._refreshPreviewInternal(changedKey);
}
};
FloatBallAppWM.prototype._refreshPreviewInternal = function(changedKey) {
if (this.state.closing) return;
var originalConfig = this.config;
try {
// 使用临时配置
this.config = this.getEffectiveConfig();
var needBall = false;
var needPanel = false;
if (!changedKey) {
needBall = true;
needPanel = true;
} else {
// 根据修改的 key 判断需要刷新什么,避免全量刷新导致闪烁
if (changedKey.indexOf("BALL_") === 0) needBall = true;
if (changedKey.indexOf("PANEL_") === 0) needPanel = true;
// 球大小改变会影响面板位置
if (changedKey === "BALL_SIZE_DP") needPanel = true;
}
// 1. 刷新悬浮球 (保持面板不关闭)
if (needBall) {
this.rebuildBallForNewSize(true);
}
// 2. 刷新主面板预览
if (needPanel) {
// 如果当前没有显示主面板,则创建并显示;如果已显示,则替换
var panel = this.buildPanelView("main");
// 计算位置 (使用当前球的位置)
var maxH = Math.floor(this.state.screen.h * 0.75);
panel.measure(
android.view.View.MeasureSpec.makeMeasureSpec(this.state.screen.w, android.view.View.MeasureSpec.AT_MOST),
android.view.View.MeasureSpec.makeMeasureSpec(maxH, android.view.View.MeasureSpec.AT_MOST)
);
var pw = panel.getMeasuredWidth();
var ph = panel.getMeasuredHeight();
if (ph > maxH) ph = maxH;
var bx = this.state.ballLp.x;
var by = this.state.ballLp.y;
var px = this.computePanelX(bx, pw);
var py = by;
// 尝试调整 Y
var r = this.tryAdjustPanelY(px, py, pw, ph, bx, by);
var finalX = r.ok ? r.x : px;
var finalY = r.ok ? r.y : this.clamp(py, 0, this.state.screen.h - ph);
// 优化闪烁:先添加新面板,再移除旧面板 (这样新面板会在最上层,符合预览需求)
var oldPanel = this.state.panel;
var oldAdded = this.state.addedPanel;
// 添加新面板 (addPanel 会更新 this.state.panel)
// 注意addPanel 中已为 main 添加 FLAG_NOT_FOCUSABLE所以即使在最上层也不会抢走 Settings 的输入焦点
this.addPanel(panel, finalX, finalY, "main");
// 移除旧面板
if (oldAdded && oldPanel) {
try { this.state.wm.removeView(oldPanel); } catch(e) {}
}
}
} catch(e) {
safeLog(this.L, 'e', "refreshPreview err=" + e);
} finally {
this.config = originalConfig;
}
};
FloatBallAppWM.prototype.persistUserCfgFromObject = function(obj) {
// # 这段代码的主要内容/用途:从临时编辑对象里按 schema 白名单抽取并保存(跳过 section 标题等无 key 项)
try {
var schema = this.getConfigSchema();
var out = {};
var i;
for (i = 0; i < schema.length; i++) {
if (!schema[i] || !schema[i].key) continue;
var k = String(schema[i].key);
out[k] = obj[k];
}
return this.saveConfig(out);
} catch (e0) { return false; }
};
FloatBallAppWM.prototype.applyImmediateEffectsForKey = function(k) {
try {
if (k === "LOG_ENABLE") {
try {
if (this.L) {
this.L.enable = !!this.config.LOG_ENABLE;
this.L.cfg.LOG_ENABLE = !!this.config.LOG_ENABLE;
this.L.i("apply LOG_ENABLE=" + String(this.config.LOG_ENABLE));
}
} catch (eLE) {}
return;
}
if (k === "LOG_DEBUG") {
try {
if (this.L) {
this.L.debug = !!this.config.LOG_DEBUG;
this.L.cfg.LOG_DEBUG = !!this.config.LOG_DEBUG;
this.L.i("apply LOG_DEBUG=" + String(this.config.LOG_DEBUG));
}
} catch (eLD) {}
return;
}
if (k === "LOG_KEEP_DAYS") {
try {
var n = Math.max(1, Math.floor(Number(this.config.LOG_KEEP_DAYS || 3)));
this.config.LOG_KEEP_DAYS = n;
if (this.L) {
this.L.keepDays = n;
this.L.cfg.LOG_KEEP_DAYS = n;
this.L.i("apply LOG_KEEP_DAYS=" + String(n));
this.L.cleanupOldFiles();
}
} catch (eLK) {}
return;
}
if (k === "BALL_SIZE_DP" || k === "BALL_PNG_MODE" || k === "BALL_ICON_TYPE" || k === "BALL_ICON_FILE_PATH" || k === "BALL_ICON_RES_ID" || k === "BALL_ICON_RES_NAME" || k === "BALL_ICON_SIZE_DP" || k === "BALL_ICON_TINT_HEX" || k === "BALL_ICON_TEXT_GAP_DP") { this.rebuildBallForNewSize(); return; }
if (k === "PANEL_ROWS" || k === "PANEL_COLS" ||
k === "PANEL_ITEM_SIZE_DP" || k === "PANEL_GAP_DP" ||
k === "PANEL_PADDING_DP" || k === "PANEL_ICON_SIZE_DP" ||
k === "PANEL_LABEL_ENABLED" || k === "PANEL_LABEL_TEXT_SIZE_SP" ||
k === "PANEL_LABEL_TOP_MARGIN_DP") {
if (this.state.addedPanel) this.hideMainPanel();
if (this.state.addedSettings) this.hideSettingsPanel();
if (this.state.addedViewer) this.hideViewerPanel();
return;
}
if (k === "EDGE_VISIBLE_RATIO") {
if (this.state.addedBall && this.state.docked) {
this.state.docked = false;
this.snapToEdgeDocked(false);
}
return;
}
} catch (e0) {}
};
FloatBallAppWM.prototype.commitPendingUserCfg = function() {
try {
if (!this.state.pendingUserCfg) return { ok: false, reason: "no_pending" };
var schema = this.getConfigSchema();
var changedKeys = [];
var i;
for (i = 0; i < schema.length; i++) {
if (!schema[i] || !schema[i].key) continue;
var k = String(schema[i].key);
var oldV = this.config[k];
var newV = this.state.pendingUserCfg[k];
if (String(oldV) !== String(newV)) {
this.config[k] = newV;
changedKeys.push(k);
}
}
this.persistUserCfgFromObject(this.state.pendingUserCfg);
var j;
for (j = 0; j < changedKeys.length; j++) {
if (changedKeys[j] === "BALL_SIZE_DP") { this.applyImmediateEffectsForKey("BALL_SIZE_DP"); break; }
}
for (j = 0; j < changedKeys.length; j++) {
if (changedKeys[j] !== "BALL_SIZE_DP") this.applyImmediateEffectsForKey(changedKeys[j]);
}
this.state.pendingDirty = false;
safeLog(this.L, 'i', "commit settings changed=" + JSON.stringify(changedKeys));
return { ok: true, changed: changedKeys };
} catch (e0) {
safeLog(this.L, 'e', "commitPendingUserCfg err=" + String(e0));
return { ok: false, err: String(e0) };
}
};