Bläddra i källkod

feat: 设置里增加主题切换,更新主题样式

lilu 1 månad sedan
förälder
incheckning
6f863b2916
43 ändrade filer med 267 tillägg och 144 borttagningar
  1. 17 13
      android/app/src/main/kotlin/app/xixi/nomo/XRayService.kt
  2. 1 31
      lib/app/app.dart
  3. 2 2
      lib/app/constants/api_domains.dart
  4. 1 1
      lib/app/controllers/api_controller.dart
  5. 3 0
      lib/app/controllers/core_controller.dart
  6. 33 6
      lib/app/dialog/all_dialog.dart
  7. 3 3
      lib/app/dialog/custom_dialog.dart
  8. 55 5
      lib/app/modules/account/views/account_view.dart
  9. 1 5
      lib/app/modules/feedback/views/feedback_view.dart
  10. 3 2
      lib/app/modules/home/views/home_view.dart
  11. 1 1
      lib/app/modules/home/widgets/menu_list.dart
  12. 2 1
      lib/app/modules/medialocation/controllers/medialocation_controller.dart
  13. 1 1
      lib/app/modules/node/views/node_view.dart
  14. 1 1
      lib/app/modules/node/widgets/node_list.dart
  15. 23 0
      lib/app/modules/setting/controllers/setting_controller.dart
  16. 12 9
      lib/app/modules/setting/views/setting_view.dart
  17. 1 1
      lib/app/modules/subscription/controllers/subscription_controller.dart
  18. 2 29
      lib/app/modules/theme/controllers/theme_controller.dart
  19. 2 6
      lib/app/widgets/submit_btn.dart
  20. 0 1
      lib/config/theme/dark_theme_colors.dart
  21. 73 4
      lib/config/theme/theme_extensions/theme_extension.dart
  22. 1 1
      lib/config/translations/ar_AR/ar_ar_translation.dart
  23. 1 1
      lib/config/translations/de_DE/de_de_translation.dart
  24. 1 1
      lib/config/translations/en_US/en_us_translation.dart
  25. 1 1
      lib/config/translations/es_ES/es_es_translation.dart
  26. 1 1
      lib/config/translations/fa_IR/fa_ir_translation.dart
  27. 1 1
      lib/config/translations/fr_FR/fr_fr_translation.dart
  28. 1 1
      lib/config/translations/hi_IN/hi_in_translation.dart
  29. 1 1
      lib/config/translations/id_ID/id_id_translation.dart
  30. 1 1
      lib/config/translations/ja_JP/ja_jp_translation.dart
  31. 1 1
      lib/config/translations/ko_KR/ko_kr_translation.dart
  32. 1 1
      lib/config/translations/my_MM/my_mm_translation.dart
  33. 1 1
      lib/config/translations/pt_BR/pt_br_translation.dart
  34. 1 1
      lib/config/translations/ru_RU/ru_ru_translation.dart
  35. 1 1
      lib/config/translations/strings_enum.dart
  36. 1 1
      lib/config/translations/th_TH/th_th_translation.dart
  37. 1 1
      lib/config/translations/tk_TM/tk_tm_translation.dart
  38. 1 1
      lib/config/translations/tl_PH/tl_ph_translation.dart
  39. 1 1
      lib/config/translations/tr_TR/tr_tr_translation.dart
  40. 1 1
      lib/config/translations/vi_VN/vi_vn_translation.dart
  41. 1 1
      lib/config/translations/zh_TW/zh_tw_translation.dart
  42. 9 1
      pubspec.lock
  43. 1 1
      pubspec.yaml

+ 17 - 13
android/app/src/main/kotlin/app/xixi/nomo/XRayService.kt

@@ -458,30 +458,34 @@ class XRayService : LifecycleVpnService() {
         // 2. 再停止 tunnel(此时不再有新数据进入)
         // 3. 最后关闭 tun fd
         stopProxyConnector()
-        stopTunnelWithTimeout()
+        stopTunnel()
         closeTunFd()
 
         VLog.i(TAG, "xray stopped")
     }
 
