Files
ShortX_ToolHub/code/th_05_persistence.js
linshenjianlu 9ad01b436d fix: 代码审查6项修复
- 689处空catch块补全日志
- eval远程代码增加SHA256校验
- 删除ToolHubLogger重复定义
- getParentFile()增加null保护
- 提取buildButtonEditorPanelView内通用工具函数到文件级
- 修复HandlerThread/ValueAnimator资源泄漏
2026-04-21 07:42:23 +08:00

299 lines
11 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) { safeLog(null, 'e', "catch " + String(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) { safeLog(null, 'e', "catch " + String(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) { safeLog(null, 'e', "catch " + String(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) { safeLog(null, 'e', "catch " + String(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) { safeLog(null, 'e', "catch " + String(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") { 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) { safeLog(null, 'e', "catch " + String(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) };
}
};