فهرست منبع

fix: 修复bug

lilu 3 ماه پیش
والد
کامیت
d238972910

+ 2 - 0
android/app/src/main/kotlin/app/xixi/nomo/XRayService.kt

@@ -113,6 +113,8 @@ class XRayService : VpnService() {
     override fun onRevoke() {
         super.onRevoke()
         VLog.i(TAG, "onRevoke")
+        // 停止计时
+        stopTimer()
     }
 
     override fun onTaskRemoved(rootIntent: Intent?) {

+ 5 - 0
ios/Podfile

@@ -29,6 +29,7 @@ flutter_ios_podfile_setup
 
 target 'Runner' do
   use_frameworks!
+  use_modular_headers!
 
   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
   target 'RunnerTests' do
@@ -39,5 +40,9 @@ end
 post_install do |installer|
   installer.pods_project.targets.each do |target|
     flutter_additional_ios_build_settings(target)
+    target.build_configurations.each do |config|
+      # 强制设置所有Pod的iOS部署目标为13.0
+      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
+    end
   end
 end

+ 11 - 1
ios/Podfile.lock

@@ -1,6 +1,9 @@
 PODS:
   - app_links (6.4.1):
     - Flutter
+  - awesome_notifications (0.10.0):
+    - Flutter
+    - IosAwnCore (~> 0.10.0)
   - connectivity_plus (0.0.1):
     - Flutter
   - device_info_plus (0.0.1):
@@ -24,6 +27,7 @@ PODS:
   - in_app_purchase_storekit (0.0.1):
     - Flutter
     - FlutterMacOS
+  - IosAwnCore (0.10.0)
   - network_info_plus (0.0.1):
     - Flutter
   - OrderedSet (6.0.3)
@@ -50,6 +54,7 @@ PODS:
 
 DEPENDENCIES:
   - app_links (from `.symlinks/plugins/app_links/ios`)
+  - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`)
   - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
   - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
   - Flutter (from `Flutter`)
@@ -71,11 +76,14 @@ DEPENDENCIES:
 
 SPEC REPOS:
   trunk:
+    - IosAwnCore
     - OrderedSet
 
 EXTERNAL SOURCES:
   app_links:
     :path: ".symlinks/plugins/app_links/ios"
+  awesome_notifications:
+    :path: ".symlinks/plugins/awesome_notifications/ios"
   connectivity_plus:
     :path: ".symlinks/plugins/connectivity_plus/ios"
   device_info_plus:
@@ -115,6 +123,7 @@ EXTERNAL SOURCES:
 
 SPEC CHECKSUMS:
   app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
+  awesome_notifications: 0f432b28098d193920b11a44cfa9d2d9313a3888
   connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
   device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
   Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
@@ -124,6 +133,7 @@ SPEC CHECKSUMS:
   flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
   image_gallery_saver_plus: e597bf65a7846979417a3eae0763b71b6dfec6c3
   in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
+  IosAwnCore: 653786a911089012092ce831f2945cd339855a89
   network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
   OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
   package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
@@ -135,6 +145,6 @@ SPEC CHECKSUMS:
   url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
   video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
 
-PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
+PODFILE CHECKSUM: b52bb60937f8c5a794d2d528df0bacde988c6b6a
 
 COCOAPODS: 1.16.2

+ 3 - 1
ios/Runner.xcodeproj/project.pbxproj

@@ -101,7 +101,6 @@
 				445206784DC0E2AC8D9085D5 /* Pods-RunnerTests.release.xcconfig */,
 				0068BBDC814DAA313C80800F /* Pods-RunnerTests.profile.xcconfig */,
 			);
-			name = Pods;
 			path = Pods;
 			sourceTree = "<group>";
 		};
@@ -495,6 +494,7 @@
 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				DEVELOPMENT_TEAM = 5S4V9Q6Y48;
@@ -678,6 +678,7 @@
 			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				DEVELOPMENT_TEAM = 5S4V9Q6Y48;
@@ -701,6 +702,7 @@
 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				DEVELOPMENT_TEAM = 5S4V9Q6Y48;

+ 15 - 0
ios/Runner/Info.plist

@@ -51,5 +51,20 @@
 	<string>We need access to save your Pre Code image to your photo library.</string>
 	<key>NSPhotoLibraryUsageDescription</key>
 	<string>We need access to save your Pre Code image to your photo library.</string>
+	
+	<!-- WebView 相关配置 -->
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+		<key>NSAllowsLocalNetworking</key>
+		<true/>
+	</dict>
+	
+	<!-- WebView 可能需要的相机和麦克风权限 -->
+	<key>NSCameraUsageDescription</key>
+	<string>This app needs camera access to use camera features in web pages.</string>
+	<key>NSMicrophoneUsageDescription</key>
+	<string>This app needs microphone access to use audio features in web pages.</string>
 </dict>
 </plist>

+ 9 - 6
lib/app/app.dart

@@ -9,7 +9,6 @@ import '../config/translations/strings_enum.dart';
 import '../utils/developer/ix_developer_tools.dart';
 import '../utils/device_manager.dart';
 import '../utils/ix_back_button_dispatcher.dart';
-import '../utils/log/logger.dart';
 import 'components/ix_snackbar.dart';
 import 'constants/configs.dart';
 import 'data/sp/ix_sp.dart';
@@ -30,7 +29,8 @@ class App extends StatelessWidget {
       minTextAdapt: true,
       splitScreenMode: true,
       useInheritedMediaQuery: true,
-      rebuildFactor: (old, data) => true,
+      // 只在屏幕尺寸真正变化时才重建(如旋转屏幕),而不是每次 MediaQuery 变化都重建
+      rebuildFactor: (old, data) => old.size != data.size,
       builder: (context, widget) => RefreshConfiguration(
         headerBuilder: () =>
             GradientCircleHeader(), // 配置默认页眉指示器。如果您每个页面都有相同的页眉指示器,您需要设置此
@@ -40,7 +40,7 @@ class App extends StatelessWidget {
           mass: 1,
           stiffness: 1000,
           damping: 100,
-        ), // 自定义弹簧回弹动画,属性含义请参阅Flutter API
+        ), // 设置没有回弹动画,属性含义请参阅Flutter API
         maxOverScrollExtent: 40, //头部最大拖动范围。如果发生超出视图区域的情况,请设置此属性
         maxUnderScrollExtent: 0, // 底部最大拖动范围
         enableScrollWhenRefreshCompleted:
@@ -54,8 +54,11 @@ class App extends StatelessWidget {
   }
 
   Widget _buildMaterialApp() {
-    bool isLightTheme = IXSP.getThemeIsLight();
-    log('App', 'isLightTheme: $isLightTheme');
+    // 只在初始化时读取一次主题设置,后续通过 Get.changeThemeMode() 动态切换
+    final initialThemeMode = ReactiveTheme.isLightTheme
+        ? ThemeMode.light
+        : ThemeMode.dark;
+
     return GetMaterialApp.router(
       title: 'ixVPN',
       useInheritedMediaQuery: true,
@@ -64,7 +67,7 @@ class App extends StatelessWidget {
       getPages: AppPages.routes,
       theme: IXTheme.getThemeData(isLight: true),
       darkTheme: IXTheme.getThemeData(isLight: false),
-      themeMode: isLightTheme ? ThemeMode.light : ThemeMode.dark,
+      themeMode: initialThemeMode,
       fallbackLocale: LocalizationService.defaultLanguage,
       locale: IXSP.getCurrentLocal(),
       translations: LocalizationService.getInstance(),

+ 14 - 5
lib/app/modules/web/controllers/web_controller.dart

@@ -59,13 +59,19 @@ class WebController extends GetxController {
     try {
       final htmlContent = await rootBundle.loadString(assetPath);
       if (webViewController != null) {
+        // iOS 需要一个有效的 baseUrl,使用 file:// 协议
+        // 这样可以确保相对路径和本地资源正确加载
+        final baseUrl = Platform.isIOS
+            ? WebUri('file:///flutter_assets/')
+            : WebUri('https://localhost/');
+
         await webViewController!.loadData(
           data: htmlContent,
-          baseUrl: WebUri('about:blank'),
+          baseUrl: baseUrl,
           mimeType: 'text/html',
           encoding: 'utf-8',
         );
-        log(TAG, '成功加载本地 HTML: $assetPath');
+        log(TAG, '成功加载本地 HTML: $assetPath (baseUrl: $baseUrl)');
       }
     } catch (e) {
       log(TAG, '加载本地 HTML 失败: $e');
@@ -124,11 +130,14 @@ class WebController extends GetxController {
           userAgent: userAgent!,
           javaScriptEnabled: true,
           useShouldOverrideUrlLoading: true,
-          useOnLoadResource: true,
           mediaPlaybackRequiresUserGesture: false,
           allowsInlineMediaPlayback: true,
-          iframeAllow: "camera; microphone",
-          iframeAllowFullscreen: true,
+
+          // iOS 特定设置
+          allowsBackForwardNavigationGestures: true,
+          suppressesIncrementalRendering: false,
+          transparentBackground: false,
+          isInspectable: true,
         ),
       );
     }

+ 43 - 25
lib/app/modules/web/views/web_view.dart

@@ -60,28 +60,28 @@ class WebView extends BaseView<WebController> {
   }
 
   Widget _buildWebContent() {
-    return Obx(() {
-      // 使用响应式状态栏样式
-      final statusBarStyle = controller.currentStatusBarStyle.value;
-
-      return AnnotatedRegion<SystemUiOverlayStyle>(
-        value: statusBarStyle,
+    // 将 WebView 移出 Obx,避免重复创建
+    return Obx(
+      () => AnnotatedRegion<SystemUiOverlayStyle>(
+        value: controller.currentStatusBarStyle.value,
         child: Stack(
           children: [
             InAppWebView(
+              key: const ValueKey('nomo_webview'), // 添加唯一 key
               initialUrlRequest: URLRequest(url: WebUri(controller.url)),
-
               initialSettings: InAppWebViewSettings(
                 userAgent: controller.userAgent,
                 javaScriptEnabled: true,
                 useShouldOverrideUrlLoading: true,
-                useOnLoadResource: true,
                 mediaPlaybackRequiresUserGesture: false,
                 allowsInlineMediaPlayback: true,
-                iframeAllow: "camera; microphone",
-                iframeAllowFullscreen: true,
                 allowFileAccessFromFileURLs: true,
                 allowUniversalAccessFromFileURLs: true,
+                // iOS 特定设置
+                allowsBackForwardNavigationGestures: true,
+                suppressesIncrementalRendering: false,
+                transparentBackground: false,
+                isInspectable: true,
               ),
               onWebViewCreated:
                   (InAppWebViewController webViewController) async {
@@ -125,9 +125,22 @@ class WebView extends BaseView<WebController> {
                   ) async {
                     final url = navigationAction.request.url?.toString() ?? '';
 
+                    // 如果是空 URL 或本地资源,允许加载
+                    if (url.isEmpty ||
+                        url.startsWith('assets/') ||
+                        url.startsWith('file://') ||
+                        url.startsWith('about:')) {
+                      return NavigationActionPolicy.ALLOW;
+                    }
+
                     try {
                       final uri = Uri.parse(url);
 
+                      // 处理 http/https 链接 - 允许在 WebView 中加载
+                      if (uri.scheme == 'http' || uri.scheme == 'https') {
+                        return NavigationActionPolicy.ALLOW;
+                      }
+
                       // 处理 intent:// 链接
                       if (url.startsWith("intent://")) {
                         final parsedUrl = controller.parseIntentUrl(url);
@@ -148,8 +161,9 @@ class WebView extends BaseView<WebController> {
                         }
                         return NavigationActionPolicy.CANCEL;
                       }
+
                       // 处理下载链接
-                      else if (uri.path.endsWith(".apk") ||
+                      if (uri.path.endsWith(".apk") ||
                           uri.path.endsWith(".pdf") ||
                           uri.path.contains("download")) {
                         if (await canLaunchUrl(uri)) {
@@ -161,25 +175,29 @@ class WebView extends BaseView<WebController> {
                         return NavigationActionPolicy.CANCEL;
                       }
 
-                      // 处理 http/https 链接
-                      if (uri.scheme == 'http' || uri.scheme == 'https') {
-                        return NavigationActionPolicy.ALLOW;
-                      }
-
-                      // 处理其他 scheme
-                      if (await canLaunchUrl(uri)) {
-                        await launchUrl(
-                          uri,
-                          mode: LaunchMode.externalApplication,
-                        );
+                      // 处理其他自定义 scheme(如 tel:, mailto:, weixin: 等)
+                      if (uri.scheme != 'http' &&
+                          uri.scheme != 'https' &&
+                          uri.scheme != 'file' &&
+                          uri.scheme != 'about') {
+                        if (await canLaunchUrl(uri)) {
+                          await launchUrl(
+                            uri,
+                            mode: LaunchMode.externalApplication,
+                          );
+                          return NavigationActionPolicy.CANCEL;
+                        }
                       }
                     } catch (e) {
                       log("Error handling shouldOverrideUrlLoading: $e");
                     }
 
-                    return NavigationActionPolicy.CANCEL;
+                    // 默认允许加载
+                    return NavigationActionPolicy.ALLOW;
                   },
             ),
+
+            // 加载进度条
             Obx(
               () => controller.isLoading.value
                   ? LinearProgressIndicator(
@@ -195,7 +213,7 @@ class WebView extends BaseView<WebController> {
             ),
           ],
         ),
-      );
-    });
+      ),
+    );
   }
 }

+ 2 - 3
lib/app/routes/app_pages.dart

@@ -58,8 +58,7 @@ class AppPages {
       name: _Paths.SPLASH,
       page: () => const SplashView(),
       binding: SplashBinding(),
-      transition: Transition.native,
-      curve: Curves.easeInOut,
+      transition: Transition.noTransition,
     ),
     GetPage(
       name: _Paths.HOME,
@@ -71,7 +70,7 @@ class AppPages {
       name: _Paths.NODE,
       page: () => const NodeView(),
       binding: NodeBinding(),
-      transition: Transition.native,
+      transition: Transition.rightToLeftWithFade,
       curve: Curves.easeInOut,
       preventDuplicates: true, // 防止重复打开同一个页面
     ),