import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:get/get.dart'; import 'package:nomo/app/constants/api_domains.dart'; import 'package:nomo/app/constants/keys.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:uuid/uuid.dart'; import '../../config/translations/strings_enum.dart'; import '../../pigeons/core_api.g.dart'; import '../../utils/boost_report_manager.dart'; import '../../utils/haptic_feedback_manager.dart'; import '../../utils/log/logger.dart'; import '../../utils/network_helper.dart'; import '../components/ix_snackbar.dart'; import '../constants/enums.dart'; import '../constants/errors.dart'; import '../data/models/api_exception.dart'; import '../data/models/failure.dart'; import '../data/models/vpn_message.dart'; import '../data/sp/ix_sp.dart'; import '../dialog/error_dialog.dart'; import '../dialog/feedback_bottom_sheet.dart'; import 'api_controller.dart'; class CoreController extends GetxService { final TAG = 'CoreController'; final _apiController = Get.find(); final _state = ConnectionState.disconnected.obs; ConnectionState get state => _state.value; set state(ConnectionState value) => _state.value = value; // 公开状态流供外部监听 Rx get stateStream => _state; final _timer = "00:00:00".obs; String get timer => _timer.value; set timer(String value) => _timer.value = value; // VPN 事件流订阅 StreamSubscription? _eventSubscription; CancelToken? _cancelToken; //全局uuid final _globalUuid = Uuid().v4(); String locationSelectionType = 'auto'; @override void onInit() { super.onInit(); _initCheckConnect(); _startListeningToEvents(); } @override void onClose() { super.onClose(); // 取消事件流订阅 _eventSubscription?.cancel(); _eventSubscription = null; } void _initCheckConnect() { CoreApi().isConnected().then((value) { if (value == true) { state = ConnectionState.connected; } else { state = ConnectionState.disconnected; } }); } void handleConnection() { if (state == ConnectionState.disconnected) { // 开始连接 - 轻微震动 state = ConnectionState.connecting; HapticFeedbackManager.connectionStart(); getDispatchInfo(); } else { // 断开连接 CoreApi().disconnect(); } } void selectLocationConnect() { if (state != ConnectionState.disconnected) { CoreApi().disconnect(); // 延迟300ms Future.delayed(const Duration(milliseconds: 300), () { log(TAG, 'selectLocationConnect disconnected = $state'); state = ConnectionState.connecting; getDispatchInfo(); }); } else { handleConnection(); } } Future getDispatchInfo() async { // 如果正在请求中,取消当前请求 if (_cancelToken != null) { log(TAG, '取消当前请求,重新发起新请求'); _cancelToken?.cancel('取消旧请求,发起新请求'); } // 创建新的 CancelToken final currentToken = CancelToken(); _cancelToken = currentToken; // 创建一条加速日志 await createBoostLog(); try { final locationId = IXSP.getSelectedLocation()?['id']; final locationCode = IXSP.getSelectedLocation()?['code']; final launch = await _apiController.getDispatchInfo( locationId, locationCode, cancelToken: currentToken, ); // 只有当前 token 没有被替换时才清空 if (_cancelToken == currentToken) { _cancelToken = null; } final remainTime = launch.userConfig?.remainTime ?? 0; if (remainTime <= 0) { _onVpnError(Errors.ERROR_REMAIN_TIME, Strings.remainTimeEnded.tr); return; } if (state == ConnectionState.connecting) { final sessionId = Uuid().v4(); final socksPort = launch.nodesConfig!.socketPort!; final tunnelConfig = launch.nodesConfig!.tunnelConfig!; final configJson = jsonEncode(launch.nodesConfig!); // 根据分流隧道设置获取 allowVpnApps 和 disallowVpnApps final splitTunnelingApps = _getSplitTunnelingApps(); final allowVpnApps = splitTunnelingApps['allowVpnApps']!; final disallowVpnApps = splitTunnelingApps['disallowVpnApps']!; final accessToken = launch.userConfig?.accessToken ?? ''; final peekTimeInterval = launch.appConfig?.peekTimeInterval ?? 600; final aesKey = Keys.aesKey; final aesIv = Keys.aesIv; final baseUrls = ApiDomains.instance.getAllLogUrls(); final params = jsonEncode(_apiController.fp); CoreApi().connect( sessionId, socksPort, tunnelConfig, configJson, remainTime, false, allowVpnApps, disallowVpnApps, accessToken, aesKey, aesIv, locationId, locationCode, baseUrls, params, peekTimeInterval, ); } } on DioException catch (e, s) { // 只有当前 token 没有被替换时才清空 if (_cancelToken == currentToken) { _cancelToken = null; } // 如果是取消错误,不处理 if (e.type == DioExceptionType.cancel) { log(TAG, '请求已取消'); return; } if (state == ConnectionState.connecting) { state = ConnectionState.disconnected; } handleErrorDialog(e, s); log(TAG, 'getDispatchInfo error: $e'); } catch (e, s) { // 只有当前 token 没有被替换时才清空 if (_cancelToken == currentToken) { _cancelToken = null; } if (state == ConnectionState.connecting) { state = ConnectionState.disconnected; } handleErrorDialog(e, s); log(TAG, 'getDispatchInfo error: $e'); } } /// 根据分流隧道设置获取 allowVpnApps 和 disallowVpnApps /// 直接从 IXSP 读取,参考 SplittunnelingController 的逻辑 Map> _getSplitTunnelingApps() { List allowVpnApps = []; List disallowVpnApps = []; try { // 读取选中的模式 final modeString = IXSP.getString('splittunneling_selected_mode'); if (modeString == null) { log(TAG, '分流隧道未设置模式'); return { 'allowVpnApps': allowVpnApps, 'disallowVpnApps': disallowVpnApps, }; } // 判断模式类型 final isExcludeMode = modeString.contains('exclude'); final isIncludeMode = modeString.contains('include'); if (!isExcludeMode && !isIncludeMode) { log(TAG, '分流隧道模式为 none'); return { 'allowVpnApps': allowVpnApps, 'disallowVpnApps': disallowVpnApps, }; } // 根据模式获取对应的应用列表 final key = isExcludeMode ? 'splittunneling_exclude_selected_apps' : 'splittunneling_include_selected_apps'; final selectedAppsJson = IXSP.getString(key); if (selectedAppsJson != null) { final selectedPackageNames = (jsonDecode(selectedAppsJson) as List).cast(); if (isIncludeMode) { // include 模式:只有选中的应用走 VPN allowVpnApps = selectedPackageNames; log(TAG, '分流隧道 include 模式,允许的应用: $allowVpnApps'); } else { // exclude 模式:选中的应用不走 VPN disallowVpnApps = selectedPackageNames; log(TAG, '分流隧道 exclude 模式,排除的应用: $disallowVpnApps'); } } } catch (e) { log(TAG, '获取分流隧道设置失败: $e'); } return {'allowVpnApps': allowVpnApps, 'disallowVpnApps': disallowVpnApps}; } /// 开始监听来自 Android 的事件 void _startListeningToEvents() { _eventSubscription = onEventChange().listen( _handleEventChange, onError: (error) { log(TAG, '事件流错误: $error'); }, ); } // 处理从原生端接收到的消息 void _handleEventChange(String message) { try { final Map json = jsonDecode(message); final String type = json['type'] ?? ''; switch (type) { case 'vpn_status': _handleVpnStatus(VpnStatusMessage.fromJson(json)); break; case 'timer_update': _handleTimerUpdate(TimerUpdateMessage.fromJson(json)); break; case 'boost_result': _handleBoostResult(BoostResultMessage.fromJson(json)); break; default: log(TAG, '未知消息类型: $type'); } } catch (e) { log(TAG, '解析消息失败: $e'); } } void _handleVpnStatus(VpnStatusMessage message) { final vpnError = VpnStatus.fromValue(message.status); log( TAG, 'VPN状态变化: ${vpnError.label}, status=${message.status}, code=${message.code}, message=${message.message}', ); // 根据状态码处理不同的VPN状态 switch (vpnError) { case VpnStatus.idle: // disconnected _onVpnDisconnected(); break; case VpnStatus.connecting: // connecting _onVpnConnecting(); break; case VpnStatus.connected: // connected _onVpnConnected(); break; case VpnStatus.error: // error _onVpnError(message.code, message.message); break; } } void _handleTimerUpdate(TimerUpdateMessage message) { // log( // TAG, // '计时更新: time=${message.currentTime}, mode=${message.mode}, running=${message.isRunning}, paused=${message.isPaused}', // ); timer = _formatTime(message.currentTime); // 处理计时更新 if (message.isRunning) { if (message.isPaused) { _onTimerPaused(message.currentTime, message.mode); } else { _onTimerRunning(message.currentTime, message.mode); } } else { _onTimerStopped(); } } void _handleBoostResult(BoostResultMessage message) async { log(TAG, '加速结果: ${message.toString()}'); if (message.success) { try { await _apiController.connected({ 'locationCode': message.locationCode, 'instanceId': message.nodeId, }); } catch (e) { log('handleRouterConnected error: $e'); } } try { final json = jsonDecode(message.param); await _apiController.uploadLogs(json); } catch (e) { log('handleBoostResult error: $e'); } } // VPN状态处理方法 void _onVpnDisconnected() { log(TAG, 'VPN已断开连接'); // 更新UI状态 _uninitState(); // FeedbackBottomSheet.show(); } void _onVpnConnecting() { log(TAG, 'VPN正在连接'); // 显示连接中状态 state = ConnectionState.connecting; } void _onVpnConnected() { log(TAG, 'VPN已连接'); // 显示已连接状态 state = ConnectionState.connected; HapticFeedbackManager.connectionSuccess(); } void _onVpnError(int code, String message) { log(TAG, 'VPN连接错误: code=$code, message=$message'); // 显示错误信息 _uninitState(); showErrorDialog(code, message); } void showErrorDialog(int code, String message) { var errorMessage = message; switch (code) { case Errors.ERROR_NODE_TIMEOUT: errorMessage = Strings.vpnConnectionTimeoutError.tr; break; case Errors.ERROR_NO_NODE: errorMessage = Strings.vpnNoNodeError.tr; break; case Errors.ERROR_INIT: errorMessage = Strings.vpnInitError.tr; break; case Errors.ERROR_KILL: errorMessage = Strings.vpnKillError.tr; break; case Errors.ERROR_REVOKE: errorMessage = Strings.vpnRevokeError.tr; break; case Errors.ERROR_SERVICE_EMPTY: errorMessage = Strings.vpnServiceEmptyError.tr; break; case Errors.ERROR_ROUTER: errorMessage = Strings.vpnRouterError.tr; break; case Errors.ERROR_PERMISSION_DENIED: errorMessage = Strings.vpnPermissionDeniedError.tr; break; } if (errorMessage.isNotEmpty) { ErrorDialog.show(message: errorMessage); } } void _uninitState() { if (state != ConnectionState.disconnected) { state = ConnectionState.disconnected; timer = "00:00:00"; HapticFeedbackManager.connectionDisconnected(); } } // 计时器状态处理方法 void _onTimerRunning(int currentTime, int mode) { log( TAG, '计时器运行中: ${_formatTime(currentTime)}, 模式: ${mode == 0 ? "普通计时" : "倒计时"}', ); } void _onTimerPaused(int currentTime, int mode) { log( TAG, '计时器已暂停: ${_formatTime(currentTime)}, 模式: ${mode == 0 ? "普通计时" : "倒计时"}', ); } void _onTimerStopped() { log(TAG, '计时器已停止'); } // 格式化时间显示 String _formatTime(int timeMs) { final totalSeconds = (timeMs / 1000).abs().round(); final hours = totalSeconds ~/ 3600; final minutes = (totalSeconds % 3600) ~/ 60; final seconds = totalSeconds % 60; return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; } void handleSnackBarError(dynamic error, StackTrace stackTrace) { if (error is ApiException) { IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: error.message, ); } else if (error is Failure) { IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: error.message ?? Strings.unknownError.tr, ); } else if (error is DioException) { switch (error.type) { case DioExceptionType.connectionError: case DioExceptionType.connectionTimeout: case DioExceptionType.receiveTimeout: case DioExceptionType.sendTimeout: IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: Strings.unableToConnectNetwork.tr, ); break; default: IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: Strings.unableToConnectServer.tr, ); } } else { IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: error.toString(), ); } } void handleErrorDialog(dynamic error, StackTrace stackTrace) { if (error is ApiException) { ErrorDialog.show(title: Strings.error.tr, message: error.message); } else if (error is Failure) { ErrorDialog.show( title: Strings.error.tr, message: error.message ?? Strings.unknownError.tr, ); } else if (error is DioException) { switch (error.type) { case DioExceptionType.connectionError: case DioExceptionType.connectionTimeout: case DioExceptionType.receiveTimeout: case DioExceptionType.sendTimeout: ErrorDialog.show( title: Strings.error.tr, message: Strings.unableToConnectNetwork.tr, ); break; default: ErrorDialog.show( title: Strings.error.tr, message: Strings.unableToConnectServer.tr, ); } } else { ErrorDialog.show(title: Strings.error.tr, message: error.toString()); } } // 创建一条加速日志 Future createBoostLog() async { await initLog(); await setSessionInfoLog(); await setTargetInfoLog(); } // 初始化日志 Future initLog() async { await BoostReportManager().init(); } // 读取历史日志 Future readHistoryLog() async { await BoostReportManager().readHistoryLog(); } // 初始化会话日志 Future setSessionInfoLog() async { final deviceInfoPlugin = DeviceInfoPlugin(); final appVersion = await PackageInfo.fromPlatform().then( (value) => value.version, ); final networkType = await NetworkHelper.instance.getNetworkType(); Map deviceInfo = {}; if (Platform.isIOS) { final iosOsInfo = await deviceInfoPlugin.iosInfo; deviceInfo = { 'deviceModel': iosOsInfo.model, 'osVersion': iosOsInfo.systemVersion, 'appVersion': appVersion, 'networkType': networkType, 'deviceBrand': iosOsInfo.utsname.machine, }; } else if (Platform.isAndroid) { final androidOsInfo = await deviceInfoPlugin.androidInfo; deviceInfo = { 'deviceModel': androidOsInfo.model, 'osVersion': androidOsInfo.version.release, 'appVersion': appVersion, 'networkType': networkType, 'deviceBrand': androidOsInfo.brand, }; } final boostSessionId = Uuid().v4(); await BoostReportManager().initSessionInfo( appSessionId: _globalUuid, boostSessionId: boostSessionId, deviceInfo: deviceInfo, ); } // 初始化目标信息 Future setTargetInfoLog() async { await BoostReportManager().addTargetInfo( locationSelectionType: locationSelectionType, location: IXSP.getSelectedLocation(), ); } }