2.2.0:手表端连上后先到「远控 / 静默更新被控端」选择页

== 用户要求 ==

"安卓那个逻辑也要改一下,就是你输入配对码后,进入一个页面,
 页面有两个按钮,第一个按钮是远控,第二个按钮是静默更新被控端"

== 做成什么样 ==

新增 choicePanel,连接成功后先进这一页:

    已连接「电脑名」
    电脑端 2.1.5 → 可更新到 2.2.0

    [        远控        ]   <- 第一个按钮
    [   静默更新被控端    ]   <- 第二个按钮

按钮顺序按用户说的来(远控在前)。

页面顶上显示电脑名和**电脑端当前版本** —— 用户之前要求
"手表端连接那个电脑端也可以查看电脑端目前所处的版本",
这里是最自然的展示位置(不弹框,只显示一行)。

为什么不让它直接进控制页:老版本的被控端可能缺功能或有 bug,
"先更新再远控"是更合理的顺序 —— 但不强制,给用户自己选。

== 一个刻意不改的地方 ==

自动切到中转服务器**恢复**时的 `showControlPanel()` **没动** ——
用户本来就在远控页,中途弹一层选择页会打断他。
测试里专门断言了这一点,免得以后有人"顺手统一一下"改坏。

== 顺手修的几个问题 ==

1. **忘了定义 lastDeviceName** —— 写选择页时凭直觉用了这个变量,
   编译报"找不到符号"。补上字段并在连接回调里赋值。
   (教训:写代码时要用的变量,先确认它存在。)

2. **泄漏闸门拦下我自己** —— 这两天写的一批服务器诊断/配置脚本里
   硬编码了服务器 IP(26 处,5 个文件)。规则是 IP 不能进任何
   被跟踪的文件。已全部改成占位符,真实值仍从
   _build/.lightsail_host(已 gitignore)读。

3. **verify_all.ps1 删掉「锁屏时记 SKIP」的逻辑** ——
   用户明确要求删。那个好意带来一个更糟的后果:锁屏时几关根本不跑,
   回归"全绿"了但关键测试其实一跳而过。
   **"因为环境不满足所以不测"比"测了发现失败"更危险。**
   删掉之后果然暴露出 3 个 FAIL —— 屏幕解锁后重跑全部通过,
   确认是环境问题而不是代码问题。

== 服务器这边(不在这份提交里,但同一天做的)==

  * nginx 按用户要求重排:
      IP(80) 任何路径 -> 404(没东西)
      域名 / -> 官网;/zhongzhuanzhan -> 中转站设置
      9100 -> 同样的内容(等 AWS 开防火墙)
  * 删掉早年 Cloudflare Tunnel 的 systemd 残留
  * **补上 ACME 挑战例外** —— 这是最要紧的一条:
    80 端口原来把所有请求都 301 到 https,包括
    /.well-known/acme-challenge/,**续期时会失败**。
    而且它是延迟爆发的(最长 90 天后证书到期才暴露)。
  * 清理 1Panel 安装包残留(223MB)

== 验证 ==

    全量回归 21 关全部 PASS(零 FAIL,桌面可用状态)
    Java 编译通过(android-34, --release 8)
    623+ Python 文件语法通过
    泄漏闸门通过
    全部产物重建,Win7 兼容校验通过
