core_controller.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:dio/dio.dart';
  4. import 'package:get/get.dart';
  5. import 'package:uuid/uuid.dart';
  6. import '../../pigeons/core_api.g.dart';
  7. import '../../utils/haptic_feedback_manager.dart';
  8. import '../../utils/log/logger.dart';
  9. import '../constants/enums.dart';
  10. import '../data/models/vpn_message.dart';
  11. import '../data/sp/ix_sp.dart';
  12. import '../dialog/error_dialog.dart';
  13. import '../dialog/feedback_bottom_sheet.dart';
  14. import 'api_controller.dart';
  15. class CoreController extends GetxService {
  16. final TAG = 'CoreController';
  17. final _apiController = Get.find<ApiController>();
  18. final _state = ConnectionState.disconnected.obs;
  19. ConnectionState get state => _state.value;
  20. set state(ConnectionState value) => _state.value = value;
  21. final _timer = "00:00:00".obs;
  22. String get timer => _timer.value;
  23. set timer(String value) => _timer.value = value;
  24. // VPN 事件流订阅
  25. StreamSubscription<String>? _eventSubscription;
  26. CancelToken? _cancelToken;
  27. @override
  28. void onInit() {
  29. super.onInit();
  30. _initCheckConnect();
  31. _startListeningToEvents();
  32. }
  33. @override
  34. void onClose() {
  35. super.onClose();
  36. // 取消事件流订阅
  37. _eventSubscription?.cancel();
  38. _eventSubscription = null;
  39. }
  40. void _initCheckConnect() {
  41. CoreApi().isConnected().then((value) {
  42. if (value == true) {
  43. state = ConnectionState.connected;
  44. CoreApi().reconnect();
  45. } else {
  46. state = ConnectionState.disconnected;
  47. }
  48. });
  49. }
  50. void handleConnection() {
  51. if (state == ConnectionState.disconnected) {
  52. // 开始连接 - 轻微震动
  53. state = ConnectionState.connecting;
  54. HapticFeedbackManager.connectionStart();
  55. getDispatchInfo();
  56. } else {
  57. // 断开连接
  58. CoreApi().disconnect();
  59. }
  60. }
  61. void selectLocationConnect() {
  62. if (state != ConnectionState.disconnected) {
  63. CoreApi().disconnect();
  64. // 延迟300ms
  65. Future.delayed(const Duration(milliseconds: 300), () {
  66. state = ConnectionState.connecting;
  67. getDispatchInfo();
  68. });
  69. } else {
  70. handleConnection();
  71. }
  72. }
  73. Future<void> getDispatchInfo() async {
  74. // 如果正在请求中,取消当前请求
  75. if (_cancelToken != null) {
  76. log(TAG, '取消当前请求,重新发起新请求');
  77. _cancelToken?.cancel('取消旧请求,发起新请求');
  78. }
  79. // 创建新的 CancelToken
  80. final currentToken = CancelToken();
  81. _cancelToken = currentToken;
  82. try {
  83. final locationId = IXSP.getSelectedLocation()?['id'];
  84. final locationCode = IXSP.getSelectedLocation()?['code'];
  85. final launch = await _apiController.getDispatchInfo(
  86. locationId,
  87. locationCode,
  88. cancelToken: currentToken,
  89. );
  90. // 只有当前 token 没有被替换时才清空
  91. if (_cancelToken == currentToken) {
  92. _cancelToken = null;
  93. }
  94. if (state == ConnectionState.connecting) {
  95. final sessionId = Uuid().v4();
  96. final socksPort = launch.socksPort!;
  97. final tunnelConfig = launch.tunnelConfig!;
  98. final configJson = jsonEncode(launch.nodes);
  99. CoreApi().connect(sessionId, socksPort, tunnelConfig, configJson);
  100. }
  101. } on DioException catch (e) {
  102. // 只有当前 token 没有被替换时才清空
  103. if (_cancelToken == currentToken) {
  104. _cancelToken = null;
  105. }
  106. // 如果是取消错误,不处理
  107. if (e.type == DioExceptionType.cancel) {
  108. log(TAG, '请求已取消');
  109. return;
  110. }
  111. if (state == ConnectionState.connecting) {
  112. state = ConnectionState.disconnected;
  113. }
  114. log(TAG, 'getDispatchInfo error: $e');
  115. } catch (e) {
  116. // 只有当前 token 没有被替换时才清空
  117. if (_cancelToken == currentToken) {
  118. _cancelToken = null;
  119. }
  120. if (state == ConnectionState.connecting) {
  121. state = ConnectionState.disconnected;
  122. }
  123. log(TAG, 'getDispatchInfo error: $e');
  124. }
  125. }
  126. /// 开始监听来自 Android 的事件
  127. void _startListeningToEvents() {
  128. _eventSubscription = onEventChange().listen(
  129. _handleEventChange,
  130. onError: (error) {
  131. log(TAG, '事件流错误: $error');
  132. },
  133. );
  134. }
  135. // 处理从原生端接收到的消息
  136. void _handleEventChange(String message) {
  137. try {
  138. final Map<String, dynamic> json = jsonDecode(message);
  139. final String type = json['type'] ?? '';
  140. switch (type) {
  141. case 'vpn_status':
  142. _handleVpnStatus(VpnStatusMessage.fromJson(json));
  143. break;
  144. case 'timer_update':
  145. _handleTimerUpdate(TimerUpdateMessage.fromJson(json));
  146. break;
  147. default:
  148. log(TAG, '未知消息类型: $type');
  149. }
  150. } catch (e) {
  151. log(TAG, '解析消息失败: $e');
  152. }
  153. }
  154. void _handleVpnStatus(VpnStatusMessage message) {
  155. final vpnError = VpnStatus.fromValue(message.status);
  156. log(TAG, 'VPN状态变化: ${vpnError.label}, message=${message.message}');
  157. // 根据状态码处理不同的VPN状态
  158. switch (vpnError) {
  159. case VpnStatus.idle:
  160. // disconnected
  161. _onVpnDisconnected();
  162. break;
  163. case VpnStatus.connecting:
  164. // connecting
  165. _onVpnConnecting();
  166. break;
  167. case VpnStatus.connected:
  168. // connected
  169. _onVpnConnected();
  170. break;
  171. case VpnStatus.error:
  172. // error
  173. _onVpnError();
  174. break;
  175. case VpnStatus.serviceDisconnected:
  176. // service disconnected
  177. _onVpnServiceDisconnected();
  178. break;
  179. case VpnStatus.permissionDenied:
  180. // permission denied
  181. _onVpnPermissionDenied();
  182. break;
  183. }
  184. }
  185. void _handleTimerUpdate(TimerUpdateMessage message) {
  186. log(
  187. TAG,
  188. '计时更新: time=${message.currentTime}, mode=${message.mode}, running=${message.isRunning}, paused=${message.isPaused}',
  189. );
  190. timer = _formatTime(message.currentTime);
  191. // 处理计时更新
  192. if (message.isRunning) {
  193. if (message.isPaused) {
  194. _onTimerPaused(message.currentTime, message.mode);
  195. } else {
  196. _onTimerRunning(message.currentTime, message.mode);
  197. }
  198. } else {
  199. _onTimerStopped();
  200. }
  201. }
  202. // VPN状态处理方法
  203. void _onVpnDisconnected() {
  204. log(TAG, 'VPN已断开连接');
  205. // 更新UI状态
  206. state = ConnectionState.disconnected;
  207. timer = "00:00:00";
  208. HapticFeedbackManager.connectionDisconnected();
  209. // FeedbackBottomSheet.show();
  210. }
  211. void _onVpnConnecting() {
  212. log(TAG, 'VPN正在连接');
  213. // 显示连接中状态
  214. state = ConnectionState.connecting;
  215. }
  216. void _onVpnConnected() {
  217. log(TAG, 'VPN已连接');
  218. // 显示已连接状态
  219. state = ConnectionState.connected;
  220. HapticFeedbackManager.connectionSuccess();
  221. }
  222. void _onVpnError() {
  223. log(TAG, 'VPN连接错误');
  224. // 显示错误信息
  225. state = ConnectionState.disconnected;
  226. timer = "00:00:00";
  227. HapticFeedbackManager.connectionDisconnected();
  228. }
  229. void _onVpnServiceDisconnected() {
  230. log(TAG, 'VPN服务异常断开连接');
  231. // 显示错误信息
  232. state = ConnectionState.disconnected;
  233. timer = "00:00:00";
  234. HapticFeedbackManager.connectionDisconnected();
  235. // 可以显示错误提示
  236. ErrorDialog.show(message: 'VPN服务异常断开连接');
  237. }
  238. void _onVpnPermissionDenied() {
  239. log(TAG, 'VPN权限拒绝');
  240. // 显示权限拒绝状态
  241. state = ConnectionState.disconnected;
  242. HapticFeedbackManager.connectionDisconnected();
  243. // 可以显示错误提示
  244. ErrorDialog.show(message: '权限拒绝');
  245. }
  246. // 计时器状态处理方法
  247. void _onTimerRunning(int currentTime, int mode) {
  248. log(
  249. TAG,
  250. '计时器运行中: ${_formatTime(currentTime)}, 模式: ${mode == 0 ? "普通计时" : "倒计时"}',
  251. );
  252. }
  253. void _onTimerPaused(int currentTime, int mode) {
  254. log(
  255. TAG,
  256. '计时器已暂停: ${_formatTime(currentTime)}, 模式: ${mode == 0 ? "普通计时" : "倒计时"}',
  257. );
  258. }
  259. void _onTimerStopped() {
  260. log(TAG, '计时器已停止');
  261. }
  262. // 格式化时间显示
  263. String _formatTime(int timeMs) {
  264. final totalSeconds = (timeMs / 1000).abs().round();
  265. final days = totalSeconds ~/ 86400; // 86400 = 24 * 3600
  266. final hours = (totalSeconds % 86400) ~/ 3600;
  267. final minutes = (totalSeconds % 3600) ~/ 60;
  268. final seconds = totalSeconds % 60;
  269. if (days > 0) {
  270. return '$days days ${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
  271. } else if (hours > 0) {
  272. return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
  273. } else {
  274. return '00:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
  275. }
  276. }
  277. }