- CommandReceiver: exported broadcast receiver for adb CLI control - start/stop/status/set_config commands - Usage: adb shell am broadcast -a com.sing.vpn.CMD -n com.sing.vpn/.CommandReceiver --es cmd start - SingConfig: add controlplane.tailscale.com → direct route rule to prevent tailnet control traffic from going through proxy
2.5 KiB
Code Review — Android Compose Migration — 2026-04-02
Summary
XML layouts + Fragments 全部迁移到 Jetpack Compose。12 个新文件,删除 2596 行 XML/Fragment 代码。Linear 深色主题落地,组件库复用良好。
整体评价
做得好:
- 色板、字体、组件库完全按 REDESIGN.md 规范
LaunchedEffect+mutableStateOf状态管理干净SectionCard,KeyValueRow,ToggleRow,NavigationRow复用率高- Bottom sheet 替代 expand,交互更现代
- LogsScreen 增量读取 (
RandomAccessFile+ delta) 设计巧妙 - ConfigScreen 分组列表紧凑,inline dropdown 比 Spinner 更好
Bugs
B1. LogsScreen 重复定义 InlineDivider
ui/screens/LogsScreen.kt:126-128 定义了私有 InlineDivider,但 ui/components/Components.kt:162-168 已有公共版本。
Fix: 删除 LogsScreen 中的私有版本,import 公共组件。
B2. ConfigScreen 双重 save
每个 onValueChange 调用 save(),DisposableEffect(Unit) 的 onDispose 也调 save()。每次输入字符都写全量 SharedPreferences,不必要。
Fix: 去掉 onValueChange 里的 save() 调用,只保留 DisposableEffect 的 onDispose { save() }。对于 toggle 类(立即生效),单独 prefs.edit().putBoolean(...).apply()。
B3. OverviewScreen recentConnections 缺 key
OverviewScreen.kt:169 的 items(recentConnections.take(5)) 没有 key 参数,列表更新时 Compose 无法高效 diff,导致不必要的重组。
Fix: 添加 key = { it.host } 或用 index。
Improvements
I1. 字符串硬编码
所有 UI 文字直接写在 Compose 代码里("Paste URI", "Subscription URL", "Not connected" 等)。strings.xml 已被清空。
Impact: 不影响功能,但阻碍了国际化。后续统一迁移到 stringResource()。
I2. fragment-ktx 依赖可移除
Compose 迁移后不再需要 androidx.fragment:fragment-ktx。build.gradle.kts 中已移除,确认无残留引用即可。
I3. NodesScreen 函数过长
NodesScreen.kt 单个 Composable 约 400 行,包含 bottom sheet、import 逻辑、tab 切换。
Suggestion: 拆分为 NodeActionSheet、ImportSheet、ProxyNodeList、TailscalePeerList 独立 Composable。
I4. fetchTrafficStats / fetchConnections 重复
OverviewScreen.kt:289 和 ConnectionsScreen.kt:141 各自实现了 Clash API 的 /connections 解析,逻辑几乎相同。
Suggestion: 抽取到 data/ClashApi.kt 统一调用。