| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- 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: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 '../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<ApiController>();
- final _state = ConnectionState.disconnected.obs;
- ConnectionState get state => _state.value;
- set state(ConnectionState value) => _state.value = value;
- // 公开状态流供外部监听
- Rx<ConnectionState> get stateStream => _state;
- final _timer = "00:00:00".obs;
- String get timer => _timer.value;
- set timer(String value) => _timer.value = value;
- // VPN 事件流订阅
- StreamSubscription<String>? _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;
- CoreApi().reconnect();
- } 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), () {
- state = ConnectionState.connecting;
- getDispatchInfo();
- });
- } else {
- handleConnection();
- }
- }
- Future<void> 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;
- }
- if (state == ConnectionState.connecting) {
- final sessionId = Uuid().v4();
- final socksPort = launch.nodesConfig!.socketPort!;
- final tunnelConfig = launch.nodesConfig!.tunnelConfig!;
- final configJson = jsonEncode(launch.nodesConfig!);
- CoreApi().connect(sessionId, socksPort, tunnelConfig, configJson);
- }
- } 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');
- }
- }
- /// 开始监听来自 Android 的事件
- void _startListeningToEvents() {
- _eventSubscription = onEventChange().listen(
- _handleEventChange,
- onError: (error) {
- log(TAG, '事件流错误: $error');
- },
- );
- }
- // 处理从原生端接收到的消息
- void _handleEventChange(String message) {
- try {
- final Map<String, dynamic> 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;
- 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}, 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.message);
- break;
- case VpnStatus.serviceDisconnected:
- // service disconnected
- _onVpnServiceDisconnected();
- break;
- case VpnStatus.permissionDenied:
- // permission denied
- _onVpnPermissionDenied();
- 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();
- }
- }
- // VPN状态处理方法
- void _onVpnDisconnected() {
- log(TAG, 'VPN已断开连接');
- // 更新UI状态
- state = ConnectionState.disconnected;
- timer = "00:00:00";
- HapticFeedbackManager.connectionDisconnected();
- FeedbackBottomSheet.show();
- }
- void _onVpnConnecting() {
- log(TAG, 'VPN正在连接');
- // 显示连接中状态
- state = ConnectionState.connecting;
- }
- void _onVpnConnected() {
- log(TAG, 'VPN已连接');
- // 显示已连接状态
- state = ConnectionState.connected;
- HapticFeedbackManager.connectionSuccess();
- }
- void _onVpnError(String message) {
- log(TAG, 'VPN连接错误');
- // 显示错误信息
- state = ConnectionState.disconnected;
- timer = "00:00:00";
- HapticFeedbackManager.connectionDisconnected();
- ErrorDialog.show(
- message: message == 'null' ? Strings.vpnConnectionError.tr : message,
- );
- }
- void _onVpnServiceDisconnected() {
- log(TAG, 'VPN服务异常断开连接');
- // 显示错误信息
- state = ConnectionState.disconnected;
- timer = "00:00:00";
- HapticFeedbackManager.connectionDisconnected();
- // 可以显示错误提示
- ErrorDialog.show(message: Strings.vpnServiceDisconnected.tr);
- }
- void _onVpnPermissionDenied() {
- log(TAG, 'VPN权限拒绝');
- // 显示权限拒绝状态
- state = ConnectionState.disconnected;
- HapticFeedbackManager.connectionDisconnected();
- // 可以显示错误提示
- ErrorDialog.show(message: '权限拒绝');
- }
- // 计时器状态处理方法
- 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 days = totalSeconds ~/ 86400; // 86400 = 24 * 3600
- final hours = (totalSeconds % 86400) ~/ 3600;
- final minutes = (totalSeconds % 3600) ~/ 60;
- final seconds = totalSeconds % 60;
- if (days > 0) {
- return '$days days ${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
- } else if (hours > 0) {
- return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
- } else {
- return '00:${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<void> createBoostLog() async {
- await initLog();
- await setSessionInfoLog();
- await setTargetInfoLog();
- }
- // 初始化日志
- Future<void> initLog() async {
- await BoostReportManager().init();
- }
- // 读取历史日志
- Future<void> readHistoryLog() async {
- await BoostReportManager().readHistoryLog();
- }
- // 初始化会话日志
- Future<void> setSessionInfoLog() async {
- final deviceInfoPlugin = DeviceInfoPlugin();
- final appVersion = await PackageInfo.fromPlatform().then(
- (value) => value.version,
- );
- final networkType = await NetworkHelper.instance.getNetworkType();
- Map<String, String> 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<void> setTargetInfoLog() async {
- await BoostReportManager().addTargetInfo(
- locationSelectionType: locationSelectionType,
- location: IXSP.getSelectedLocation(),
- );
- }
- }
|