import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:nomo/config/theme/theme_extensions/theme_extension.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../../utils/log/logger.dart'; import '../../../base/base_view.dart'; import '../../../widgets/ix_app_bar.dart'; import '../controllers/web_controller.dart'; class WebView extends BaseView { const WebView({super.key}); @override PreferredSizeWidget? get appBar { // 如果是全面屏模式,不显示AppBar return controller.isFullScreen.value ? null : IXAppBar( title: controller.title, // 不需要传递颜色参数,会自动使用响应式主题 onBackPressed: () async { if (await controller.checkCanGoBack()) { controller.goBack(); } else { Get.back(); } }, actions: [ IconButton( onPressed: () { controller.reload(); }, icon: const Icon(Icons.refresh_rounded), ), Obx( () => controller.canGoBack.value ? IconButton( onPressed: () { Get.back(); }, icon: const Icon(Icons.close_rounded), ) : const SizedBox.shrink(), ), ], ); } @override bool get extendBodyBehindAppBar => controller.isFullScreen.value; @override Widget buildContent(BuildContext context) { return _buildWebContent(); } Widget _buildWebContent() { // 将 WebView 移出 Obx,避免重复创建 return Obx( () => AnnotatedRegion( 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, mediaPlaybackRequiresUserGesture: false, allowsInlineMediaPlayback: true, allowFileAccessFromFileURLs: true, allowUniversalAccessFromFileURLs: true, // iOS 特定设置 allowsBackForwardNavigationGestures: true, suppressesIncrementalRendering: false, transparentBackground: false, isInspectable: true, ), onWebViewCreated: (InAppWebViewController webViewController) async { controller.setWebViewController(webViewController); controller.setupJavaScriptHandlers(webViewController); // 如果 URL 是 assets 路径,则使用 loadData 加载 if (controller.url.startsWith('assets/')) { await controller.loadAssetHtml(controller.url); } }, onProgressChanged: (InAppWebViewController webViewController, int progress) { controller.loadingProgress.value = progress / 100; controller.isLoading.value = progress < 100; }, onLoadStart: (InAppWebViewController webViewController, Uri? url) { controller.checkCanGoBack(); }, onLoadStop: (InAppWebViewController webViewController, Uri? url) async { controller.isLoading.value = false; controller.loadingProgress.value = 0.0; controller.checkCanGoBack(); // 页面加载完成后再次注入,确保万无一失 await controller.injectNativeAttrs(); }, onReceivedError: ( InAppWebViewController webViewController, WebResourceRequest request, WebResourceError error, ) { controller.isLoading.value = false; controller.loadingProgress.value = 0.0; }, shouldOverrideUrlLoading: ( InAppWebViewController webViewController, NavigationAction navigationAction, ) 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); final fallbackUrl = controller.parseFallbackUrl(url); if (parsedUrl != null && await canLaunchUrl(Uri.parse(parsedUrl))) { await launchUrl( Uri.parse(parsedUrl), mode: LaunchMode.externalApplication, ); } else if (fallbackUrl != null && await canLaunchUrl(Uri.parse(fallbackUrl))) { await launchUrl( Uri.parse(fallbackUrl), mode: LaunchMode.externalApplication, ); } return NavigationActionPolicy.CANCEL; } // 处理下载链接 if (uri.path.endsWith(".apk") || uri.path.endsWith(".pdf") || uri.path.contains("download")) { if (await canLaunchUrl(uri)) { await launchUrl( uri, mode: LaunchMode.externalApplication, ); } return NavigationActionPolicy.CANCEL; } // 处理其他自定义 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.ALLOW; }, ), // 加载进度条 Obx( () => controller.isLoading.value ? LinearProgressIndicator( value: controller.loadingProgress.value, backgroundColor: Get.reactiveTheme.scaffoldBackgroundColor, minHeight: 2.w, valueColor: AlwaysStoppedAnimation( Get.reactiveTheme.primaryColor, ), ) : const SizedBox.shrink(), ), ], ), ), ); } }