feat(client): 自动连接开关 + Windows 开机自启改任务计划程序
- 新增「自动连接」设置开关(默认关,持久化):启动 app 进登录态主界面后,等节点就绪 自动连一次(_RootFlowState 一次性 listenManual nodesProvider → toggle())。全平台生效。 - Windows 开机自启改用 schtasks /rl highest 登录触发任务:本应用 requireAdministrator, 原注册表 Run 键(非提权)开机会弹 UAC/起不来 → 任务计划静默提权拉起。macOS/Linux 仍用 launch_at_startup 登录项。 - 设置页功能组加「自动连接」行;新增 autoConnect/autoConnectSub 文案。 - _load 加 mounted 守卫(异步初始化期间已 dispose 不再写 state)。 - 测试:autoConnect 默认关 / _load 读持久化 / setAutoConnect 落盘。 注:移动端真·常连(Android Always-on + iOS/iPad On-Demand,无 UI 自连)是更大的原生活, 已单列 todo #14 后续做;本轮移动端只有「打开 app 自动连接」。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
// settings_autoconnect_test.dart — 自动连接开关:默认关 + 改状态并持久化(重启恢复)。
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:pangolin_vpn/bridge/vpn_bridge_mock.dart';
|
||||
import 'package:pangolin_vpn/bridge/vpn_bridge_provider.dart';
|
||||
import 'package:pangolin_vpn/state/settings_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
setUp(() {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
// 桌面(mac 测试机)构造时会跑 _applyAutostart → PackageInfo.fromPlatform;
|
||||
// mock 掉避免无插件挂起、_load 拖到 dispose 之后报「used after dispose」。
|
||||
PackageInfo.setMockInitialValues(
|
||||
appName: 'Pangolin', packageName: 'com.pangolin',
|
||||
version: '1.0.0', buildNumber: '1', buildSignature: '',
|
||||
);
|
||||
});
|
||||
|
||||
ProviderContainer makeContainer() => ProviderContainer(overrides: [
|
||||
vpnBridgeProvider.overrideWithValue(VpnBridgeMock()),
|
||||
]);
|
||||
|
||||
// 轮询等 _load() 把持久化值读进 state(异步,首次 SharedPreferences 初始化可能偏慢)。
|
||||
Future<void> waitAutoConnect(ProviderContainer c, bool want) async {
|
||||
for (var i = 0; i < 60; i++) {
|
||||
if (c.read(settingsProvider).autoConnect == want) return;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
}
|
||||
}
|
||||
|
||||
test('autoConnect 默认关(const 默认 + 空存储加载)', () async {
|
||||
expect(const AppSettings().autoConnect, isFalse);
|
||||
final c = makeContainer();
|
||||
addTearDown(c.dispose);
|
||||
await waitAutoConnect(c, false);
|
||||
expect(c.read(settingsProvider).autoConnect, isFalse);
|
||||
});
|
||||
|
||||
test('_load 从持久化恢复 autoConnect=true', () async {
|
||||
SharedPreferences.setMockInitialValues({'set_auto_connect': true});
|
||||
final c = makeContainer();
|
||||
addTearDown(c.dispose);
|
||||
await waitAutoConnect(c, true);
|
||||
expect(c.read(settingsProvider).autoConnect, isTrue, reason: '应读出持久化值');
|
||||
});
|
||||
|
||||
test('setAutoConnect 写入持久化存储', () async {
|
||||
final c = makeContainer();
|
||||
addTearDown(c.dispose);
|
||||
await waitAutoConnect(c, false); // 等初次加载安定
|
||||
await c.read(settingsProvider.notifier).setAutoConnect(true);
|
||||
// 校验落盘(状态可能被并发的初次 _load 覆盖,但存储一定被写入)。
|
||||
final p = await SharedPreferences.getInstance();
|
||||
expect(p.getBool('set_auto_connect'), isTrue, reason: '应持久化');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user