This commit is contained in:
xmz7891
2026-10-10 17:13:24 +08:00
parent 48bc392466
commit 1d368979aa
29 changed files with 2000 additions and 122 deletions
@@ -343,6 +343,21 @@ public class MainActivity extends Activity implements RemoteClient.Listener {
// ---------------- 鼠标控制(触摸板)----------------
private FrameLayout mousePanel;
/**
* 连上之后先给用户看的那一页:远控 / 静默更新被控端。
*
* <p>用户要求:"输入配对码后,进入一个页面,页面有两个按钮,
* 第一个按钮是远控,第二个按钮是静默更新被控端"。
*
* <p>为什么不让它直接进控制页:老版本的被控端可能缺功能或者有 bug,
* "先更新再远控"是更合理的顺序 —— 但也不能强制,所以给用户选。
*/
private FrameLayout choicePanel;
/** 选择页上显示"电脑端版本 X.Y.Z"那一行。 */
private TextView choiceVersionText;
/** 选择页上显示"已连接 XXX"。 */
private TextView choiceWhoText;
private View mousePad;
private boolean mouseDragging = false;
private float mouseLastX = 0f;
@@ -429,6 +444,7 @@ public class MainActivity extends Activity implements RemoteClient.Listener {
// 编译就报 cannot find symbol。教训:写调用之前先 grep 一遍。
loadQuickSlots();
buildMousePanel(); // 只建,先不显示(showOnly 会切)
buildChoicePanel(); // 同上:连上之后才显示
buildQuickBar();
// 面板顶部给快捷栏让出一截,否则每页顶部内容会被盖住
@@ -438,11 +454,16 @@ public class MainActivity extends Activity implements RemoteClient.Listener {
applyPanelTopMargin(lanPanel);
applyPanelTopMargin(settingsPanel);
applyPanelTopMargin(mousePanel);
applyPanelTopMargin(choicePanel);
root.addView(mousePanel, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mousePanel.setVisibility(View.GONE);
root.addView(choicePanel, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
choicePanel.setVisibility(View.GONE);
// 快捷栏最后加 -> 永远在最上层
root.addView(quickBar, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dp(QUICK_BAR_DP)));
@@ -3422,12 +3443,143 @@ public class MainActivity extends Activity implements RemoteClient.Listener {
"dx", (int) (dx * 3.0f), "dy", (int) (dy * 3.0f)), "鼠标");
}
// ==================================================================
// 连接后的选择页(远控 / 静默更新)
// ==================================================================
/**
* 建选择页。
*
* <p>布局从上到下:连接信息 -> 版本 -> [远控] -> [静默更新被控端]。
* 按钮顺序就是用户说的顺序(远控在前)。
*/
private void buildChoicePanel() {
choicePanel = new FrameLayout(this);
choicePanel.setBackgroundColor(C_BG);
LinearLayout box = new LinearLayout(this);
box.setOrientation(LinearLayout.VERTICAL);
box.setPadding(dp(14), dp(20), dp(14), dp(14));
box.setGravity(android.view.Gravity.CENTER_HORIZONTAL);
choicePanel.addView(box, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
choiceWhoText = new TextView(this);
choiceWhoText.setText("已连接");
choiceWhoText.setTextColor(C_FG);
choiceWhoText.setTextSize(14f);
choiceWhoText.setGravity(android.view.Gravity.CENTER);
box.addView(choiceWhoText);
choiceVersionText = new TextView(this);
choiceVersionText.setText("电脑端版本:正在查…");
choiceVersionText.setTextColor(C_DIM);
choiceVersionText.setTextSize(12f);
choiceVersionText.setGravity(android.view.Gravity.CENTER);
choiceVersionText.setPadding(0, dp(4), 0, dp(10));
box.addView(choiceVersionText);
// 第一个按钮:远控
Button ctrl = bigButton("远控", C_OK, 17);
LinearLayout.LayoutParams ctrlLp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dp(54));
ctrlLp.setMargins(0, 0, 0, dp(10));
ctrl.setLayoutParams(ctrlLp);
ctrl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showControlPanel();
}
});
box.addView(ctrl);
// 第二个按钮:静默更新被控端
Button upd = bigButton("静默更新被控端", C_ACCENT, 15);
LinearLayout.LayoutParams updLp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dp(54));
upd.setLayoutParams(updLp);
upd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 复用已经做好的那条链路:查版本 -> 确认 -> 触发更新
checkPcUpdate();
}
});
box.addView(upd);
TextView tip = new TextView(this);
tip.setText("更新时电脑会自动下载并静默安装,"
+ "不会弹任何窗口,设置和配对码都不变。");
tip.setTextColor(C_DIM);
tip.setTextSize(10f);
tip.setGravity(android.view.Gravity.CENTER);
tip.setPadding(0, dp(10), 0, 0);
box.addView(tip);
}
/** 显示选择页,并顺手查一下电脑端版本。 */
private void showChoicePanel() {
if (choicePanel == null) {
buildChoicePanel();
}
if (choiceWhoText != null) {
String who = lastDeviceName;
choiceWhoText.setText(who == null || who.isEmpty()
? "已连接" : "已连接「" + who + "」");
}
if (choiceVersionText != null) {
choiceVersionText.setText("电脑端版本:正在查…");
}
showOnly(choicePanel);
queryPcVersionForChoice();
}
/**
* 查电脑端版本填到选择页上。
*
* <p>单独写一个(不直接用 checkPcUpdate)是因为那个会弹确认框 ——
* 这里只是"让你看一眼版本",不该弹东西。
*/
private void queryPcVersionForChoice() {
io.execute(new Runnable() {
@Override
public void run() {
Map<String, Object> reply;
try {
reply = client.call(Json.obj("cmd", "update",
"action", "check"), 25000);
} catch (final Throwable error) {
reply = null;
}
final String text;
if (reply != null && Boolean.TRUE.equals(reply.get("ok"))) {
String cur = String.valueOf(reply.get("current"));
String latest = String.valueOf(reply.get("latest"));
text = Boolean.TRUE.equals(reply.get("needUpdate"))
? "电脑端 " + cur + " → 可更新到 " + latest
: "电脑端 " + cur + "(已是最新)";
} else {
text = "电脑端版本:查不到(可以点下面的更新按钮再试)";
}
ui.post(new Runnable() {
@Override
public void run() {
if (choiceVersionText != null) {
choiceVersionText.setText(text);
}
}
});
}
});
}
private void showOnly(View keep) {
// ⚠ 新增面板必须加进这个数组 —— 漏了的话那个面板永远不隐藏,
// 表现是"进了别的页面它还压在上面"。
// (注释里那条"漏了 lanPanel 导致局域网连上没反应"就是这个坑。)
View[] all = {connectPanel, controlPanel, mirrorPanel, lanPanel,
settingsPanel, mousePanel};
settingsPanel, mousePanel, choicePanel};
for (View v : all) {
if (v != null) {
v.setVisibility(v == keep ? View.VISIBLE : View.GONE);
@@ -3689,6 +3841,14 @@ public class MainActivity extends Activity implements RemoteClient.Listener {
/** 刚才连的地址/配对码/令牌,切回中转时要用。 */
private volatile String lastHost = "";
/**
* 当前连上的电脑名。选择页要显示「已连接『电脑名』」——
* 只显示 IP 的话用户不好认是哪台。
*
* <p>⚠ 我第一版在这里凭直觉写了个 `lastDeviceName` 却忘了定义它,
* 编译报 "找不到符号"。**写代码时要用的变量,先确认它存在。**
*/
private volatile String lastDeviceName = "";
private volatile int lastPort;
private volatile String lastCode = "";
private volatile String lastToken = "";
@@ -3917,7 +4077,15 @@ public class MainActivity extends Activity implements RemoteClient.Listener {
+ (finalName == null ? host : finalName)
+ (finalLanDirect ? "(局域网)" : "(中转)"));
}
showControlPanel();
// 用户要求:输入配对码连上之后先到选择页
// (远控 / 静默更新被控端),不直接进控制页。
//
// ⚠ 只有**这个**调用点改。下面"自动切到中转恢复"
// 那个 showControlPanel() 不改 —— 用户本来就在
// 远控页,中途弹选择页会打断他。
lastDeviceName = (finalName == null
? host : finalName);
showChoicePanel();
appendLog("已连接到 " + host);
toast("连接成功");
} else {