# -*- coding: utf-8 -*- r'''安卓端:中转地址改成"手动填" + 加一个设置页。 == 用户报的问题 == 「安卓端为什么还是默认填的我的中转站,以后改成手动填, 安卓端加一个设置,各种设置就在这里面调」 == 根因 == 代码里的默认值**已经改成空**了: private static final String DEFAULT_RELAY_URL = ""; 但你手表上看到的还是 `https://relay.xxmxxs.top` —— 因为 **老版本 1.5.0 把作者的域名硬编码成默认值**(git 历史里查到了): private static final String DEFAULT_RELAY_URL = "https://relay.xxmxxs.top"; 第一次运行它就 `setRelayUrl(默认值)` 存进了 SharedPreferences。 装新 APK **不会清偏好设置**,所以那个值一直留着。 **用户从来没"选"过这个地址** —— 是程序硬塞的。所以正确做法是 **一次性迁移**:如果存的就是当时内置的那个域名,当成没设过,清掉。 == 做的事 == 1. 迁移:把老版本硬塞的那个域名从偏好里清掉 2. 新增「设置」页,把散落的设置收进来: * 中转服务器(手动填 / 清空) * 记住的配对码(看 / 清) * 已配对设备(看数量 / 清空) * 关于(版本号、连接方式说明) 3. 首页那个「中转设置(跨网络用)」按钮改成「设置」,进设置页 4. settingsPanel 纳入 showOnly(),避免再出现"面板漏藏"那类问题 ''' from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent SRC = (ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "MainActivity.java") # ====================================================================== # 1. 迁移 + 面板字段 # ====================================================================== MIGRATION = ''' /** * 老版本硬塞进来的中转地址,要清掉。 * *

1.5.0 那版把作者的域名写死成默认值: *

     *     private static final String DEFAULT_RELAY_URL = "https://relay.xxmxxs.top";
     * 
* 而且第一次运行就存进了 SharedPreferences。装新 APK 不会清偏好, * 所以用户会看到「默认还是那个中转站」—— 其实他从来没选过。 * *

现在默认值是空串(跨网络要自己填)。这里做一次性迁移: * 存的值如果正好是当时内置的那个域名,就当成没设过,清掉。 * *

