// connection_provider.dart — 连接状态机(严格三态,禁止乐观显示) // // 设计约定(design/CLAUDE.md §2/§5):连接键三态严格对应状态层事件, // UI 只渲染本通知器的真实状态,绝不在点击时本地乐观翻转。连接握手由 // 状态层(此处为 mock 计时器,后续替换为隧道事件)决定何时进入 on。 import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; /// 连接阶段。严格三态——与 React 原型一致。 enum VpnPhase { off, connecting, on } /// 连接状态快照。 class ConnectionState { const ConnectionState({required this.phase, this.elapsed = Duration.zero}); final VpnPhase phase; final Duration elapsed; ConnectionState copyWith({VpnPhase? phase, Duration? elapsed}) => ConnectionState(phase: phase ?? this.phase, elapsed: elapsed ?? this.elapsed); @override bool operator ==(Object other) => other is ConnectionState && other.phase == phase && other.elapsed == elapsed; @override int get hashCode => Object.hash(phase, elapsed); } /// 连接状态机。握手时长可注入,便于测试确定化。 class ConnectionController extends StateNotifier { ConnectionController({this.handshake = const Duration(milliseconds: 1500)}) : super(const ConnectionState(phase: VpnPhase.off)); final Duration handshake; Timer? _handshakeTimer; Timer? _tick; /// 用户轻点连接键:仅依据真实状态决定动作,握手中点击被忽略(禁乐观)。 void toggle() { switch (state.phase) { case VpnPhase.off: connect(); case VpnPhase.on: disconnect(); case VpnPhase.connecting: break; // 握手进行中,不响应——避免乐观回退 } } void connect() { _cancelTimers(); state = const ConnectionState(phase: VpnPhase.connecting); _handshakeTimer = Timer(handshake, _onConnected); } void disconnect() { _cancelTimers(); state = const ConnectionState(phase: VpnPhase.off); } /// 切换节点时触发:已连接则重连(短暂回到 connecting)。 void onNodeChanged() { if (state.phase == VpnPhase.on || state.phase == VpnPhase.connecting) { connect(); } } void _onConnected() { state = const ConnectionState(phase: VpnPhase.on); _tick = Timer.periodic(const Duration(seconds: 1), (_) { state = state.copyWith(elapsed: state.elapsed + const Duration(seconds: 1)); }); } void _cancelTimers() { _handshakeTimer?.cancel(); _handshakeTimer = null; _tick?.cancel(); _tick = null; } @override void dispose() { _cancelTimers(); super.dispose(); } } final connectionProvider = StateNotifierProvider( (ref) => ConnectionController(), );