-    private suspend fun stopTunnelWithTimeout() {
-        val startTime = System.currentTimeMillis()
-        val result = withTimeoutOrNull(STOP_TUNNEL_TIMEOUT_MS) {
-            withContext(Dispatchers.IO) {
+    private fun stopTunnel() {
+        // 注意:必须先停止 tunnel,再关闭 fd
+        // 否则 hev-socks5-tunnel 可能在读写 fd 时卡住
+        try {
+            // 使用单独线程执行 stopTunnel,避免阻塞,并添加超时
+            val stopThread = Thread {
                 try {
                     tunnelService.stopTunnel()
-                    val elapsed = System.currentTimeMillis() - startTime
-                    VLog.i(TAG, "Tunnel stopped in ${elapsed}ms")
-                    true
+                    VLog.i(TAG, "Tunnel stopped")
                 } catch (e: Exception) {
                     VLog.e(TAG, "停止 tunnel 失败", e)
-                    false
                 }
             }
-        }
-        if (result == null) {
-            val elapsed = System.currentTimeMillis() - startTime
-            VLog.w(TAG, "stopTunnel 超时 (${elapsed}ms),强制继续")
+            stopThread.start()
+            // 等待最多 3 秒
+            stopThread.join(3000)
+            if (stopThread.isAlive) {
+                VLog.w(TAG, "stopTunnel 超时,强制继续")
+                stopThread.interrupt()
+            }
+        } catch (e: Exception) {
+            VLog.e(TAG, "停止 tunnel 异常", e)
         }
     }
 

+ 1 - 31
lib/app/app.dart

@@ -54,11 +54,6 @@ class App extends StatelessWidget {
   }
 
   Widget _buildMaterialApp() {
-    // 只在初始化时读取一次主题设置,后续通过 Get.changeThemeMode() 动态切换
-    final initialThemeMode = ReactiveTheme.isLightTheme
-        ? ThemeMode.light
-        : ThemeMode.dark;
-
     return GetMaterialApp.router(
       title: 'ixVPN',
       useInheritedMediaQuery: true,
@@ -67,7 +62,7 @@ class App extends StatelessWidget {
       getPages: AppPages.routes,
       theme: IXTheme.getThemeData(isLight: true),
       darkTheme: IXTheme.getThemeData(isLight: false),
-      themeMode: initialThemeMode,
+      themeMode: ReactiveTheme.initialThemeMode,
       fallbackLocale: LocalizationService.defaultLanguage,
       locale: IXSP.getCurrentLocal(),
       translations: LocalizationService.getInstance(),
@@ -122,31 +117,6 @@ class App extends StatelessWidget {
                 },
               ),
             ),
-          if (Configs.debug)
-            Positioned(
-              bottom: 100,
-              right: 10,
-              child: ClickOpacity(
-                child: Container(
-                  width: 40,
-                  height: 40,
-                  decoration: BoxDecoration(
-                    color: Get.reactiveTheme.primaryColor.withValues(
-                      alpha: 0.5,
-                    ),
-                    borderRadius: BorderRadius.circular(40),
-                  ),
-                  child: Icon(
-                    ReactiveTheme.isLightTheme ? Icons.sunny : Icons.nightlight,
-                    color: Get.reactiveTheme.primaryColor,
-                  ),
-                ),
-                onTap: () {
-                  // 切换主题
-                  IXTheme.changeTheme();
-                },
-              ),
-            ),
         ],
       ),
     );

+ 2 - 2
lib/app/constants/api_domains.dart

@@ -133,8 +133,8 @@ class ApiDomains {
   void initUrls() {
     if (Configs.debug) {
       _apiUrls = [
-        // 'https://api.znomo.com', // 测试环境
-        "https://nomo-api.clickto.dev", // 开发环境
+        'https://api.znomo.com', // 测试环境
+        // "https://nomo-api.clickto.dev", // 开发环境
       ];
       _routerUrls = [];
       _logUrls = [];

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

@@ -1617,7 +1617,7 @@ class ApiController extends GetxService with WidgetsBindingObserver {
   /// 格式化剩余时间显示
   String _formatRemainTime(int totalSeconds) {
     if (totalSeconds <= 0) {
-      return '0';
+      return '--';
     }
 
     final days = totalSeconds ~/ 86400;

+ 3 - 0
lib/app/controllers/core_controller.dart

@@ -552,6 +552,9 @@ class CoreController extends GetxService {
       case Errors.ERROR_PERMISSION_DENIED:
         errorMessage = Strings.vpnPermissionDeniedError.tr;
         break;
+      case Errors.ERROR_REMAIN_TIME:
+        errorMessage = Strings.remainTimeEnded.tr;
+        break;
     }
     if (errorMessage.isNotEmpty) {
       ErrorDialog.show(message: errorMessage);

+ 33 - 6
lib/app/dialog/all_dialog.dart

@@ -7,17 +7,33 @@ import '../../config/theme/dark_theme_colors.dart';
 import '../../utils/system_helper.dart';
 import '../constants/iconfont/iconfont.dart';
 import '../data/models/launch/upgrade.dart';
+import '../routes/app_pages.dart';
 import 'custom_dialog.dart';
 
 /// 弹窗使用示例
 class AllDialog {
   /// 显示绑定邮箱/会员权益弹窗
   static void showBindEmailMemberBenefits() {
-    CustomDialog.showInfo(
+    final isLight = ReactiveTheme.isLightTheme;
+    CustomDialog.showError(
       title: Strings.bindEmailMemberBenefits.tr,
       message: Strings.bindingAccountEmailProtectsPreRights.tr,
       icon: IconFont.icon23,
-      iconColor: DarkThemeColors.subscriptionColor,
+      iconColor: isLight
+          ? Get.reactiveTheme.primaryColor
+          : DarkThemeColors.subscriptionColor,
+      titleColor: isLight ? Get.reactiveTheme.primaryColor : null,
+      buttonText: Strings.login.tr,
+      cancelText: Strings.cancel.tr,
+      onPressed: () {
+        // 处理重试逻辑
+        Navigator.of(Get.context!).pop();
+        Get.toNamed(Routes.LOGIN);
+      },
+      onCancel: () {
+        // 处理取消逻辑
+        Navigator.of(Get.context!).pop();
+      },
     );
   }
 
@@ -81,8 +97,9 @@ class AllDialog {
       buttonText: Strings.logOut.tr,
       cancelText: Strings.cancel.tr,
       icon: Icons.info_outline,
-      iconColor: const Color(0xFFFF3B30),
-      confirmButtonColor: const Color(0xFFFF3B30),
+      iconColor: DarkThemeColors.errorColor,
+      titleColor: DarkThemeColors.errorColor,
+      confirmButtonColor: DarkThemeColors.errorColor,
       onPressed: () {
         // 处理退出登录逻辑
         Navigator.of(Get.context!).pop();
@@ -112,11 +129,16 @@ class AllDialog {
 
   /// 显示UID信息弹窗
   static void showUidInfo() {
+    final isLight = ReactiveTheme.isLightTheme;
     CustomDialog.showInfo(
       icon: IconFont.icon14,
-      iconColor: Get.theme.textTheme.bodyLarge!.color,
+      iconColor: isLight
+          ? Get.reactiveTheme.primaryColor
+          : Get.theme.textTheme.bodyLarge!.color,
+      titleColor: isLight ? Get.reactiveTheme.primaryColor : null,
       title: Strings.whatIsUid.tr,
       message: Strings.uidMessage.tr,
+      messageColor: Get.theme.textTheme.bodyLarge!.color,
       buttonText: Strings.ok.tr,
       onPressed: () {
         // 处理邮件发送成功后的逻辑
@@ -151,7 +173,9 @@ class AllDialog {
       buttonText: Strings.deleteAccount.tr,
       cancelText: Strings.cancel.tr,
       icon: IconFont.icon40,
-      iconColor: DarkThemeColors.deleteAccountIconColor,
+      iconColor: DarkThemeColors.errorColor,
+      titleColor: DarkThemeColors.errorColor,
+      confirmButtonColor: DarkThemeColors.errorColor,
       onPressed: () {
         // 处理删除账户逻辑
         Navigator.of(Get.context!).pop();
@@ -166,8 +190,11 @@ class AllDialog {
 
   /// 显示更新弹窗
   static void showUpdate(Upgrade upgrade, {bool hasForceUpdate = false}) {
+    final isLight = ReactiveTheme.isLightTheme;
     CustomDialog.showUpdateDialog(
       title: Strings.newVersionAvailable.tr,
+      iconColor: isLight ? Get.reactiveTheme.primaryColor : null,
+      titleColor: isLight ? Get.reactiveTheme.primaryColor : null,
       message: upgrade.info ?? '',
       buttonText: Strings.upgradeNow.tr,
       cancelText: hasForceUpdate ? null : Strings.cancel.tr,

+ 3 - 3
lib/app/dialog/custom_dialog.dart

@@ -415,7 +415,7 @@ class _CustomDialogWidget extends StatelessWidget {
             style: TextStyle(
               fontSize: 14.sp,
               fontWeight: FontWeight.w600,
-              color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+              color: Colors.white,
             ),
           ),
         ),
@@ -445,7 +445,7 @@ class _CustomDialogWidget extends StatelessWidget {
                 style: TextStyle(
                   fontSize: 14.sp,
                   fontWeight: FontWeight.w600,
-                  color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+                  color: Colors.white,
                 ),
               ),
             ),
@@ -507,7 +507,7 @@ class _CustomDialogWidget extends StatelessWidget {
                 style: TextStyle(
                   fontSize: 14.sp,
                   fontWeight: FontWeight.w600,
-                  color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+                  color: Colors.white,
                 ),
               ),
             ),

+ 55 - 5
lib/app/modules/account/views/account_view.dart

@@ -219,13 +219,16 @@ class AccountView extends BaseView<AccountController> {
 
   /// 底部按钮
   Widget _buildBottomButtons() {
+    final isLight = ReactiveTheme.isLightTheme;
     return Column(
       children: [
         if (controller.apiController.isPremium) ...[
           if (controller.apiController.isGuest) ...[
             SubmitButton(
               text: Strings.changeSubscription.tr,
-              bgColor: Get.reactiveTheme.highlightColor,
+              bgColor: isLight
+                  ? Colors.black
+                  : Get.reactiveTheme.highlightColor,
               textColor: DarkThemeColors.subscriptionColor,
               onPressed: () {
                 controller.toSubscription();
@@ -255,7 +258,7 @@ class AccountView extends BaseView<AccountController> {
         if (controller.apiController.isGuest &&
             controller.apiController.isPremium) ...[
           20.verticalSpaceFromWidth,
-          _buildSecondaryButton(
+          _buildEmailButton(
             text: Strings.bindEmailMemberBenefits.tr,
             icon: IconFont.icon23,
             onTap: () {
@@ -284,13 +287,17 @@ class AccountView extends BaseView<AccountController> {
     required IconData icon,
     required VoidCallback onTap,
   }) {
+    final isLight = ReactiveTheme.isLightTheme;
     return ClickOpacity(
       onTap: onTap,
       child: Container(
         height: 52.w,
         decoration: BoxDecoration(
-          border: Border.all(color: Get.reactiveTheme.dividerColor, width: 1.w),
+          border: isLight
+              ? null
+              : Border.all(color: Get.reactiveTheme.dividerColor, width: 1.w),
           borderRadius: BorderRadius.circular(12.r),
+          color: isLight ? Colors.black : null,
         ),
         child: Row(
           mainAxisAlignment: MainAxisAlignment.center,
@@ -311,6 +318,49 @@ class AccountView extends BaseView<AccountController> {
     );
   }
 
+  /// 次要按钮(黑色边框)
+  Widget _buildEmailButton({
+    required String text,
+    required IconData icon,
+    required VoidCallback onTap,
+  }) {
+    final isLight = ReactiveTheme.isLightTheme;
+    return ClickOpacity(
+      onTap: onTap,
+      child: Container(
+        height: 52.w,
+        decoration: BoxDecoration(
+          border: isLight
+              ? null
+              : Border.all(color: Get.reactiveTheme.dividerColor, width: 1.w),
+          borderRadius: BorderRadius.circular(12.r),
+          color: isLight ? Get.reactiveTheme.primaryColor : null,
+        ),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            Text(
+              text,
+              style: TextStyle(
+                fontSize: 16.sp,
+                color: isLight
+                    ? Colors.white
+                    : DarkThemeColors.subscriptionColor,
+                fontWeight: FontWeight.w400,
+              ),
+            ),
+            SizedBox(width: 8.w),
+            Icon(
+              icon,
+              size: 20.w,
+              color: isLight ? Colors.white : DarkThemeColors.subscriptionColor,
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
   /// 构建分割线
   Widget _buildDivider() {
     return Divider(height: 1.w, color: Get.reactiveTheme.dividerColor);
@@ -554,7 +604,7 @@ class AccountView extends BaseView<AccountController> {
               end: Alignment.bottomCenter,
             ),
             title: Strings.logout.tr,
-            titleColor: const Color(0xFFEF0000),
+            titleColor: DarkThemeColors.errorColor,
             onTap: () {
               AllDialog.showLogoutConfirm(() {
                 // 退出登录
@@ -639,7 +689,7 @@ class AccountView extends BaseView<AccountController> {
                       child: Icon(
                         IconFont.icon59,
                         size: 20.w,
-                        color: Colors.white,
+                        color: Get.reactiveTheme.hintColor,
                       ),
                     ),
                 ],

+ 1 - 5
lib/app/modules/feedback/views/feedback_view.dart

@@ -170,11 +170,7 @@ class FeedbackView extends BaseView<FeedbackController> {
                                   fontSize: 16.sp,
                                   fontWeight: FontWeight.w600,
                                   color: isEnabled
-                                      ? Get
-                                            .reactiveTheme
-                                            .textTheme
-                                            .bodyLarge!
-                                            .color
+                                      ? Colors.white
                                       : Get.reactiveTheme.hintColor,
                                 ),
                               ),

+ 3 - 2
lib/app/modules/home/views/home_view.dart

@@ -204,6 +204,7 @@ class HomeView extends BaseView<HomeController> {
                                     }),
                                   ),
                                   MenuList(),
+                                  14.verticalSpaceFromWidth,
                                 ],
                               ),
                             ),
@@ -423,7 +424,7 @@ class HomeView extends BaseView<HomeController> {
         Get.toNamed(Routes.NODE);
       },
       child: Obx(() {
-        final isLight = IXSP.getThemeIsLight();
+        final isLight = ReactiveTheme.isLightTheme;
         return Container(
           height: 56.w,
           width: double.maxFinite,
@@ -481,7 +482,7 @@ class HomeView extends BaseView<HomeController> {
   /// 构建最近位置卡片(支持展开/收缩)
   Widget _buildRecentLocationsCard() {
     return Obx(() {
-      final isLight = IXSP.getThemeIsLight();
+      final isLight = ReactiveTheme.isLightTheme;
       return Container(
         margin: EdgeInsets.symmetric(horizontal: 10.w),
         padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 56.w, bottom: 0),

+ 1 - 1
lib/app/modules/home/widgets/menu_list.dart

@@ -93,7 +93,7 @@ class MenuList extends StatelessWidget {
 
   /// 构建单个菜单项
   Widget _buildMenuItem(Banner banner, HomeController controller) {
-    final isLight = IXSP.getThemeIsLight();
+    final isLight = ReactiveTheme.isLightTheme;
     return GestureDetector(
       onTap: () => controller.onBannerTap(banner),
       child: Container(

+ 2 - 1
lib/app/modules/medialocation/controllers/medialocation_controller.dart

@@ -24,7 +24,8 @@ class MedialocationController extends GetxController {
   bool get isConnected =>
       _isSameLocation && coreController.state == ConnectionState.connected;
   bool get isConnecting =>
-      _isSameLocation && coreController.state == ConnectionState.connecting;
+      _isSameLocation && coreController.state == ConnectionState.connecting ||
+      coreController.state == ConnectionState.connectingVirtual;
 
   // 判断当前选中的节点是否是 bannerLocation
   bool get _isSameLocation {

+ 1 - 1
lib/app/modules/node/views/node_view.dart

@@ -47,7 +47,7 @@ class NodeView extends BaseView<NodeController> {
         borderRadius: BorderRadius.circular(8.r),
       ),
       // indicatorWeight: 2,
-      labelColor: Get.reactiveTheme.textTheme.bodyLarge!.color,
+      labelColor: Colors.white,
       unselectedLabelColor: Get.reactiveTheme.hintColor,
       labelStyle: TextStyle(
         fontSize: 14.sp,

+ 1 - 1
lib/app/modules/node/widgets/node_list.dart

@@ -282,7 +282,7 @@ class _CountrySection extends StatelessWidget {
                                     locationName,
                                     style: TextStyle(
                                       fontSize: 14.sp,
-                                      fontWeight: FontWeight.w500,
+                                      fontWeight: FontWeight.w400,
                                       color: isSelected
                                           ? Get.reactiveTheme.primaryColor
                                           : Get

+ 23 - 0
lib/app/modules/setting/controllers/setting_controller.dart

@@ -27,13 +27,36 @@ class SettingController extends GetxController {
   bool get hasUpdate => _hasUpdate.value;
   set hasUpdate(bool value) => _hasUpdate.value = value;
 
+  final _themeMode = ''.obs;
+  String get themeMode => _themeMode.value;
+  set themeMode(String value) => _themeMode.value = value;
+
   @override
   void onInit() {
     super.onInit();
+    initThemeMode();
     _initPushNotifications();
     _getVersion();
   }
 
+  void initThemeMode() {
+    themeMode = _getThemeModeText();
+  }
+
+  /// 获取当前主题模式文本
+  String _getThemeModeText() {
+    final mode = IXSP.getThemeMode();
+    switch (mode) {
+      case 'light':
+        return Strings.lightMode.tr;
+      case 'dark':
+        return Strings.darkMode.tr;
+      case 'system':
+      default:
+        return Strings.followSystem.tr;
+    }
+  }
+
   /// 获取版本信息
   void _getVersion() async {
     final packageInfo = await PackageInfo.fromPlatform();

+ 12 - 9
lib/app/modules/setting/views/setting_view.dart

@@ -423,11 +423,13 @@ class SettingView extends BaseView<SettingController> {
               trailing: Row(
                 mainAxisSize: MainAxisSize.min,
                 children: [
-                  Text(
-                    '',
-                    style: TextStyle(
-                      fontSize: 13.sp,
-                      color: Get.reactiveTheme.hintColor,
+                  Obx(
+                    () => Text(
+                      controller.themeMode,
+                      style: TextStyle(
+                        fontSize: 13.sp,
+                        color: Get.reactiveTheme.hintColor,
+                      ),
                     ),
                   ),
                   8.horizontalSpace,
@@ -439,8 +441,9 @@ class SettingView extends BaseView<SettingController> {
                 ],
               ),
               onTap: () {
-                // TODO: 跳转到语言选择页面
-                Get.toNamed(Routes.THEME);
+                Get.toNamed(
+                  Routes.THEME,
+                )?.then((_) => controller.initThemeMode());
               },
             ),
             _buildDivider(),
@@ -652,7 +655,7 @@ class SettingView extends BaseView<SettingController> {
                 end: Alignment.bottomCenter,
               ),
               title: Strings.logout.tr,
-              titleColor: const Color(0xFFEF0000),
+              titleColor: DarkThemeColors.errorColor,
               onTap: () {
                 AllDialog.showLogoutConfirm(() {
                   // 退出登录
@@ -738,7 +741,7 @@ class SettingView extends BaseView<SettingController> {
                       child: Icon(
                         IconFont.icon59,
                         size: 20.w,
-                        color: Colors.white,
+                        color: Get.reactiveTheme.hintColor,
                       ),
                     ),
                 ],

+ 1 - 1
lib/app/modules/subscription/controllers/subscription_controller.dart

@@ -120,7 +120,7 @@ class SubscriptionController extends GetxController {
   void onInit() {
     super.onInit();
     _initializeVideoPlayer();
-    _initializeInAppPurchase();
+    // _initializeInAppPurchase();
     _getChannelPlanList();
     refreshSubscriptionStatus();
   }

+ 2 - 29
lib/app/modules/theme/controllers/theme_controller.dart

@@ -1,7 +1,6 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/scheduler.dart';
 import 'package:get/get.dart';
 
+import '../../../../config/theme/theme_extensions/theme_extension.dart';
 import '../../../../config/translations/strings_enum.dart';
 import '../../../data/sp/ix_sp.dart';
 
@@ -44,32 +43,6 @@ class ThemeController extends GetxController {
 
   /// 应用主题
   void _applyTheme(String mode) {
-    ThemeMode themeMode;
-    bool isLight;
-
-    switch (mode) {
-      case 'light':
-        themeMode = ThemeMode.light;
-        isLight = true;
-        break;
-      case 'dark':
-        themeMode = ThemeMode.dark;
-        isLight = false;
-        break;
-      case 'system':
-      default:
-        themeMode = ThemeMode.system;
-        // 获取系统当前的主题
-        final brightness =
-            SchedulerBinding.instance.platformDispatcher.platformBrightness;
-        isLight = brightness == Brightness.light;
-        break;
-    }
-
-    // 更新 GetX 主题
-    Get.changeThemeMode(themeMode);
-
-    // 同步更新 legacy 的 isLight 标记(兼容旧代码)
-    IXSP.setThemeIsLight(isLight);
+    ReactiveTheme.setThemeMode(mode);
   }
 }

+ 2 - 6
lib/app/widgets/submit_btn.dart

@@ -59,9 +59,7 @@ class SubmitButton extends StatelessWidget {
                 ? SpinKitRing(
                     size: 20.w,
                     lineWidth: 2.w,
-                    color:
-                        textColor ??
-                        Get.reactiveTheme.textTheme.bodyLarge!.color!,
+                    color: textColor ?? Colors.white,
                   )
                 : Row(
                     mainAxisAlignment: MainAxisAlignment.center,
@@ -73,9 +71,7 @@ class SubmitButton extends StatelessWidget {
                       Text(
                         text,
                         style: TextStyle(
-                          color:
-                              textColor ??
-                              Get.reactiveTheme.textTheme.bodyLarge!.color,
+                          color: textColor ?? Colors.white,
                           fontSize: fontSize ?? 16.sp,
                           height: 1.2,
                           fontWeight: FontWeight.w500,

+ 0 - 1
lib/config/theme/dark_theme_colors.dart

@@ -96,7 +96,6 @@ class DarkThemeColors {
   static const Color settingSecurityLinearGradientEndColor = Color(0xFFC9433C);
   static const Color validTermColor = Color(0xFFFFCC00);
 
-  static const Color deleteAccountIconColor = Color(0xFFEF0000);
   static const Color disconnectedIconColor = Color(0xFF646776);
   static const Color connectingIconColor = Color(0xFFEA9800);
   static const Color connectedIconColor = Color(0xFF5182E1);

+ 73 - 4
lib/config/theme/theme_extensions/theme_extension.dart

@@ -1,29 +1,98 @@
 import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
 import 'package:get/get.dart';
 import '../../../app/data/sp/ix_sp.dart';
 import '../ix_theme.dart';
 
 /// 响应式主题扩展
-/// 让 Get.theme 变成响应式对象
+/// 支持三种模式:system(跟随系统)、dark、light
 class ReactiveTheme {
   static final RxBool _isLightTheme = true.obs;
 
   /// 初始化主题状态
   static void init() {
-    _isLightTheme.value = IXSP.getThemeIsLight();
+    _updateThemeFromMode();
+  }
+
+  /// 根据当前模式更新主题
+  static void _updateThemeFromMode() {
+    final mode = IXSP.getThemeMode();
+    bool isLight;
+
+    switch (mode) {
+      case 'light':
+        isLight = true;
+        break;
+      case 'dark':
+        isLight = false;
+        break;
+      case 'system':
+      default:
+        // 跟随系统
+        final brightness =
+            SchedulerBinding.instance.platformDispatcher.platformBrightness;
+        isLight = brightness == Brightness.light;
+        break;
+    }
+
+    _isLightTheme.value = isLight;
   }
 
   /// 获取当前主题状态
   static bool get isLightTheme => _isLightTheme.value;
 
-  /// 切换主题
+  /// 获取初始 ThemeMode(用于 GetMaterialApp)
+  static ThemeMode get initialThemeMode {
+    final mode = IXSP.getThemeMode();
+    switch (mode) {
+      case 'light':
+        return ThemeMode.light;
+      case 'dark':
+        return ThemeMode.dark;
+      case 'system':
+      default:
+        return ThemeMode.system;
+    }
+  }
+
+  /// 切换主题(legacy,保持兼容)
   static void toggleTheme() {
     _isLightTheme.value = !_isLightTheme.value;
     IXSP.setThemeIsLight(_isLightTheme.value);
     Get.changeThemeMode(_isLightTheme.value ? ThemeMode.light : ThemeMode.dark);
   }
 
-  /// 设置主题
+  /// 设置主题模式
+  static void setThemeMode(String mode) {
+    IXSP.setThemeMode(mode);
+
+    ThemeMode themeMode;
+    bool isLight;
+
+    switch (mode) {
+      case 'light':
+        themeMode = ThemeMode.light;
+        isLight = true;
+        break;
+      case 'dark':
+        themeMode = ThemeMode.dark;
+        isLight = false;
+        break;
+      case 'system':
+      default:
+        themeMode = ThemeMode.system;
+        final brightness =
+            SchedulerBinding.instance.platformDispatcher.platformBrightness;
+        isLight = brightness == Brightness.light;
+        break;
+    }
+
+    _isLightTheme.value = isLight;
+    IXSP.setThemeIsLight(isLight);
+    Get.changeThemeMode(themeMode);
+  }
+
+  /// 设置主题(legacy,保持兼容)
   static void setTheme(bool isLight) {
     if (_isLightTheme.value != isLight) {
       _isLightTheme.value = isLight;

+ 1 - 1
lib/config/translations/ar_AR/ar_ar_translation.dart

@@ -377,7 +377,7 @@ final Map<String, String> arAR = {
   Strings.upgradeNow: 'الترقية الآن',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'ربط البريد الإلكتروني/مزايا العضوية',
+  Strings.bindEmailMemberBenefits: 'ربط الحساب/مزايا العضوية',
   Strings.bindingAccountEmailProtectsPreRights:
       'ربط الحساب/البريد الإلكتروني يحمي حقوق Pre الخاصة بك.',
   Strings.associatedInterests: 'المزايا المرتبطة',

+ 1 - 1
lib/config/translations/de_DE/de_de_translation.dart

@@ -382,7 +382,7 @@ const Map<String, String> deDE = {
   Strings.upgradeNow: 'Jetzt upgraden',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'E-Mail verknüpfen/Mitgliedervorteile',
+  Strings.bindEmailMemberBenefits: 'Konto verknüpfen/Mitgliedervorteile',
   Strings.bindingAccountEmailProtectsPreRights:
       'Das Verknüpfen von Konto/E-Mail schützt Ihre Pre-Rechte.',
   Strings.associatedInterests: 'Verknüpfte Vorteile',

+ 1 - 1
lib/config/translations/en_US/en_us_translation.dart

@@ -426,7 +426,7 @@ Map<String, String> enUs = {
   Strings.deleteAccountConfirmButton: 'Delete',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'Bind email/Member benefits',
+  Strings.bindEmailMemberBenefits: 'Bind account/Member benefits',
   Strings.bindingAccountEmailProtectsPreRights:
       'Binding account/email protects your Pre rights.',
   Strings.associatedInterests: 'Associated interests',

+ 1 - 1
lib/config/translations/es_ES/es_es_translation.dart

@@ -387,7 +387,7 @@ const Map<String, String> esEs = {
   Strings.upgradeNow: 'Actualizar ahora',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'Vincular correo/Beneficios de membresía',
+  Strings.bindEmailMemberBenefits: 'Vincular cuenta/Beneficios de membresía',
   Strings.bindingAccountEmailProtectsPreRights:
       'Vincular cuenta/correo protege tus derechos Pre.',
   Strings.associatedInterests: 'Beneficios asociados',

+ 1 - 1
lib/config/translations/fa_IR/fa_ir_translation.dart

@@ -383,7 +383,7 @@ const Map<String, String> faIR = {
   Strings.upgradeNow: 'اکنون ارتقا دهید',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'اتصال ایمیل/مزایای عضویت',
+  Strings.bindEmailMemberBenefits: 'اتصال حساب/مزایای عضویت',
   Strings.bindingAccountEmailProtectsPreRights:
       'اتصال حساب/ایمیل از حقوق Pre شما محافظت می‌کند.',
   Strings.associatedInterests: 'مزایای مرتبط',

+ 1 - 1
lib/config/translations/fr_FR/fr_fr_translation.dart

@@ -388,7 +388,7 @@ const Map<String, String> frFR = {
   Strings.upgradeNow: 'Mettre à niveau maintenant',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'Lier l\'email/Avantages membres',
+  Strings.bindEmailMemberBenefits: 'Lier le compte/Avantages membres',
   Strings.bindingAccountEmailProtectsPreRights:
       'Lier votre compte/email protège vos droits Pre.',
   Strings.associatedInterests: 'Avantages associés',

+ 1 - 1
lib/config/translations/hi_IN/hi_in_translation.dart

@@ -296,7 +296,7 @@ Map<String, String> hiIN = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'ईमेल जोड़ें/सदस्य लाभ',
+  Strings.bindEmailMemberBenefits: 'खाता जोड़ें/सदस्य लाभ',
   Strings.bindingAccountEmailProtectsPreRights:
       'खाता/ईमेल जोड़ने से आपके Pre अधिकार सुरक्षित रहते हैं।',
   Strings.associatedInterests: 'संबंधित लाभ',

+ 1 - 1
lib/config/translations/id_ID/id_id_translation.dart

@@ -296,7 +296,7 @@ Map<String, String> idID = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'Hubungkan email/Manfaat anggota',
+  Strings.bindEmailMemberBenefits: 'Hubungkan akun/Manfaat anggota',
   Strings.bindingAccountEmailProtectsPreRights:
       'Menghubungkan akun/email melindungi hak Pre Anda.',
   Strings.associatedInterests: 'Manfaat terkait',

+ 1 - 1
lib/config/translations/ja_JP/ja_jp_translation.dart

@@ -362,7 +362,7 @@ const Map<String, String> jaJP = {
   Strings.upgradeNow: '今すぐアップグレード',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'メール連携/会員特典',
+  Strings.bindEmailMemberBenefits: 'アカウント連携/会員特典',
   Strings.bindingAccountEmailProtectsPreRights:
       'アカウント/メールを連携すると、Pre特典が保護されます。',
   Strings.associatedInterests: '関連特典',

+ 1 - 1
lib/config/translations/ko_KR/ko_kr_translation.dart

@@ -355,7 +355,7 @@ const Map<String, String> koKR = {
   Strings.upgradeNow: '지금 업그레이드',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: '이메일 연결/회원 혜택',
+  Strings.bindEmailMemberBenefits: '계정 연결/회원 혜택',
   Strings.bindingAccountEmailProtectsPreRights:
       '계정/이메일을 연결하면 Pre 혜택이 보호됩니다.',
   Strings.associatedInterests: '관련 혜택',

+ 1 - 1
lib/config/translations/my_MM/my_mm_translation.dart

@@ -391,7 +391,7 @@ const Map<String, String> myMM = {
   Strings.upgradeNow: 'ယခုပင် အဆင့်မြှင့်ပါ',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'အီးမေးလ်ချိတ်ဆက်ခြင်း/အသင်းဝင်အကျိုးခံစားခွင့်များ',
+  Strings.bindEmailMemberBenefits: 'အကောင့်ချိတ်ဆက်ခြင်း/အသင်းဝင်အကျိုးခံစားခွင့်များ',
   Strings.bindingAccountEmailProtectsPreRights:
       'အကောင့်/အီးမေးလ်ချိတ်ဆက်ခြင်းသည် သင်၏ Pre အခွင့်အရေးများကို ကာကွယ်ပေးသည်။',
   Strings.associatedInterests: 'ဆက်စပ်အကျိုးခံစားခွင့်များ',

+ 1 - 1
lib/config/translations/pt_BR/pt_br_translation.dart

@@ -426,7 +426,7 @@ Map<String, String> ptBR = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'Vincular email/Benefícios de membro',
+  Strings.bindEmailMemberBenefits: 'Vincular conta/Benefícios de membro',
   Strings.bindingAccountEmailProtectsPreRights:
       'Vincular conta/email protege seus direitos Pre.',
   Strings.associatedInterests: 'Benefícios associados',

+ 1 - 1
lib/config/translations/ru_RU/ru_ru_translation.dart

@@ -387,7 +387,7 @@ const Map<String, String> ruRU = {
   Strings.upgradeNow: 'Обновить сейчас',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'Привязать email/Преимущества членства',
+  Strings.bindEmailMemberBenefits: 'Привязать аккаунт/Преимущества членства',
   Strings.bindingAccountEmailProtectsPreRights:
       'Привязка аккаунта/email защищает ваши права Pre.',
   Strings.associatedInterests: 'Связанные преимущества',

+ 1 - 1
lib/config/translations/strings_enum.dart

@@ -444,7 +444,7 @@ class Strings {
   static const String pushNotifications = 'Push Notifications';
   static const String upgradeNow = 'Upgrade Now';
 
-  static const String bindEmailMemberBenefits = 'Bind email/Member benefits';
+  static const String bindEmailMemberBenefits = 'Bind account/Member benefits';
   static const String bindingAccountEmailProtectsPreRights =
       'Binding account/email protects your Pre rights.';
   static const String associatedInterests = 'Associated interests';

+ 1 - 1
lib/config/translations/th_TH/th_th_translation.dart

@@ -296,7 +296,7 @@ Map<String, String> thTH = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'เชื่อมต่ออีเมล/สิทธิประโยชน์สมาชิก',
+  Strings.bindEmailMemberBenefits: 'เชื่อมต่อบัญชี/สิทธิประโยชน์สมาชิก',
   Strings.bindingAccountEmailProtectsPreRights:
       'การเชื่อมต่อบัญชี/อีเมลจะปกป้องสิทธิ์ Pre ของคุณ',
   Strings.associatedInterests: 'สิทธิประโยชน์ที่เกี่ยวข้อง',

+ 1 - 1
lib/config/translations/tk_TM/tk_tm_translation.dart

@@ -423,7 +423,7 @@ Map<String, String> tkTM = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'E-poçta birikdirmek/Agza artykmaçlyklary',
+  Strings.bindEmailMemberBenefits: 'Hasap birikdirmek/Agza artykmaçlyklary',
   Strings.bindingAccountEmailProtectsPreRights:
       'Hasap/e-poçta birikdirmek Pre hukuklaryňyzy goraýar.',
   Strings.associatedInterests: 'Baglanyşykly artykmaçlyklar',

+ 1 - 1
lib/config/translations/tl_PH/tl_ph_translation.dart

@@ -296,7 +296,7 @@ Map<String, String> tlPH = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'I-link ang email/Mga benepisyo ng miyembro',
+  Strings.bindEmailMemberBenefits: 'I-link ang account/Mga benepisyo ng miyembro',
   Strings.bindingAccountEmailProtectsPreRights:
       'Ang pag-link ng account/email ay nagpoprotekta sa iyong mga karapatan sa Pre.',
   Strings.associatedInterests: 'Mga kaugnay na benepisyo',

+ 1 - 1
lib/config/translations/tr_TR/tr_tr_translation.dart

@@ -296,7 +296,7 @@ Map<String, String> trTR = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'E-posta bağla/Üyelik avantajları',
+  Strings.bindEmailMemberBenefits: 'Hesap bağla/Üyelik avantajları',
   Strings.bindingAccountEmailProtectsPreRights:
       'Hesap/e-posta bağlamak Pre haklarınızı korur.',
   Strings.associatedInterests: 'İlişkili avantajlar',

+ 1 - 1
lib/config/translations/vi_VN/vi_vn_translation.dart

@@ -337,7 +337,7 @@ Map<String, String> viVN = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: 'Liên kết email/Quyền lợi thành viên',
+  Strings.bindEmailMemberBenefits: 'Liên kết tài khoản/Quyền lợi thành viên',
   Strings.bindingAccountEmailProtectsPreRights:
       'Liên kết tài khoản/email bảo vệ quyền Pre của bạn.',
   Strings.associatedInterests: 'Quyền lợi liên quan',

+ 1 - 1
lib/config/translations/zh_TW/zh_tw_translation.dart

@@ -389,7 +389,7 @@ Map<String, String> zhTW = {
   Strings.trLang: 'Türkçe',
 
   // Bind email / Member benefits
-  Strings.bindEmailMemberBenefits: '綁定郵箱/會員權益',
+  Strings.bindEmailMemberBenefits: '綁定帳號/會員權益',
   Strings.bindingAccountEmailProtectsPreRights: '綁定帳號/郵箱可保護您的Pre權益。',
   Strings.associatedInterests: '關聯權益',
   Strings.associatedInterestsDesc:

+ 9 - 1
pubspec.lock

@@ -591,7 +591,7 @@ packages:
     source: hosted
     version: "0.7.7+1"
   flutter_native_splash:
-    dependency: "direct dev"
+    dependency: "direct main"
     description:
       name: flutter_native_splash
       sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc"
@@ -1360,6 +1360,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "3.0.0"
+  shortid:
+    dependency: "direct main"
+    description:
+      name: shortid
+      sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.1.2"
   sky_engine:
     dependency: transitive
     description: flutter

+ 1 - 1
pubspec.yaml

@@ -89,6 +89,7 @@ dependencies:
   shelf: ^1.4.2 # windows 进程通信
   ffi: ^2.1.5 # FFI
   shortid: ^0.1.2 # windows 菜单id
+  flutter_native_splash: ^2.4.6 # 应用启动页
   # hive_ce: ^2.17.0 # 本地缓存
 
 dev_dependencies:
@@ -106,7 +107,6 @@ dev_dependencies:
   json_serializable: ^6.9.3
   pigeon: ^26.0.1
   flutter_launcher_icons: ^0.14.4 # 应用图标
-  flutter_native_splash: ^2.4.6 # 应用启动页
   change_app_package_name: ^1.5.0 # 改变应用包名
   rename_app: ^1.6.5 # 重命名应用