Add ToolApp surface back gesture mode

This commit is contained in:
7015725
2026-05-22 08:56:24 +08:00
parent 479a23592c
commit 8fd7a560d8
4 changed files with 140 additions and 29 deletions

View File

@@ -100,6 +100,7 @@ var ConfigValidator = {
LONG_PRESS_TRIGGERED_MOVE_SLOP_DP: { type: "int", min: 8, max: 80, default: 28 },
LONG_PRESS_VIBRATE_MS: { type: "int", min: 10, max: 100, default: 40 },
CLICK_SLOP_DP: { type: "int", min: 2, max: 20, default: 6 },
TOOLAPP_BACK_GESTURE_MODE: { type: "enum", values: ["edge", "surface", "off"], default: "surface" },
TOOLAPP_BACK_EDGE_WIDTH_DP: { type: "int", min: 1, max: 120, default: 72 },
ENABLE_TOOLAPP_INNER_BACK_STRIPS: { type: "bool", default: false },
ENABLE_TOOLAPP_SCREEN_BACK_STRIPS: { type: "bool", default: true },
@@ -736,6 +737,7 @@ var ConfigManager = {
LONG_PRESS_MS: 520,
LONG_PRESS_TRIGGERED_MOVE_SLOP_DP: 28,
CLICK_SLOP_DP: 6,
TOOLAPP_BACK_GESTURE_MODE: "surface",
TOOLAPP_BACK_EDGE_WIDTH_DP: 72,
ENABLE_TOOLAPP_INNER_BACK_STRIPS: false,
ENABLE_TOOLAPP_SCREEN_BACK_STRIPS: true,
@@ -850,7 +852,12 @@ var ConfigManager = {
{ type: "section", name: "触摸与手势" },
{ key: "CLICK_SLOP_DP", name: "点击位移阈值(dp)", type: "int", min: 1, max: 40, step: 1 },
{ key: "TOOLAPP_BACK_EDGE_WIDTH_DP", name: "返回起手边缘宽度", type: "int", min: 1, max: 120, step: 1 },
{ key: "TOOLAPP_BACK_GESTURE_MODE", name: "设置页滑动返回模式", type: "single_choice", options: [
{ label: "全表面横滑", value: "surface" },
{ label: "仅左右边缘", value: "edge" },
{ label: "关闭", value: "off" }
]},
{ key: "TOOLAPP_BACK_EDGE_WIDTH_DP", name: "边缘模式起手宽度", type: "int", min: 1, max: 120, step: 1 },
{ key: "ENABLE_TOOLAPP_INNER_BACK_STRIPS", name: "启用旧版页面覆盖热区(不推荐)", type: "bool" },
{ key: "ENABLE_TOOLAPP_SCREEN_BACK_STRIPS", name: "启用屏幕空白区返回", type: "bool" },
{ key: "TOOLAPP_BACK_COMMIT_DISTANCE_DP", name: "设置页返回触发距离", type: "int", min: 1, max: 480, step: 1 },
@@ -892,7 +899,7 @@ var ConfigManager = {
var needReset = false;
if (s) {
var sStr = JSON.stringify(s);
if (sStr.indexOf("ENABLE_SNAP_TO_EDGE") < 0 || sStr.indexOf("ENABLE_ANIMATIONS") < 0 || sStr.indexOf("BALL_IDLE_ALPHA") < 0 || sStr.indexOf("PANEL_POS_GRAVITY") < 0 || sStr.indexOf("single_choice") < 0 || sStr.indexOf("ball_shortx_icon") < 0 || sStr.indexOf("ball_color") < 0 || sStr.indexOf("SETTINGS_THEME") < 0 || sStr.indexOf("BALL_BG_COLOR_HEX") < 0 || sStr.indexOf("BALL_ICON_SIZE_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_EDGE_WIDTH_DP") < 0 || sStr.indexOf("ENABLE_TOOLAPP_INNER_BACK_STRIPS") < 0 || sStr.indexOf("ENABLE_TOOLAPP_SCREEN_BACK_STRIPS") < 0 || sStr.indexOf("TOOLAPP_BACK_COMMIT_DISTANCE_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_PROGRESS_DISTANCE_DP") < 0 || sStr.indexOf("LONG_PRESS_TRIGGERED_MOVE_SLOP_DP") < 0) {
if (sStr.indexOf("ENABLE_SNAP_TO_EDGE") < 0 || sStr.indexOf("ENABLE_ANIMATIONS") < 0 || sStr.indexOf("BALL_IDLE_ALPHA") < 0 || sStr.indexOf("PANEL_POS_GRAVITY") < 0 || sStr.indexOf("single_choice") < 0 || sStr.indexOf("ball_shortx_icon") < 0 || sStr.indexOf("ball_color") < 0 || sStr.indexOf("SETTINGS_THEME") < 0 || sStr.indexOf("BALL_BG_COLOR_HEX") < 0 || sStr.indexOf("BALL_ICON_SIZE_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_GESTURE_MODE") < 0 || sStr.indexOf("TOOLAPP_BACK_EDGE_WIDTH_DP") < 0 || sStr.indexOf("ENABLE_TOOLAPP_INNER_BACK_STRIPS") < 0 || sStr.indexOf("ENABLE_TOOLAPP_SCREEN_BACK_STRIPS") < 0 || sStr.indexOf("TOOLAPP_BACK_COMMIT_DISTANCE_DP") < 0 || sStr.indexOf("TOOLAPP_BACK_PROGRESS_DISTANCE_DP") < 0 || sStr.indexOf("LONG_PRESS_TRIGGERED_MOVE_SLOP_DP") < 0) {
needReset = true;
}
@@ -923,6 +930,7 @@ var ConfigManager = {
if (schemaItemDiffers("BALL_ICON_TINT_HEX", ["name", "type"]) ||
schemaItemDiffers("BALL_ICON_SIZE_DP", ["name", "type", "min", "max", "step"]) ||
schemaItemDiffers("BALL_BG_COLOR_HEX", ["name", "type"]) ||
schemaItemDiffers("TOOLAPP_BACK_GESTURE_MODE", ["name", "type"]) ||
schemaItemDiffers("TOOLAPP_BACK_EDGE_WIDTH_DP", ["name", "type", "min", "max", "step"]) ||
schemaItemDiffers("ENABLE_TOOLAPP_INNER_BACK_STRIPS", ["name", "type"]) ||
schemaItemDiffers("ENABLE_TOOLAPP_SCREEN_BACK_STRIPS", ["name", "type"]) ||

View File

@@ -1231,6 +1231,73 @@ FloatBallAppWM.prototype.getToolAppResponsiveSpec = function() {
};
};
FloatBallAppWM.prototype.getToolAppBackGestureMode = function() {
var mode = "surface";
try { mode = String(this.config.TOOLAPP_BACK_GESTURE_MODE || "surface"); } catch(e) { mode = "surface"; }
if (mode !== "edge" && mode !== "surface" && mode !== "off") mode = "surface";
return mode;
};
FloatBallAppWM.prototype.isToolAppBackInteractiveView = function(v) {
try {
if (!v) return false;
try { if (v instanceof android.widget.SeekBar) return true; } catch(eSeek) {}
try { if (v instanceof android.widget.CompoundButton) return true; } catch(eComp) {}
try { if (v instanceof android.widget.Switch) return true; } catch(eSw) {}
try { if (v instanceof android.widget.EditText) return true; } catch(eEdit) {}
try { if (v instanceof android.widget.Button) return true; } catch(eBtn) {}
try { if (v instanceof android.widget.AbsListView) return true; } catch(eAbs) {}
try { if (v instanceof android.widget.ListView) return true; } catch(eList) {}
try { if (v instanceof android.widget.HorizontalScrollView) return true; } catch(eHsv) {}
try {
var rvCls = java.lang.Class.forName("androidx.recyclerview.widget.RecyclerView");
if (rvCls && rvCls.isInstance(v)) return true;
} catch(eRv1) {}
try {
var rvCls2 = java.lang.Class.forName("android.support.v7.widget.RecyclerView");
if (rvCls2 && rvCls2.isInstance(v)) return true;
} catch(eRv2) {}
try { if (v.isClickable && v.isClickable()) return true; } catch(eClick) {}
try { if (v.isLongClickable && v.isLongClickable()) return true; } catch(eLong) {}
} catch(e) {}
return false;
};
FloatBallAppWM.prototype.findToolAppTouchedChild = function(v, rawX, rawY) {
try {
if (!v || !v.getVisibility || v.getVisibility() !== android.view.View.VISIBLE) return null;
var loc = java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE, 2);
try { v.getLocationOnScreen(loc); } catch(eLoc) { return null; }
var l = Number(loc[0] || 0), t = Number(loc[1] || 0);
var r = l + Number(v.getWidth ? v.getWidth() : 0);
var b = t + Number(v.getHeight ? v.getHeight() : 0);
if (rawX < l || rawX > r || rawY < t || rawY > b) return null;
try {
if (v instanceof android.view.ViewGroup) {
var count = v.getChildCount ? v.getChildCount() : 0;
for (var i = count - 1; i >= 0; i--) {
var child = v.getChildAt(i);
var hit = this.findToolAppTouchedChild(child, rawX, rawY);
if (hit) return hit;
}
}
} catch(eGroup) {}
return v;
} catch(e) {}
return null;
};
FloatBallAppWM.prototype.isToolAppBackBlockedAt = function(root, rawX, rawY) {
try {
var v = this.findToolAppTouchedChild(root, rawX, rawY);
while (v && v !== root) {
if (this.isToolAppBackInteractiveView && this.isToolAppBackInteractiveView(v)) return true;
try { v = v.getParent ? v.getParent() : null; } catch(eParent) { v = null; }
}
} catch(e) {}
return false;
};
FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBack) {
var self = this;
var isDark = this.isDarkTheme();
@@ -1244,8 +1311,13 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
var topBarHeight = spec ? spec.topBarHeight : this.dp(56);
var rootDownX = 0;
var rootDownY = 0;
var rootDownRawX = 0;
var rootDownRawY = 0;
var rootEdge = -1;
var rootBackMode = "surface";
var rootBackActive = false;
var rootBackEligible = false;
var rootBackBlocked = false;
var rootBackMoved = false;
var root = new JavaAdapter(android.widget.FrameLayout, {
onInterceptTouchEvent: function(ev) {
@@ -1255,36 +1327,61 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
if (action === android.view.MotionEvent.ACTION_DOWN) {
rootDownX = ev.getX();
rootDownY = ev.getY();
rootDownRawX = ev.getRawX();
rootDownRawY = ev.getRawY();
rootBackActive = false;
rootBackEligible = false;
rootBackBlocked = false;
rootBackMoved = false;
rootEdge = -1;
rootBackMode = self.getToolAppBackGestureMode ? self.getToolAppBackGestureMode() : "surface";
if (rootBackMode === "off") return false;
var canBackNow = !!(self.state && self.state.toolAppActive && self.hasToolAppBackTarget && self.hasToolAppBackTarget());
if (canBackNow) {
if (!canBackNow) return false;
var edgeW = self.getToolAppBackEdgeWidthPx ? self.getToolAppBackEdgeWidthPx() : self.dp(72);
var rw = 0;
try { rw = this.getWidth(); } catch(eW) { rw = 0; }
if (rootDownX <= edgeW) rootEdge = 0;
else if (rw > 0 && rootDownX >= rw - edgeW) rootEdge = 1;
if (rootBackMode === "edge") {
if (rootDownX <= edgeW) { rootEdge = 0; rootBackEligible = true; }
else if (rw > 0 && rootDownX >= rw - edgeW) { rootEdge = 1; rootBackEligible = true; }
} else {
rootBackEligible = true;
rootBackBlocked = !!(self.isToolAppBackBlockedAt && self.isToolAppBackBlockedAt(this, rootDownRawX, rootDownRawY));
}
// DOWN 必须放行给子控件,避免按钮/列表/Switch/SeekBar 被边缘手势抢走
// DOWN 必须放行给子控件,surface 模式也不抢按钮/列表/Switch/SeekBar 原始点击
return false;
}
if (action === android.view.MotionEvent.ACTION_MOVE) {
if (rootEdge < 0) return false;
if (!rootBackEligible || rootBackBlocked || rootBackMode === "off") return false;
if (!(self.hasToolAppBackTarget && self.hasToolAppBackTarget())) return false;
var dx = ev.getX() - rootDownX;
var dy = ev.getY() - rootDownY;
var adx = Math.abs(dx);
var ady = Math.abs(dy);
var validDir = (rootEdge === 0 && dx > 0) || (rootEdge === 1 && dx < 0);
var edge = rootEdge;
var validDir = false;
if (rootBackMode === "surface") {
edge = dx >= 0 ? 0 : 1;
validDir = (dx !== 0);
} else {
validDir = (edge === 0 && dx > 0) || (edge === 1 && dx < 0);
}
var shouldIntercept = false;
if (rootBackMode === "surface") {
shouldIntercept = validDir && adx > self.dp(48) && adx > ady * 1.2;
} else {
var slopDp = Number(self.config.CLICK_SLOP_DP || 6);
if (isNaN(slopDp)) slopDp = 6;
if (slopDp < 1) slopDp = 1;
if (slopDp > 40) slopDp = 40;
var touchSlop = Math.max(self.dp(8), self.dp(slopDp));
if (validDir && adx > touchSlop && adx > ady * 0.75) {
shouldIntercept = validDir && adx > touchSlop && adx > ady * 0.75;
}
if (shouldIntercept) {
rootEdge = edge;
rootBackActive = true;
rootBackMoved = true;
try { self.prepareToolAppBackPreview(rootEdge); } catch(ePrep) { try { safeLog(self.L, 'w', 'root back preview prepare fail: ' + String(ePrep)); } catch(eLogPrep) {} }
try { self.prepareToolAppBackPreview(edge); } catch(ePrep) { try { safeLog(self.L, 'w', 'root back preview prepare fail: ' + String(ePrep)); } catch(eLogPrep) {} }
try {
var triggerDp0 = Number(self.config.TOOLAPP_BACK_PROGRESS_DISTANCE_DP || 96);
if (isNaN(triggerDp0)) triggerDp0 = 96;
@@ -1292,9 +1389,9 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
if (triggerDp0 > 720) triggerDp0 = 720;
var triggerDistance0 = self.dp(triggerDp0);
var p0 = Math.min(1, adx / triggerDistance0);
self.applyToolAppBackPreviewProgress(rootEdge, p0, adx);
self.applyToolAppBackPreviewProgress(edge, p0, adx);
} catch(eFirstMove) {}
try { safeLog(self.L, 'd', 'root edge back intercept edge=' + String(rootEdge) + ' dx=' + String(dx)); } catch(eMoveLog) {}
try { safeLog(self.L, 'd', 'root back intercept mode=' + String(rootBackMode) + ' edge=' + String(edge) + ' dx=' + String(dx)); } catch(eMoveLog) {}
return true;
}
return false;
@@ -1314,7 +1411,8 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
var mx = ev.getX() - rootDownX;
var my = ev.getY() - rootDownY;
var validDir2 = (rootEdge === 0 && mx > 0) || (rootEdge === 1 && mx < 0);
if (validDir2 && Math.abs(mx) > Math.abs(my) * 0.75) {
var dominance = rootBackMode === "surface" ? 1.2 : 0.75;
if (validDir2 && Math.abs(mx) > Math.abs(my) * dominance) {
var triggerDp = Number(self.config.TOOLAPP_BACK_PROGRESS_DISTANCE_DP || 96);
if (isNaN(triggerDp)) triggerDp = 96;
if (triggerDp < 1) triggerDp = 1;
@@ -1334,9 +1432,12 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
if (commitDp > 480) commitDp = 480;
var completeDistance = self.dp(commitDp);
var okDir = (rootEdge === 0 && ux > completeDistance) || (rootEdge === 1 && ux < -completeDistance);
var ok = (action === android.view.MotionEvent.ACTION_UP) && rootBackMoved && okDir && Math.abs(ux) > Math.abs(uy) * 0.75;
var ratio = rootBackMode === "surface" ? 1.2 : 0.75;
var ok = (action === android.view.MotionEvent.ACTION_UP) && rootBackMoved && okDir && Math.abs(ux) > Math.abs(uy) * ratio;
var edgeDone = rootEdge;
rootBackActive = false;
rootBackEligible = false;
rootBackBlocked = false;
rootBackMoved = false;
rootEdge = -1;
self.finishToolAppBackPreview(edgeDone, ok);
@@ -1348,6 +1449,8 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
try { safeLog(self.L, 'w', 'tool app root back touch fail: ' + String(e2)); } catch(eLog2) {}
}
rootBackActive = false;
rootBackEligible = false;
rootBackBlocked = false;
rootBackMoved = false;
rootEdge = -1;
return false;
@@ -1419,7 +1522,7 @@ FloatBallAppWM.prototype.buildToolAppShell = function(contentView, title, canBac
body.addView(host, hostLp);
// 兼容旧设置:不再添加页面内透明返回热区。
// 返回手势由 root.onInterceptTouchEvent 按“边缘起手 + 横向阈值 + 方向正确”延迟拦截,避免覆盖控件
// 返回手势由 root.onInterceptTouchEvent 延迟拦截surface 模式会排除交互控件edge 模式保留边缘起手
try {
this.state.toolAppInnerBackLeftStrip = null;
this.state.toolAppInnerBackRightStrip = null;

View File

@@ -2,8 +2,8 @@
"alg": "SHA256withRSA",
"files": {
"th_01_base.js": {
"sha256": "2dfbf8e338b15d62399f5084586a7b593fd81eb6d321f5403da928434469a210",
"size": 57148
"sha256": "c186450c6a64cf51fdf6cc93e45624793596ce9f566d1b18c3e70905459fd618",
"size": 57722
},
"th_02_core.js": {
"sha256": "3c5c498d200e961d48fc9ca3f885475e770ecb32b83ec6a89d23df3f88aed1c9",
@@ -58,8 +58,8 @@
"size": 304993
},
"th_15_extra.js": {
"sha256": "93dc6db5c01cdb080660e988c1665cbe742b80849b583bc485f6d1d27310f908",
"size": 120294
"sha256": "e0d3b2f39ac13126f4534756ea0d928955e540e63d4fb20eddc53c72fe63f720",
"size": 124928
},
"th_16_entry.js": {
"sha256": "6c59d9891cd010647f84c3db93f1cf95c7bbfb758470ea21044bf72eb8ff73d1",
@@ -68,5 +68,5 @@
},
"keyId": "toolhub-targets-2026-rsa3072",
"schema": 2,
"version": 20260522003824
"version": 20260522005618
}

View File

@@ -1 +1 @@
P2H2QBMFs6oL8m45jPxIXQ/zR2glhsEnduNFmKr8/HS5vnRVDVAnfcZKxOn9wpl9XdllaeRDbr7nHGpgK1rHT7UAJbt+byDYiz50As4C52c2xGV3kq/bim6FRhKd5sSh4cumWGUhBMMPRbfgXuMQen9+B72THYOBME2xEiylyMKedrxrLh2flFT9Y5SkeNFgFAdPYxGLaazCwpn/ydGLqFPrZLNLS2an7D0+WeDDZOF0nxZEvKNoHW1wi0BFDecR4I4cJVsQw8jjL7GMt/tLOZrZXBML4R4gYM+4UQu3AxjAIi6iuw/9LFs5IzIJXDJLIO4Q2grs6avpWQGMKhVC9UpvQgijvR14S7tmHL6vgpNX+JLzoziVlzsZWWj2FLlXazsfJTkYCPiEjknJyzBaevgyZIRwYilyCeDTRwtenBiPXluyc1Y94Q2Cr02iIu6d/DF5mSi/lVV6VN/JTmGDNAwaYbOE1uVwtXs+t1hqDBJKaJBWT0UL1tmSAj1m3eVY
UMkA/Pj4p59+4HuG1tDQY3FxqOn4DK4/chmqh62NAfUoQ8EpTqVzMzuuJInAAdhGZjAmJK4esjOpOATXjHZQiwaaFsm5EGBXq/bxJTuxuc8714p8qIwvFGXVtQuv7pBS0SHAAFhQ0a9JKdMwSo5zyZeEO/NjTE8VbloQu42R562Dfuyas5tMVvawG2u34kHRGvsSQFPz28Ju4r3fyUg8UIareb8HK8OW4mniQLHoNdueCgERGCPavhTSbWzXdO7QozExFFoMgxlbEFH8SJ6Ta8/cVhLj83oElX2bhCuOBvxHvb2Rli4LGoJezlQgetv/vCz31bsZZhpGuRtwjMVjm9qK0MxTB7XfZdgMjmgbYDEbEeoKxYwhgPaQiKIcWlSzqv9PmsJYwN2MYI5QHbc2ibb7JQ72dslgUA/mtIUgNmjLbY1wNSLemmh5C5Igwn34hGORAQ+UmGjIP60oEADWRHt6teLqDPHrI3n458lIW7viOspmMaQmhcZi3tMEd+oa