⚠ 只在**完全等于**内置值时清 —— 用户自己手填的地址不能动。 */ private static final String[] LEGACY_BAKED_IN_RELAYS = { "https://relay.xxmxxs.top", "http://relay.xxmxxs.top", }; private void migrateLegacyRelay() { String saved = getSharedPreferences("lanremote", MODE_PRIVATE) .getString("relay_url", ""); if (saved == null) { return; } String trimmed = saved.trim(); for (String legacy : LEGACY_BAKED_IN_RELAYS) { if (legacy.equalsIgnoreCase(trimmed)) { // 这是老版本硬塞的,不是用户选的 —— 清掉,让他自己填 getSharedPreferences("lanremote", MODE_PRIVATE).edit() .remove("relay_url").apply(); appendLog("已清掉旧版本内置的中转地址(不是你自己填的)"); return; } } } ''' SETTINGS_BUILD = ''' /** * 「设置」页。用户要求:"安卓端加一个设置,各种设置就在这里面调"。 * *

以前设置散在首页(一个「中转设置」按钮)和别处, * 现在统一收到这里。 */ private void buildSettingsPanel() { settingsPanel = new FrameLayout(this); settingsPanel.setBackgroundColor(C_BG); ScrollView scroll = new ScrollView(this); scroll.setBackgroundColor(C_BG); LinearLayout box = new LinearLayout(this); box.setOrientation(LinearLayout.VERTICAL); box.setPadding(dp(10), dp(12), dp(10), dp(12)); scroll.addView(box, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); settingsPanel.addView(scroll, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); LinearLayout top = new LinearLayout(this); top.setOrientation(LinearLayout.HORIZONTAL); top.setGravity(Gravity.CENTER_VERTICAL); box.addView(top, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); TextView title = new TextView(this); title.setText("设置"); title.setTextColor(C_FG); title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); title.setTypeface(null, android.graphics.Typeface.BOLD); top.addView(title, new LinearLayout.LayoutParams( 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); top.addView(smallButton("返回", C_CARD, new Runnable() { @Override public void run() { hideSettingsPanel(); } })); // ================= 中转服务器 ================= settingsRelayValue = new TextView(this); settingsRelayValue.setTextColor(C_DIM); settingsRelayValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); settingsRelayValue.setPadding(0, dp(6), 0, dp(4)); box.addView(sectionTitle("中转服务器")); box.addView(hintText("手表和电脑不在同一个网络时(比如在办公室管教室)" + "才需要填。填你自己那台的地址,比如 relay.你的域名。\\n" + "只在一个 WiFi 里用就留空 —— 那样直接连,最快也不耗流量。")); box.addView(settingsRelayValue); Button editRelay = bigButton("填写中转服务器", C_ACCENT, 14); editRelay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { promptRelayUrl(); } }); box.addView(editRelay); Button clearRelay = bigButton("清空中转服务器(只走局域网)", C_CARD, 13); clearRelay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { setRelayUrl(""); refreshSettingsPanel(); toast("已清空 —— 以后只走局域网"); } }); box.addView(clearRelay); // ================= 配对码 ================= settingsCodeValue = new TextView(this); settingsCodeValue.setTextColor(C_DIM); settingsCodeValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); settingsCodeValue.setPadding(0, dp(6), 0, dp(4)); box.addView(sectionTitle("记住的配对码")); box.addView(hintText("手表会记住最近一次用的配对码," + "下次连接自动带上,不用重复输。")); box.addView(settingsCodeValue); Button clearCode = bigButton("清除记住的配对码", C_CARD, 13); clearCode.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { getSharedPreferences("lanremote", MODE_PRIVATE).edit() .remove("paired_code").apply(); refreshSettingsPanel(); toast("已清除"); } }); box.addView(clearCode); // ================= 已配对设备 ================= settingsDevicesValue = new TextView(this); settingsDevicesValue.setTextColor(C_DIM); settingsDevicesValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); settingsDevicesValue.setPadding(0, dp(6), 0, dp(4)); box.addView(sectionTitle("已配对的电脑")); box.addView(hintText("配对过的电脑会出现在首页列表里,点一下就能连。" + "换电脑或者想重新配对时可以清空。")); box.addView(settingsDevicesValue); Button clearDevices = bigButton("清空已配对的电脑", C_DANGER, 13); clearDevices.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new android.app.AlertDialog.Builder(MainActivity.this) .setTitle("清空已配对的电脑?") .setMessage("清掉之后首页列表就空了," + "需要重新输配对码连接。") .setPositiveButton("清空", new android.content.DialogInterface.OnClickListener() { @Override public void onClick(android.content.DialogInterface d, int w) { store.clearDevices(); refreshDeviceList(); refreshSettingsPanel(); toast("已清空"); } }) .setNegativeButton("取消", null) .show(); } }); box.addView(clearDevices); // ================= 关于 ================= box.addView(sectionTitle("关于")); box.addView(hintText("ykxmz 手表端 版本 " + appVersion() + "\\n\\n" + "连接顺序:先试局域网直连(同一个 WiFi,几毫秒);" + "连不上再走中转(跨网络,受线路影响)。")); } /** 一行小节标题 */ private TextView sectionTitle(String text) { TextView v = new TextView(this); v.setText(text); v.setTextColor(C_FG); v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); v.setTypeface(null, android.graphics.Typeface.BOLD); v.setPadding(0, dp(14), 0, 0); return v; } /** 一行灰色说明 */ private TextView hintText(String text) { TextView v = new TextView(this); v.setText(text); v.setTextColor(C_DIM); v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11); v.setLineSpacing(0, 1.25f); v.setPadding(0, dp(4), 0, 0); return v; } /** 版本号。从 PackageManager 读,和构建脚本里的版本天然一致, * 不用在代码里再写一份(写两份必然有一份会忘改)。 */ private String appVersion() { try { return getPackageManager() .getPackageInfo(getPackageName(), 0).versionName; } catch (Exception e) { return "?"; } } /** 把设置页上的文字刷新成当前值 */ private void refreshSettingsPanel() { if (settingsRelayValue == null) { return; } String relay = relayUrl(); settingsRelayValue.setText(relay.isEmpty() ? "现在是:空(只走局域网)" : "现在是:" + relay); settingsRelayValue.setTextColor(relay.isEmpty() ? C_DIM : C_OK); String code = savedPairingCode(); settingsCodeValue.setText((code == null || code.isEmpty()) ? "现在没有记住的配对码" : "记住的是:" + code); int n = store.devices() == null ? 0 : store.devices().size(); settingsDevicesValue.setText("已配对 " + n + " 台电脑"); } private void showSettingsPanel() { refreshSettingsPanel(); showOnly(settingsPanel); } private void hideSettingsPanel() { showConnectPanel(); } ''' def patch(text: str, old: str, new: str, label: str, results: list) -> str: if old not in text: results.append(("✗", label, "找不到锚点")) return text if text.count(old) != 1: results.append(("!", label, "锚点出现 %d 次" % text.count(old))) results.append(("✓", label, "")) return text.replace(old, new, 1) def main() -> int: text = SRC.read_text(encoding="utf-8") results: list = [] # ---- 1. 面板字段声明 ---- text = patch( text, " private FrameLayout lanPanel;", " private FrameLayout lanPanel;\n" " // ---- 设置页(用户要求:各种设置收在一个页面里调)----\n" " private FrameLayout settingsPanel;\n" " private TextView settingsRelayValue;\n" " private TextView settingsCodeValue;\n" " private TextView settingsDevicesValue;", "声明 settingsPanel 和各显示控件", results) # ---- 2. 插入迁移函数(放在 relayUrl 之前)---- text = patch( text, " private String relayUrl() {", MIGRATION + " private String relayUrl() {", "插入 migrateLegacyRelay()", results) # ---- 3. buildSettingsPanel 等方法(插在 buildLanPanel 之前)---- text = patch( text, " private void buildLanPanel() {", SETTINGS_BUILD + " private void buildLanPanel() {", "插入 buildSettingsPanel() 等", results) # ---- 4. 建面板的地方也要建它 ---- text = patch( text, " buildLanPanel();", " buildLanPanel();\n buildSettingsPanel();", "构建时创建 settingsPanel", results) # ---- 5. showOnly 纳入 settingsPanel ---- text = patch( text, " View[] all = {connectPanel, controlPanel, mirrorPanel, lanPanel};", " View[] all = {connectPanel, controlPanel, mirrorPanel, lanPanel,\n" " settingsPanel};", "settingsPanel 纳入 showOnly()", results) # ---- 6. 首页那个按钮改成「设置」 ---- text = patch( text, ''' Button relayButton = bigButton("中转设置(跨网络用)", C_ACCENT, 14);''', ''' Button relayButton = bigButton("设置", C_ACCENT, 14);''', "首页按钮文案改成「设置」", results) text = patch( text, ''' public void onClick(View view) { promptRelayUrl(); } }); box.addView(relayButton);''', ''' public void onClick(View view) { showSettingsPanel(); } }); box.addView(relayButton);''', "首页按钮改成打开设置页", results) # ---- 7. 启动时做迁移 ---- # 找 onCreate 里合适的位置:appendLog 第一次出现之前太早, # 用 "store = " 初始化之后 import re m = re.search(r"( store = new DeviceStore\(this\);\n)", text) if m: text = (text[:m.end()] + " // 老版本硬塞的中转地址要清掉(见 migrateLegacyRelay)\n" " migrateLegacyRelay();\n" + text[m.end():]) results.append(("✓", "启动时调用迁移", "")) else: results.append(("✗", "启动时调用迁移", "找不到 store 初始化那行")) # ---- 输出 ---- print(" ── 补丁结果 ──") for mark, label, note in results: print(" %s %s%s" % (mark, label, (" —— " + note) if note else "")) if any(m == "✗" for m, _l, _n in results): print() print(" 有锚点没匹配,没有写回") return 1 if not any(m == "✗" for m, _l, _n in results): SRC.write_text(text, encoding="utf-8", newline="\n") print() print(" 已写回 MainActivity.java") return 0 if __name__ == "__main__": sys.exit(main())