Просмотр исходного кода

fix: 更新so,更新主页布局,增加core_api基类

lilu 3 месяцев назад
Родитель
Сommit
1487eadcdc

BIN
android/app/libs/arm64-v8a/libhev-socks5-tunnel.so


BIN
android/app/libs/armeabi-v7a/libhev-socks5-tunnel.so


BIN
android/app/libs/libxray.aar


BIN
assets/images/identity/test.png


+ 2 - 2
lib/app/base/base_view.dart

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 import '../../config/theme/light_theme_colors.dart';
 import '../../config/theme/theme_extensions/theme_extension.dart';
-import '../../pigeons/core_api.g.dart';
+import '../controllers/base_core_api.dart';
 
 abstract class BaseView<T> extends GetView<T> {
   const BaseView({super.key});
@@ -27,7 +27,7 @@ abstract class BaseView<T> extends GetView<T> {
   // 添加这个方法来处理返回事件
   void onPopInvoked(bool didPop) {
     if (!didPop) {
-      CoreApi().moveTaskToBack();
+      BaseCoreApi().moveTaskToBack();
     }
   }
 

+ 5 - 0
lib/app/constants/assets.dart

@@ -119,4 +119,9 @@ class Assets {
       'assets/images/round/connected_switch.png';
   static const String disconnectedSwitch =
       'assets/images/round/disconnected_switch.png';
+
+  // home页的会员
+  static const String homePremium = 'assets/images/identity/premium.png';
+  static const String homeTest = 'assets/images/identity/test.png';
+  static const String homeFree = 'assets/images/identity/free.png';
 }

+ 8 - 8
lib/app/controllers/api_controller.dart

@@ -15,7 +15,7 @@ import 'package:play_install_referrer/play_install_referrer.dart';
 
 import '../../config/translations/localization_service.dart';
 import '../../config/translations/strings_enum.dart';
-import '../../pigeons/core_api.g.dart';
+import 'base_core_api.dart';
 import '../../utils/api_statistics.dart';
 import '../../utils/device_manager.dart';
 import '../../utils/geo_downloader.dart';
@@ -107,7 +107,7 @@ class ApiController extends GetxService with WidgetsBindingObserver {
 
   @override
   void didChangeAppLifecycleState(AppLifecycleState state) {
-    log("App state: $state");
+    log(TAG, "App state: $state");
     if (state == AppLifecycleState.paused) {
       isBackground = true;
       ApiStatistics.instance.onAppPaused();
@@ -126,14 +126,14 @@ class ApiController extends GetxService with WidgetsBindingObserver {
       fp.channel = 'apple';
     } else if (Platform.isAndroid) {
       try {
-        final channel = await CoreApi().getChannel();
+        final channel = await BaseCoreApi().getChannel();
         fp.channel = channel ?? 'unknown';
       } catch (e) {
         log(TAG, 'read app channel error: $e');
         fp.channel = '';
       }
       try {
-        final advertisingId = await CoreApi().getAdvertisingId();
+        final advertisingId = await BaseCoreApi().getAdvertisingId();
         fp.googleId = advertisingId ?? '';
       } catch (e) {
         log(TAG, 'read app googleId error: $e');
@@ -184,12 +184,12 @@ class ApiController extends GetxService with WidgetsBindingObserver {
   Future<void> updateFingerprintData() async {
     fp.lang = IXSP.getCurrentLocal().languageCode;
     fp.phoneCountryIso = LocalizationService.getSystemCountry();
-    fp.isVpn = await CoreApi().isConnected() ?? false;
+    fp.isVpn = await BaseCoreApi().isConnected() ?? false;
     if (!fp.isVpn) {
       fp.isConnectedVpn = false;
     }
     try {
-      final simInfo = await CoreApi().getSimInfo();
+      final simInfo = await BaseCoreApi().getSimInfo();
       // 解析sim
       final sim = jsonDecode(simInfo ?? '{}');
       fp.simReady = sim['simReady'];
@@ -402,7 +402,7 @@ class ApiController extends GetxService with WidgetsBindingObserver {
       final data = isRefreshLaunch
           ? await refreshLaunch()
           : await launch(isCache: true);
-      final isVpnRunning = await CoreApi().isConnected() ?? false;
+      final isVpnRunning = await BaseCoreApi().isConnected() ?? false;
       if (!isVpnRunning) {
         await checkUpdate();
         // 下载smartgeo文件
@@ -443,7 +443,7 @@ class ApiController extends GetxService with WidgetsBindingObserver {
         }
         return;
       }
-      final isVpnRunning = await CoreApi().isConnected() ?? false;
+      final isVpnRunning = await BaseCoreApi().isConnected() ?? false;
       if (!isVpnRunning) {
         await checkUpdate();
       }

+ 81 - 0
lib/app/controllers/base_core_api.dart

@@ -0,0 +1,81 @@
+import 'dart:io';
+
+import 'package:nomo/pigeons/core_api.g.dart' as pigeon;
+
+import 'mobile_core_api.dart';
+import 'windows_core_api.dart';
+
+// 导出子类供外部使用
+export 'mobile_core_api.dart';
+export 'windows_core_api.dart';
+
+/// 导出 onEventChange 函数供外部使用
+Stream<String> onEventChange({String instanceName = ''}) {
+  if (Platform.isWindows) {
+    // Windows 使用自定义实现
+    return WindowsCoreApi.eventStream;
+  }
+  // Android/iOS 使用 Pigeon 实现
+  return pigeon.onEventChange(instanceName: instanceName);
+}
+
+/// 核心 API 抽象基类
+/// Android/iOS 使用 Pigeon 实现,Windows 使用自定义实现
+abstract class BaseCoreApi {
+  static BaseCoreApi? _instance;
+
+  /// 工厂方法,单例模式,根据平台返回对应实现
+  factory BaseCoreApi() {
+    _instance ??= Platform.isWindows
+        ? WindowsCoreApi.create()
+        : MobileCoreApi.create();
+    return _instance!;
+  }
+
+  /// 获取应用列表
+  Future<String?> getApps();
+
+  /// 获取系统语言
+  Future<String?> getSystemLocale();
+
+  /// 连接
+  Future<bool?> connect(
+    String sessionId,
+    int socksPort,
+    String tunnelConfig,
+    String configJson,
+    int remainTime,
+    bool isCountdown,
+    List<String> allowVpnApps,
+    List<String> disallowVpnApps,
+    String accessToken,
+    String aesKey,
+    String aesIv,
+    int locationId,
+    String locationCode,
+    List<String> baseUrls,
+    String params,
+    int peekTimeInterval,
+  );
+
+  /// 断开连接
+  Future<bool?> disconnect();
+
+  /// 获取远程 IP
+  Future<String?> getRemoteIp();
+
+  /// 获取广告 ID
+  Future<String?> getAdvertisingId();
+
+  /// 将任务移至后台
+  Future<bool?> moveTaskToBack();
+
+  /// 是否已连接
+  Future<bool?> isConnected();
+
+  /// 获取 SIM 卡信息
+  Future<String?> getSimInfo();
+
+  /// 获取渠道
+  Future<String?> getChannel();
+}

+ 5 - 5
lib/app/controllers/core_controller.dart

@@ -11,7 +11,7 @@ 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 'base_core_api.dart';
 import '../../utils/boost_report_manager.dart';
 import '../../utils/haptic_feedback_manager.dart';
 import '../../utils/log/logger.dart';
@@ -67,7 +67,7 @@ class CoreController extends GetxService {
   }
 
   void _initCheckConnect() {
-    CoreApi().isConnected().then((value) {
+    BaseCoreApi().isConnected().then((value) {
       if (value == true) {
         state = ConnectionState.connected;
       } else {
@@ -84,13 +84,13 @@ class CoreController extends GetxService {
       getDispatchInfo();
     } else {
       // 断开连接
-      CoreApi().disconnect();
+      BaseCoreApi().disconnect();
     }
   }
 
   void selectLocationConnect() {
     if (state != ConnectionState.disconnected) {
-      CoreApi().disconnect();
+      BaseCoreApi().disconnect();
       // 延迟300ms
       Future.delayed(const Duration(milliseconds: 300), () {
         log(TAG, 'selectLocationConnect disconnected = $state');
@@ -150,7 +150,7 @@ class CoreController extends GetxService {
         final aesIv = Keys.aesIv;
         final baseUrls = ApiDomains.instance.getAllLogUrls();
         final params = jsonEncode(_apiController.fp);
-        CoreApi().connect(
+        BaseCoreApi().connect(
           sessionId,
           socksPort,
           tunnelConfig,

+ 79 - 0
lib/app/controllers/mobile_core_api.dart

@@ -0,0 +1,79 @@
+import 'package:nomo/pigeons/core_api.g.dart' as pigeon;
+
+import 'base_core_api.dart';
+
+/// Android/iOS 实现 - 使用 Pigeon 生成的 CoreApi
+class MobileCoreApi implements BaseCoreApi {
+  MobileCoreApi._();
+
+  /// 内部构造方法,供 BaseCoreApi 工厂使用
+  factory MobileCoreApi.create() => MobileCoreApi._();
+
+  final pigeon.CoreApi _coreApi = pigeon.CoreApi();
+
+  @override
+  Future<String?> getApps() => _coreApi.getApps();
+
+  @override
+  Future<String?> getSystemLocale() => _coreApi.getSystemLocale();
+
+  @override
+  Future<bool?> connect(
+    String sessionId,
+    int socksPort,
+    String tunnelConfig,
+    String configJson,
+    int remainTime,
+    bool isCountdown,
+    List<String> allowVpnApps,
+    List<String> disallowVpnApps,
+    String accessToken,
+    String aesKey,
+    String aesIv,
+    int locationId,
+    String locationCode,
+    List<String> baseUrls,
+    String params,
+    int peekTimeInterval,
+  ) {
+    return _coreApi.connect(
+      sessionId,
+      socksPort,
+      tunnelConfig,
+      configJson,
+      remainTime,
+      isCountdown,
+      allowVpnApps,
+      disallowVpnApps,
+      accessToken,
+      aesKey,
+      aesIv,
+      locationId,
+      locationCode,
+      baseUrls,
+      params,
+      peekTimeInterval,
+    );
+  }
+
+  @override
+  Future<bool?> disconnect() => _coreApi.disconnect();
+
+  @override
+  Future<String?> getRemoteIp() => _coreApi.getRemoteIp();
+
+  @override
+  Future<String?> getAdvertisingId() => _coreApi.getAdvertisingId();
+
+  @override
+  Future<bool?> moveTaskToBack() => _coreApi.moveTaskToBack();
+
+  @override
+  Future<bool?> isConnected() => _coreApi.isConnected();
+
+  @override
+  Future<String?> getSimInfo() => _coreApi.getSimInfo();
+
+  @override
+  Future<String?> getChannel() => _coreApi.getChannel();
+}

+ 155 - 0
lib/app/controllers/windows_core_api.dart

@@ -0,0 +1,155 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:flutter/services.dart';
+
+import 'base_core_api.dart';
+
+/// Windows 实现
+class WindowsCoreApi implements BaseCoreApi {
+  WindowsCoreApi._() {
+    _initEventChannel();
+  }
+
+  /// 内部构造方法,供 BaseCoreApi 工厂使用
+  factory WindowsCoreApi.create() => WindowsCoreApi._();
+
+  // Windows Method Channel
+  static const MethodChannel _channel = MethodChannel('app.xixi.nomo/core_api');
+
+  // Windows 事件流控制器
+  static final StreamController<String> _eventController =
+      StreamController<String>.broadcast();
+
+  // Windows 事件流
+  static Stream<String> get eventStream => _eventController.stream;
+
+  // 初始化事件监听
+  void _initEventChannel() {
+    // 监听来自 Windows 原生端的方法调用
+    _channel.setMethodCallHandler(_handleMethodCall);
+  }
+
+  // 处理来自 Windows 原生端的方法调用
+  Future<dynamic> _handleMethodCall(MethodCall call) async {
+    switch (call.method) {
+      case 'onEventChange':
+        // 原生端发送事件,转发到事件流
+        final String event = call.arguments as String;
+        _eventController.add(event);
+        return null;
+      default:
+        throw PlatformException(
+          code: 'Unimplemented',
+          message: 'Method ${call.method} not implemented',
+        );
+    }
+  }
+
+  // TODO: 实现 Windows 特定的逻辑
+
+  @override
+  Future<String?> getApps() async {
+    // Windows 不需要获取应用列表
+    return null;
+  }
+
+  @override
+  Future<String?> getSystemLocale() async {
+    // TODO: 实现 Windows 获取系统语言
+    return Platform.localeName;
+  }
+
+  @override
+  Future<bool?> connect(
+    String sessionId,
+    int socksPort,
+    String tunnelConfig,
+    String configJson,
+    int remainTime,
+    bool isCountdown,
+    List<String> allowVpnApps,
+    List<String> disallowVpnApps,
+    String accessToken,
+    String aesKey,
+    String aesIv,
+    int locationId,
+    String locationCode,
+    List<String> baseUrls,
+    String params,
+    int peekTimeInterval,
+  ) async {
+    // TODO: 实现 Windows 连接逻辑
+    throw UnimplementedError('Windows connect not implemented yet');
+  }
+
+  @override
+  Future<bool?> disconnect() async {
+    // TODO: 实现 Windows 断开连接逻辑
+    throw UnimplementedError('Windows disconnect not implemented yet');
+  }
+
+  @override
+  Future<String?> getRemoteIp() async {
+    // TODO: 实现 Windows 获取远程 IP
+    throw UnimplementedError('Windows getRemoteIp not implemented yet');
+  }
+
+  @override
+  Future<String?> getAdvertisingId() async {
+    // Windows 不支持广告 ID
+    return null;
+  }
+
+  @override
+  Future<bool?> moveTaskToBack() async {
+    // Windows 不需要此功能
+    return true;
+  }
+
+  @override
+  Future<bool?> isConnected() async {
+    // TODO: 实现 Windows 连接状态检查
+    throw UnimplementedError('Windows isConnected not implemented yet');
+  }
+
+  @override
+  Future<String?> getSimInfo() async {
+    // Windows 不支持 SIM 卡信息
+    return null;
+  }
+
+  @override
+  Future<String?> getChannel() async {
+    // TODO: 实现 Windows 渠道获取
+    return 'windows';
+  }
+
+  /// 发送事件(供 Windows 实现内部使用)
+  ///
+  /// Windows 原生端可以通过 MethodChannel 发送事件:
+  /// ```cpp
+  /// // C++ 示例
+  /// flutter::MethodChannel<flutter::EncodableValue> channel(
+  ///     messenger, "app.xixi.nomo/core_api",
+  ///     &flutter::StandardMethodCodec::GetInstance());
+  ///
+  /// // 发送 VPN 状态变化
+  /// channel.InvokeMethod("onEventChange",
+  ///     flutter::EncodableValue("{\"type\":\"vpn_status\",\"status\":2}"));
+  /// ```
+  ///
+  /// 事件 JSON 格式:
+  /// - vpn_status: {"type":"vpn_status","status":0|1|2|3,"code":0,"message":""}
+  ///   - status: 0=idle, 1=connecting, 2=connected, 3=error
+  /// - timer_update: {"type":"timer_update","currentTime":123,"mode":"countdown"}
+  /// - boost_result: {"type":"boost_result","locationCode":"US","nodeId":"xxx","success":true}
+  static void sendEvent(String event) {
+    _eventController.add(event);
+  }
+
+  /// 释放资源
+  static void dispose() {
+    _eventController.close();
+  }
+}

+ 1 - 0
lib/app/data/models/launch/nodes_config.dart

@@ -8,6 +8,7 @@ abstract class NodesConfig with _$NodesConfig {
   const factory NodesConfig({
     List<dynamic>? nodes,
     String? tunnelConfig,
+    dynamic dataConfig,
     int? socketPort,
     int? maxTryCount,
   }) = _NodesConfig;

+ 26 - 1
lib/app/data/models/launch/nodes_config.freezed.dart

@@ -23,6 +23,7 @@ NodesConfig _$NodesConfigFromJson(Map<String, dynamic> json) {
 mixin _$NodesConfig {
   List<dynamic>? get nodes => throw _privateConstructorUsedError;
   String? get tunnelConfig => throw _privateConstructorUsedError;
+  dynamic get dataConfig => throw _privateConstructorUsedError;
   int? get socketPort => throw _privateConstructorUsedError;
   int? get maxTryCount => throw _privateConstructorUsedError;
 
@@ -46,6 +47,7 @@ abstract class $NodesConfigCopyWith<$Res> {
   $Res call({
     List<dynamic>? nodes,
     String? tunnelConfig,
+    dynamic dataConfig,
     int? socketPort,
     int? maxTryCount,
   });
@@ -68,6 +70,7 @@ class _$NodesConfigCopyWithImpl<$Res, $Val extends NodesConfig>
   $Res call({
     Object? nodes = freezed,
     Object? tunnelConfig = freezed,
+    Object? dataConfig = freezed,
     Object? socketPort = freezed,
     Object? maxTryCount = freezed,
   }) {
@@ -81,6 +84,10 @@ class _$NodesConfigCopyWithImpl<$Res, $Val extends NodesConfig>
                 ? _value.tunnelConfig
                 : tunnelConfig // ignore: cast_nullable_to_non_nullable
                       as String?,
+            dataConfig: freezed == dataConfig
+                ? _value.dataConfig
+                : dataConfig // ignore: cast_nullable_to_non_nullable
+                      as dynamic,
             socketPort: freezed == socketPort
                 ? _value.socketPort
                 : socketPort // ignore: cast_nullable_to_non_nullable
@@ -107,6 +114,7 @@ abstract class _$$NodesConfigImplCopyWith<$Res>
   $Res call({
     List<dynamic>? nodes,
     String? tunnelConfig,
+    dynamic dataConfig,
     int? socketPort,
     int? maxTryCount,
   });
@@ -128,6 +136,7 @@ class __$$NodesConfigImplCopyWithImpl<$Res>
   $Res call({
     Object? nodes = freezed,
     Object? tunnelConfig = freezed,
+    Object? dataConfig = freezed,
     Object? socketPort = freezed,
     Object? maxTryCount = freezed,
   }) {
@@ -141,6 +150,10 @@ class __$$NodesConfigImplCopyWithImpl<$Res>
             ? _value.tunnelConfig
             : tunnelConfig // ignore: cast_nullable_to_non_nullable
                   as String?,
+        dataConfig: freezed == dataConfig
+            ? _value.dataConfig
+            : dataConfig // ignore: cast_nullable_to_non_nullable
+                  as dynamic,
         socketPort: freezed == socketPort
             ? _value.socketPort
             : socketPort // ignore: cast_nullable_to_non_nullable
@@ -160,6 +173,7 @@ class _$NodesConfigImpl with DiagnosticableTreeMixin implements _NodesConfig {
   const _$NodesConfigImpl({
     final List<dynamic>? nodes,
     this.tunnelConfig,
+    this.dataConfig,
     this.socketPort,
     this.maxTryCount,
   }) : _nodes = nodes;
@@ -180,13 +194,15 @@ class _$NodesConfigImpl with DiagnosticableTreeMixin implements _NodesConfig {
   @override
   final String? tunnelConfig;
   @override
+  final dynamic dataConfig;
+  @override
   final int? socketPort;
   @override
   final int? maxTryCount;
 
   @override
   String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
-    return 'NodesConfig(nodes: $nodes, tunnelConfig: $tunnelConfig, socketPort: $socketPort, maxTryCount: $maxTryCount)';
+    return 'NodesConfig(nodes: $nodes, tunnelConfig: $tunnelConfig, dataConfig: $dataConfig, socketPort: $socketPort, maxTryCount: $maxTryCount)';
   }
 
   @override
@@ -196,6 +212,7 @@ class _$NodesConfigImpl with DiagnosticableTreeMixin implements _NodesConfig {
       ..add(DiagnosticsProperty('type', 'NodesConfig'))
       ..add(DiagnosticsProperty('nodes', nodes))
       ..add(DiagnosticsProperty('tunnelConfig', tunnelConfig))
+      ..add(DiagnosticsProperty('dataConfig', dataConfig))
       ..add(DiagnosticsProperty('socketPort', socketPort))
       ..add(DiagnosticsProperty('maxTryCount', maxTryCount));
   }
@@ -208,6 +225,10 @@ class _$NodesConfigImpl with DiagnosticableTreeMixin implements _NodesConfig {
             const DeepCollectionEquality().equals(other._nodes, _nodes) &&
             (identical(other.tunnelConfig, tunnelConfig) ||
                 other.tunnelConfig == tunnelConfig) &&
+            const DeepCollectionEquality().equals(
+              other.dataConfig,
+              dataConfig,
+            ) &&
             (identical(other.socketPort, socketPort) ||
                 other.socketPort == socketPort) &&
             (identical(other.maxTryCount, maxTryCount) ||
@@ -220,6 +241,7 @@ class _$NodesConfigImpl with DiagnosticableTreeMixin implements _NodesConfig {
     runtimeType,
     const DeepCollectionEquality().hash(_nodes),
     tunnelConfig,
+    const DeepCollectionEquality().hash(dataConfig),
     socketPort,
     maxTryCount,
   );
@@ -242,6 +264,7 @@ abstract class _NodesConfig implements NodesConfig {
   const factory _NodesConfig({
     final List<dynamic>? nodes,
     final String? tunnelConfig,
+    final dynamic dataConfig,
     final int? socketPort,
     final int? maxTryCount,
   }) = _$NodesConfigImpl;
@@ -254,6 +277,8 @@ abstract class _NodesConfig implements NodesConfig {
   @override
   String? get tunnelConfig;
   @override
+  dynamic get dataConfig;
+  @override
   int? get socketPort;
   @override
   int? get maxTryCount;

+ 2 - 0
lib/app/data/models/launch/nodes_config.g.dart

@@ -10,6 +10,7 @@ _$NodesConfigImpl _$$NodesConfigImplFromJson(Map<String, dynamic> json) =>
     _$NodesConfigImpl(
       nodes: json['nodes'] as List<dynamic>?,
       tunnelConfig: json['tunnelConfig'] as String?,
+      dataConfig: json['dataConfig'],
       socketPort: (json['socketPort'] as num?)?.toInt(),
       maxTryCount: (json['maxTryCount'] as num?)?.toInt(),
     );
@@ -18,6 +19,7 @@ Map<String, dynamic> _$$NodesConfigImplToJson(_$NodesConfigImpl instance) =>
     <String, dynamic>{
       'nodes': instance.nodes,
       'tunnelConfig': instance.tunnelConfig,
+      'dataConfig': instance.dataConfig,
       'socketPort': instance.socketPort,
       'maxTryCount': instance.maxTryCount,
     };

+ 33 - 21
lib/app/data/models/launch/user.freezed.dart

@@ -31,16 +31,19 @@ mixin _$User {
   int? get createTime =>
       throw _privateConstructorUsedError; // API 返回 int 类型的时间戳
   bool? get geographyEea => throw _privateConstructorUsedError;
-  int? get memberLevel =>
-      throw _privateConstructorUsedError; // 会员等级 1 游客 2 普通用户 3 会员
-  int? get userLevel => throw _privateConstructorUsedError;
-  int? get expireTime => throw _privateConstructorUsedError;
-  int? get remainTime => throw _privateConstructorUsedError;
-  bool? get isExpired => throw _privateConstructorUsedError;
-  bool? get isTestUser => throw _privateConstructorUsedError;
-  bool? get isSubscribeUser => throw _privateConstructorUsedError;
-  Account? get account => throw _privateConstructorUsedError; // API 返回对象类型
-  ChannelPlan? get planInfo => throw _privateConstructorUsedError;
+  int? get memberLevel => throw _privateConstructorUsedError; // 1设备用户 2注册用户
+  int? get userLevel =>
+      throw _privateConstructorUsedError; // 1试用 2免费 3会员 9999内部
+  int? get expireTime => throw _privateConstructorUsedError; // VIP套餐到期时间
+  int? get remainTime => throw _privateConstructorUsedError; // VIP套餐剩余时间
+  bool? get isExpired => throw _privateConstructorUsedError; // VIP套餐是否过期
+  bool? get isTestUser => throw _privateConstructorUsedError; // 是否是测试用户
+  bool? get isSubscribeUser => throw _privateConstructorUsedError; // 是否是连续订阅用户
+  Account? get account =>
+      throw _privateConstructorUsedError; //isSubscribeUser=false 时返回最后一次购买套餐信息
+  //isSubscribeUser=true 时返回当前订阅套餐信息
+  ChannelPlan? get planInfo =>
+      throw _privateConstructorUsedError; // userLevel=3时生效
   bool? get activated => throw _privateConstructorUsedError;
 
   /// Serializes this User to a JSON map.
@@ -430,24 +433,32 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User {
   final bool? geographyEea;
   @override
   final int? memberLevel;
-  // 会员等级 1 游客 2 普通用户 3 会员
+  // 1设备用户 2注册用户
   @override
   final int? userLevel;
+  // 1试用 2免费 3会员 9999内部
   @override
   final int? expireTime;
+  // VIP套餐到期时间
   @override
   final int? remainTime;
+  // VIP套餐剩余时间
   @override
   final bool? isExpired;
+  // VIP套餐是否过期
   @override
   final bool? isTestUser;
+  // 是否是测试用户
   @override
   final bool? isSubscribeUser;
+  // 是否是连续订阅用户
   @override
   final Account? account;
-  // API 返回对象类型
+  //isSubscribeUser=false 时返回最后一次购买套餐信息
+  //isSubscribeUser=true 时返回当前订阅套餐信息
   @override
   final ChannelPlan? planInfo;
+  // userLevel=3时生效
   @override
   final bool? activated;
 
@@ -607,23 +618,24 @@ abstract class _User implements User {
   @override
   bool? get geographyEea;
   @override
-  int? get memberLevel; // 会员等级 1 游客 2 普通用户 3 会员
+  int? get memberLevel; // 1设备用户 2注册用户
   @override
-  int? get userLevel;
+  int? get userLevel; // 1试用 2免费 3会员 9999内部
   @override
-  int? get expireTime;
+  int? get expireTime; // VIP套餐到期时间
   @override
-  int? get remainTime;
+  int? get remainTime; // VIP套餐剩余时间
   @override
-  bool? get isExpired;
+  bool? get isExpired; // VIP套餐是否过期
   @override
-  bool? get isTestUser;
+  bool? get isTestUser; // 是否是测试用户
   @override
-  bool? get isSubscribeUser;
+  bool? get isSubscribeUser; // 是否是连续订阅用户
   @override
-  Account? get account; // API 返回对象类型
+  Account? get account; //isSubscribeUser=false 时返回最后一次购买套餐信息
+  //isSubscribeUser=true 时返回当前订阅套餐信息
   @override
-  ChannelPlan? get planInfo;
+  ChannelPlan? get planInfo; // userLevel=3时生效
   @override
   bool? get activated;
 

+ 2 - 2
lib/app/modules/home/controllers/home_controller.dart

@@ -1,7 +1,7 @@
 import 'package:get/get.dart';
 import 'package:nomo/app/controllers/api_controller.dart';
 import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
-import '../../../../pigeons/core_api.g.dart';
+import '../../../controllers/base_core_api.dart';
 import '../../../../utils/awesome_notifications_helper.dart';
 import '../../../../utils/log/logger.dart';
 import '../../../base/base_controller.dart';
@@ -84,7 +84,7 @@ class HomeController extends BaseController {
           // 如果节点不存在于 groups 中,选中第一个可用节点
           // 如果当前节点是连接中的状态,则断开连接
           if (coreController.state != ConnectionState.disconnected) {
-            CoreApi().disconnect();
+            BaseCoreApi().disconnect();
           }
           log(
             TAG,

+ 132 - 27
lib/app/modules/home/views/home_view.dart

@@ -37,37 +37,142 @@ class HomeView extends BaseView<HomeController> {
                 Row(
                   mainAxisAlignment: MainAxisAlignment.spaceBetween,
                   children: [
-                    Obx(
-                      () => ClickOpacity(
-                        onTap: () => Get.toNamed(Routes.SUBSCRIPTION),
-                        child: IXImage(
-                          source: controller.apiController.userLevel == 3
-                              ? Assets.premium
-                              : controller.apiController.userLevel == 9999
-                              ? Assets.test
-                              : Assets.free,
-                          width: controller.apiController.userLevel == 3
-                              ? 92.w
-                              : 64.w,
-                          height: 28.w,
-                          sourceType: ImageSourceType.asset,
+                    ClipRRect(
+                      borderRadius: BorderRadius.circular(100.r),
+                      child: Container(
+                        decoration: const BoxDecoration(
+                          color: Color(0x0DFFD900),
                         ),
-                      ),
-                    ),
-                    Obx(
-                      () => Text(
-                        controller.apiController.isGuest &&
-                                !controller.apiController.isPremium &&
-                                controller.apiController.remainTimeSeconds > 0
-                            ? controller.apiController.remainTimeFormatted
-                            : controller.coreController.timer,
-                        style: TextStyle(
-                          fontSize: 28.sp,
-                          height: 1.2,
-                          color: Get.reactiveTheme.primaryColor,
+                        child: Stack(
+                          children: [
+                            // 左上角光晕
+                            Positioned(
+                              left: -8.w,
+                              top: -8.w,
+                              child: Container(
+                                width: 32.w,
+                                height: 32.w,
+                                decoration: BoxDecoration(
+                                  shape: BoxShape.circle,
+                                  gradient: RadialGradient(
+                                    colors: [
+                                      const Color(
+                                        0xFFFCEFCF,
+                                      ).withValues(alpha: 0.4),
+                                      const Color(
+                                        0xFFFCEFCF,
+                                      ).withValues(alpha: 0.0),
+                                    ],
+                                    stops: const [0.0, 1.0],
+                                  ),
+                                ),
+                              ),
+                            ),
+                            // 右下角光晕
+                            Positioned(
+                              right: -12.w,
+                              bottom: -42.w,
+                              child: Container(
+                                width: 84.w,
+                                height: 84.w,
+                                decoration: BoxDecoration(
+                                  shape: BoxShape.rectangle,
+                                  gradient: RadialGradient(
+                                    colors: [
+                                      const Color(
+                                        0xFFFCEFCF,
+                                      ).withValues(alpha: 0.35),
+                                      const Color(
+                                        0xFFFCEFCF,
+                                      ).withValues(alpha: 0.0),
+                                    ],
+                                    stops: const [0.0, 1.0],
+                                  ),
+                                ),
+                              ),
+                            ),
+                            // 内容
+                            Padding(
+                              padding: EdgeInsets.symmetric(
+                                horizontal: 6.w,
+                                vertical: 4.w,
+                              ),
+                              child: Row(
+                                children: [
+                                  Obx(
+                                    () => ClickOpacity(
+                                      onTap: () =>
+                                          Get.toNamed(Routes.SUBSCRIPTION),
+                                      child: IXImage(
+                                        source:
+                                            controller
+                                                    .apiController
+                                                    .userLevel ==
+                                                3
+                                            ? Assets.homePremium
+                                            : controller
+                                                      .apiController
+                                                      .userLevel ==
+                                                  9999
+                                            ? Assets.homeTest
+                                            : Assets.homeFree,
+                                        width:
+                                            controller
+                                                    .apiController
+                                                    .userLevel ==
+                                                3
+                                            ? 92.w
+                                            : 64.w,
+                                        height: 28.w,
+                                        sourceType: ImageSourceType.asset,
+                                      ),
+                                    ),
+                                  ),
+                                  Obx(
+                                    () => Text(
+                                      controller.apiController.isGuest &&
+                                              !controller
+                                                  .apiController
+                                                  .isPremium &&
+                                              controller
+                                                      .apiController
+                                                      .remainTimeSeconds >
+                                                  0
+                                          ? controller
+                                                .apiController
+                                                .remainTimeFormatted
+                                          : controller.coreController.timer,
+                                      style: TextStyle(
+                                        fontSize: 13.sp,
+                                        height: 1.5,
+                                        fontStyle: FontStyle.italic,
+                                        fontWeight: FontWeight.w500,
+                                        color:
+                                            controller
+                                                        .apiController
+                                                        .userLevel ==
+                                                    3 ||
+                                                controller
+                                                        .apiController
+                                                        .userLevel ==
+                                                    9999
+                                            ? Get
+                                                  .reactiveTheme
+                                                  .textTheme
+                                                  .bodyLarge!
+                                                  .color
+                                            : Get.reactiveTheme.hintColor,
+                                      ),
+                                    ),
+                                  ),
+                                ],
+                              ),
+                            ),
+                          ],
                         ),
                       ),
                     ),
+
                     ClickOpacity(
                       child: Padding(
                         padding: EdgeInsets.only(

+ 2 - 2
lib/app/modules/home/widgets/connection_round_button.dart

@@ -117,8 +117,8 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
   // 启动连接中状态的文本轮播计时器
   void _startConnectingTimer() {
     _connectingTimer?.cancel();
-    _connectingTextIndex = 0;
-    _connectingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
+    _connectingTextIndex = -1;
+    _connectingTimer = Timer.periodic(const Duration(seconds: 4), (timer) {
       if (mounted) {
         setState(() {
           // 每秒切换到下一个文本,循环显示0-4

+ 2 - 2
lib/app/modules/splittunneling/selectapp/controllers/splittunneling_selectapp_controller.dart

@@ -3,7 +3,7 @@ import 'dart:convert';
 import 'package:get/get.dart';
 import 'package:nomo/app/base/base_controller.dart';
 import 'package:nomo/app/data/sp/ix_sp.dart';
-import 'package:nomo/pigeons/core_api.g.dart';
+import 'package:nomo/app/controllers/base_core_api.dart';
 import 'package:nomo/utils/log/logger.dart';
 
 /// 分流隧道模式枚举
@@ -90,7 +90,7 @@ class SplittunnelingSelectappController extends BaseController {
     }
 
     final newApps = <AppInfo>[];
-    final appsJson = await CoreApi().getApps();
+    final appsJson = await BaseCoreApi().getApps();
     if (appsJson != null) {
       final apps = jsonDecode(appsJson);
       for (dynamic app in apps) {