web_view.dart 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_inappwebview/flutter_inappwebview.dart';
  3. import 'package:flutter_screenutil/flutter_screenutil.dart';
  4. import 'package:get/get.dart';
  5. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  6. import 'package:url_launcher/url_launcher.dart';
  7. import '../../../../utils/log/logger.dart';
  8. import '../../../base/base_view.dart';
  9. import '../../../widgets/ix_app_bar.dart';
  10. import '../controllers/web_controller.dart';
  11. class WebView extends BaseView<WebController> {
  12. const WebView({super.key});
  13. @override
  14. PreferredSizeWidget? get appBar => IXAppBar(
  15. title: controller.title,
  16. // 不需要传递颜色参数,会自动使用响应式主题
  17. onBackPressed: () async {
  18. if (await controller.checkCanGoBack()) {
  19. controller.goBack();
  20. } else {
  21. Get.back();
  22. }
  23. },
  24. actions: [
  25. IconButton(
  26. onPressed: () {
  27. controller.reload();
  28. },
  29. icon: const Icon(Icons.refresh_rounded),
  30. ),
  31. Obx(
  32. () => controller.canGoBack.value
  33. ? IconButton(
  34. onPressed: () {
  35. Get.back();
  36. },
  37. icon: const Icon(Icons.close_rounded),
  38. )
  39. : const SizedBox.shrink(),
  40. ),
  41. ],
  42. );
  43. @override
  44. Widget buildContent(BuildContext context) {
  45. return _buildWebContent();
  46. }
  47. Widget _buildWebContent() {
  48. return Stack(
  49. children: [
  50. InAppWebView(
  51. initialUrlRequest: URLRequest(url: WebUri(controller.url)),
  52. initialSettings: InAppWebViewSettings(
  53. userAgent: controller.userAgent,
  54. javaScriptEnabled: true,
  55. useShouldOverrideUrlLoading: true,
  56. useOnLoadResource: true,
  57. mediaPlaybackRequiresUserGesture: false,
  58. allowsInlineMediaPlayback: true,
  59. iframeAllow: "camera; microphone",
  60. iframeAllowFullscreen: true,
  61. ),
  62. onWebViewCreated: (InAppWebViewController webViewController) {
  63. controller.setWebViewController(webViewController);
  64. },
  65. onProgressChanged:
  66. (InAppWebViewController webViewController, int progress) {
  67. controller.loadingProgress.value = progress / 100;
  68. controller.isLoading.value = progress < 100;
  69. },
  70. onLoadStart: (InAppWebViewController webViewController, Uri? url) {
  71. controller.checkCanGoBack();
  72. },
  73. onLoadStop: (InAppWebViewController webViewController, Uri? url) {
  74. controller.isLoading.value = false;
  75. controller.loadingProgress.value = 0.0;
  76. controller.checkCanGoBack();
  77. },
  78. onReceivedError:
  79. (
  80. InAppWebViewController webViewController,
  81. WebResourceRequest request,
  82. WebResourceError error,
  83. ) {
  84. controller.isLoading.value = false;
  85. controller.loadingProgress.value = 0.0;
  86. },
  87. shouldOverrideUrlLoading:
  88. (
  89. InAppWebViewController webViewController,
  90. NavigationAction navigationAction,
  91. ) async {
  92. final url = navigationAction.request.url?.toString() ?? '';
  93. try {
  94. final uri = Uri.parse(url);
  95. // 处理 intent:// 链接
  96. if (url.startsWith("intent://")) {
  97. final parsedUrl = controller.parseIntentUrl(url);
  98. final fallbackUrl = controller.parseFallbackUrl(url);
  99. if (parsedUrl != null &&
  100. await canLaunchUrl(Uri.parse(parsedUrl))) {
  101. await launchUrl(
  102. Uri.parse(parsedUrl),
  103. mode: LaunchMode.externalApplication,
  104. );
  105. } else if (fallbackUrl != null &&
  106. await canLaunchUrl(Uri.parse(fallbackUrl))) {
  107. await launchUrl(
  108. Uri.parse(fallbackUrl),
  109. mode: LaunchMode.externalApplication,
  110. );
  111. }
  112. return NavigationActionPolicy.CANCEL;
  113. }
  114. // 处理下载链接
  115. else if (uri.path.endsWith(".apk") ||
  116. uri.path.endsWith(".pdf") ||
  117. uri.path.contains("download")) {
  118. if (await canLaunchUrl(uri)) {
  119. await launchUrl(
  120. uri,
  121. mode: LaunchMode.externalApplication,
  122. );
  123. }
  124. return NavigationActionPolicy.CANCEL;
  125. }
  126. // 处理 http/https 链接
  127. if (uri.scheme == 'http' || uri.scheme == 'https') {
  128. return NavigationActionPolicy.ALLOW;
  129. }
  130. // 处理其他 scheme
  131. if (await canLaunchUrl(uri)) {
  132. await launchUrl(uri, mode: LaunchMode.externalApplication);
  133. }
  134. } catch (e) {
  135. log("Error handling shouldOverrideUrlLoading: $e");
  136. }
  137. return NavigationActionPolicy.CANCEL;
  138. },
  139. ),
  140. Obx(
  141. () => controller.isLoading.value
  142. ? LinearProgressIndicator(
  143. value: controller.loadingProgress.value,
  144. backgroundColor: Get.reactiveTheme.scaffoldBackgroundColor,
  145. minHeight: 2.w,
  146. valueColor: AlwaysStoppedAnimation<Color>(
  147. Get.reactiveTheme.primaryColor,
  148. ),
  149. )
  150. : const SizedBox.shrink(),
  151. ),
  152. ],
  153. );
  154. }
  155. }