| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- 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<WebController> {
- 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() {
- return Obx(() {
- // 使用响应式状态栏样式
- final statusBarStyle = controller.currentStatusBarStyle.value;
- return AnnotatedRegion<SystemUiOverlayStyle>(
- value: statusBarStyle,
- child: Stack(
- children: [
- InAppWebView(
- 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,
- ),
- 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() ?? '';
- try {
- final uri = Uri.parse(url);
- // 处理 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;
- }
- // 处理下载链接
- else 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;
- }
- // 处理 http/https 链接
- if (uri.scheme == 'http' || uri.scheme == 'https') {
- return NavigationActionPolicy.ALLOW;
- }
- // 处理其他 scheme
- if (await canLaunchUrl(uri)) {
- await launchUrl(
- uri,
- mode: LaunchMode.externalApplication,
- );
- }
- } catch (e) {
- log("Error handling shouldOverrideUrlLoading: $e");
- }
- return NavigationActionPolicy.CANCEL;
- },
- ),
- Obx(
- () => controller.isLoading.value
- ? LinearProgressIndicator(
- value: controller.loadingProgress.value,
- backgroundColor:
- Get.reactiveTheme.scaffoldBackgroundColor,
- minHeight: 2.w,
- valueColor: AlwaysStoppedAnimation<Color>(
- Get.reactiveTheme.primaryColor,
- ),
- )
- : const SizedBox.shrink(),
- ),
- ],
- ),
- );
- });
- }
- }
|