From 1e868ad23eb6c25d011b992ccb18a7edec17ed44 Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Sat, 20 Jul 2024 13:36:09 +0800 Subject: [PATCH] feat:webview库替换 --- lib/common/pages/wow_web_page.dart | 309 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------- lib/common/widgets/we_app_bar.dart | 24 ++++++++++++++---------- lib/common/widgets/webview_dialog.dart | 75 ++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- lib/pages/home/view.dart | 38 ++++++++++++++++++++++++++------------ pubspec.yaml | 4 +++- 5 files changed, 361 insertions(+), 89 deletions(-) diff --git a/lib/common/pages/wow_web_page.dart b/lib/common/pages/wow_web_page.dart index bdc3f6f..c6814a2 100644 --- a/lib/common/pages/wow_web_page.dart +++ b/lib/common/pages/wow_web_page.dart @@ -1,13 +1,21 @@ +import 'dart:collection'; + import 'package:flutter/material.dart'; -import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:webview_flutter/webview_flutter.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/common/widgets/we_app_bar.dart'; +import '../../utils/log_util.dart'; +import '../../utils/toast_util.dart'; + class WowWebViewPage extends StatefulWidget { - const WowWebViewPage({super.key, required this.urlStr, required this.webViewTitle}); + WowWebViewPage( + {super.key, required this.urlStr, required this.webViewTitle}); final String urlStr; - final String webViewTitle; + String webViewTitle; @override State createState() { @@ -15,9 +23,59 @@ class WowWebViewPage extends StatefulWidget { } } +/// 生成进度条组件,进度从0 ~ 1 +_createProgressBar(double progress, BuildContext context) { + return LinearProgressIndicator( + backgroundColor: Colors.white70.withOpacity(0), + value: progress == 1.0 ? 0 : progress, + valueColor: const AlwaysStoppedAnimation(Colors.blue), + ); +} + class _WowWebViewPageState extends State { + late InAppWebViewController? _inAppWebViewController; + final GlobalKey webViewKey = GlobalKey(); + double _progress = 0; + bool isCanGoBack = false; + bool isCanForward = false; + late final String defaultWebViewTitle = widget.webViewTitle; + + // InAppWebViewSettings webViewSettings = InAppWebViewSettings( + // useShouldOverrideUrlLoading: true, + // mediaPlaybackRequiresUserGesture: true, + // + // /// android 支持HybridComposition + // useHybridComposition: true, + // allowsInlineMediaPlayback: true, + // ); + InAppWebViewGroupOptions webViewSettings = InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + useShouldOverrideUrlLoading: true, // 是否需要跳转 + mediaPlaybackRequiresUserGesture: false, // 设置为true,防止H5的音频自动播放 + transparentBackground: true, + ), + android: AndroidInAppWebViewOptions( + useHybridComposition: true, + mixedContentMode: AndroidMixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW), + ios: IOSInAppWebViewOptions( + allowsInlineMediaPlayback: true, + ), + ); - late WebViewController _controller; + Future getUrl() { + if (_inAppWebViewController == null) { + return Future.sync(() => null); + } + return _inAppWebViewController!.getUrl().then((uri) => uri.toString()); + } + + Future loadUrl(String url) { + if (_inAppWebViewController == null) { + return Future.sync(() => null); + } + return _inAppWebViewController! + .loadUrl(urlRequest: URLRequest(url: Uri.parse(url))); + } @override void initState() { @@ -28,44 +86,217 @@ class _WowWebViewPageState extends State { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); }*/ - _controller =WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setBackgroundColor(const Color(0x00000000)) - ..setNavigationDelegate( - NavigationDelegate( - onProgress: (int progress) { - // Update loading bar. - }, - onPageStarted: (String url) { - EasyLoading.show(); - }, - onPageFinished: (String url) { - EasyLoading.dismiss(); - }, - onWebResourceError: (WebResourceError error) { - EasyLoading.showError(error.description); - }, - onNavigationRequest: (NavigationRequest request) { - return NavigationDecision.navigate; - }, - ), - ) - ..loadRequest(Uri.parse(widget.urlStr)); + // _controller = WebViewController() + // ..setJavaScriptMode(JavaScriptMode.unrestricted) + // ..setBackgroundColor(const Color(0x00000000)) + // ..setNavigationDelegate( + // NavigationDelegate( + // onProgress: (int progress) { + // // Update loading bar. + // }, + // onPageStarted: (String url) { + // Log.d("WQF onPageStarted $url"); + // EasyLoading.show(); + // }, + // onPageFinished: (String url) { + // Log.d("WQF onPageFinished $url"); + // EasyLoading.dismiss(); + // }, + // onWebResourceError: (WebResourceError error) { + // Log.d("WQF onWebResourceError ${error.description}"); + // EasyLoading.showError(error.description); + // }, + // onUrlChange: (UrlChange change) { + // Log.d("WQF onUrlChange ${change.url}"); + // }, + // onNavigationRequest: (NavigationRequest request) async { + // var url = request.url; + // Log.d("WQF onNavigationRequest $url"); + // + // /// Allow the navigation + // // return NavigationDecision.navigate; + // /// Block the navigation + // /// return NavigationDecision.prevent; + // if (url.startsWith("alipay:") || url.startsWith("alipays")) { + // Log.d("WQF onNavigationRequest 支付宝 $url"); + // launch(request.url); + // return NavigationDecision.navigate; + // } + // if (url.startsWith('http:') || url.startsWith('https:')) { + // return NavigationDecision.navigate; + // } else { + // try { + // await launch(url); + // } catch (e) { + // print('Could not launch $request.url: $e'); + // } + // return NavigationDecision.prevent; + // } + // }, + // ), + // ) + // ..loadRequest(Uri.parse(widget.urlStr)); } @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - appBar: WEAppBar( - titleText: widget.webViewTitle, - ), - body: Container( - color: Colors.white, - child: SafeArea( - child: WebViewWidget(controller: _controller,), - ) - ), + return WillPopScope( + onWillPop: () { + Future canGoBack = _inAppWebViewController!.canGoBack(); + return canGoBack.then((isCanGoBack) { + if (isCanGoBack) { + _inAppWebViewController!.goBack(); + return false; + } else { + return true; + } + }); + }, + // return PopScope( + // canPop: () async { + // if (_controller != null) { + // bool canGoBack = await _controller!.canGoBack(); + // return !canGoBack; + // } + // return true; + // }, + // onPopInvoked: (PopDisposition disposition) async { + // if (_controller != null) { + // bool canGoBack = await _controller!.canGoBack(); + // if (canGoBack) { + // _controller!.goBack(); + // return PopDisposition.popCancelled; + // } + // } + // return PopDisposition.pop; + // }, + child: Scaffold( + backgroundColor: Colors.red, + appBar: WEAppBar( + titleText: widget.webViewTitle, + leadingWidth: 96, + leading: Row( + children: [ + IconButton( + icon: Image.asset( + 'back_around'.assetPng, + height: 40.h, + width: 40.w, + ), + onPressed: () { + if (isCanGoBack) { + _inAppWebViewController!.goBack(); + } else { + Navigator.pop(context); + } + }, + ), + Visibility( + visible: isCanGoBack, + child: IconButton( + // icon: Image.asset( + // 'back_around'.assetPng, + // height: 40.h, + // width: 40.w, + // ), + icon: Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + ], + ), + ), + body: Container( + color: Colors.white, + // child: SafeArea( + child: InAppWebView( + key: webViewKey, + initialUrlRequest: URLRequest( + url: Uri.parse(widget.urlStr), + ), + initialUserScripts: UnmodifiableListView([]), + initialOptions: webViewSettings, + onWebViewCreated: (controller) { + _inAppWebViewController = controller; + }, + onTitleChanged: + (InAppWebViewController controller, String? title) { + Log.d( + "WQF onTitleChanged title=$title`"); + setState(() { + if (title?.endsWith(".com") == true) { + widget.webViewTitle = defaultWebViewTitle; + } else { + widget.webViewTitle = title ?? defaultWebViewTitle; + } + }); + }, + shouldOverrideUrlLoading: (controller, navigationAction) async { + var url = navigationAction.request.url; + Log.d( + "WQF shouldOverrideUrlLoading url=$url scheme=${url?.scheme}"); + if (![ + "http", + "https", + "file", + "chrome", + "data", + "javascript", + "about", + ].contains(url?.scheme ?? "")) { + Log.d( + "WQF shouldOverrideUrlLoading contains scheme=${url?.scheme} uri=${url?.data?.uri} url=$url"); + Log.d("WQF canLaunchUrl(url)=${canLaunchUrl(url!)}"); + await canLaunchUrl(url) + ? await launchUrl(url) + : showToast("对不起,打不开链接地址:$url"); + return NavigationActionPolicy.CANCEL; + } + return NavigationActionPolicy.ALLOW; + }, + onLoadStop: (controller, url) async { + Log.d("WQF onLoadStop url=$url"); + //页面加载完毕,显示隐藏AppBar的返回键 + _inAppWebViewController!.canGoBack().then((canGoBack) => { + setState(() { + isCanGoBack = canGoBack; + }) + }); + _inAppWebViewController!.canGoForward().then((canForward) => { + setState(() { + isCanForward = canForward; + }) + }); + }, + onLoadError: (controller, request, code, message) { + Log.d( + "WQF onReceivedError request=$request error=$code message=$message"); + }, + onLoadHttpError: (controller, request, errorResponse, message) { + Log.d( + "WQF onReceivedError request=$request errorResponse=$errorResponse message=$message"); + }, + onProgressChanged: (controller, progress) { + Log.d("WQF onProgressChanged progress=$progress"); + //进度从0 ~ 100 + setState(() { + _progress = progress / 100.0; + }); + }, + onUpdateVisitedHistory: (controller, url, androidIsReload) { + Log.d("WQF onUpdateVisitedHistory url=$url"); + }, + // onLoadResourceWithCustomScheme: (controller, request) { + // Log.d("WQF onLoadResourceWithCustomScheme request=$request"); + // }, + onConsoleMessage: (controller, consoleMessage) { + // Log.d("WQF onConsoleMessage consoleMessage=$consoleMessage"); + }, + ), + )), + // ), ); } } diff --git a/lib/common/widgets/we_app_bar.dart b/lib/common/widgets/we_app_bar.dart index 95e9b76..abfa9b2 100644 --- a/lib/common/widgets/we_app_bar.dart +++ b/lib/common/widgets/we_app_bar.dart @@ -9,17 +9,18 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget { final Color? backgroundColor; final PreferredSizeWidget? bottom; final Widget? leading; + final double? leadingWidth; final List? actions; - const WEAppBar( - {this.titleText, - this.centerTitle = true, - this.onBack, - this.backgroundColor, - this.bottom, - this.leading, - this.actions, - super.key}); + const WEAppBar({this.titleText, + this.centerTitle = true, + this.onBack, + this.backgroundColor, + this.bottom, + this.leading, + this.leadingWidth, + this.actions, + super.key}); @override Widget build(BuildContext context) { @@ -33,6 +34,7 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget { fontWeight: FontWeight.w700, ), ), + leadingWidth: leadingWidth, leading: leading ?? GestureDetector( onTap: () { @@ -58,5 +60,7 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget { @override // TODO: implement preferredSize - Size get preferredSize => Size.fromHeight(kToolbarHeight + (bottom == null ? 0.0 : bottom!.preferredSize.height)); + Size get preferredSize => + Size.fromHeight(kToolbarHeight + + (bottom == null ? 0.0 : bottom!.preferredSize.height)); } diff --git a/lib/common/widgets/webview_dialog.dart b/lib/common/widgets/webview_dialog.dart index 0595634..e8faf9f 100644 --- a/lib/common/widgets/webview_dialog.dart +++ b/lib/common/widgets/webview_dialog.dart @@ -1,6 +1,6 @@ +import 'dart:collection'; import 'package:flutter/material.dart'; -import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:webview_flutter/webview_flutter.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; class WebviewDialog extends StatelessWidget { final String title; @@ -45,8 +45,8 @@ class WebviewDialog extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( - child: - const Text('同意并继续', style: TextStyle(color: Color(0xFFFBB621))), + child: const Text('同意并继续', + style: TextStyle(color: Color(0xFFFBB621))), onPressed: () { // 处理接受按钮的点击事件 leftTap(); // 关闭对话框 @@ -68,30 +68,51 @@ class WebviewDialog extends StatelessWidget { Future buildWebViewController(String url) async { Widget res; try { - WebViewController controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setBackgroundColor(const Color(0x00000000)) - ..setNavigationDelegate( - NavigationDelegate( - onProgress: (int progress) { - // Update loading bar. - }, - onPageStarted: (String url) { - EasyLoading.show(); - }, - onPageFinished: (String url) { - EasyLoading.dismiss(); - }, - onWebResourceError: (WebResourceError error) { - EasyLoading.showError(error.description); - }, - onNavigationRequest: (NavigationRequest request) { - return NavigationDecision.navigate; - }, + // WebViewController controller = WebViewController() + // ..setJavaScriptMode(JavaScriptMode.unrestricted) + // ..setBackgroundColor(const Color(0x00000000)) + // ..setNavigationDelegate( + // NavigationDelegate( + // onProgress: (int progress) { + // // Update loading bar. + // }, + // onPageStarted: (String url) { + // EasyLoading.show(); + // }, + // onPageFinished: (String url) { + // EasyLoading.dismiss(); + // }, + // onWebResourceError: (WebResourceError error) { + // EasyLoading.showError(error.description); + // }, + // onNavigationRequest: (NavigationRequest request) { + // return NavigationDecision.navigate; + // }, + // ), + // ); + // await controller.loadRequest(Uri.parse(url)); + // res = WebViewWidget(controller: controller); + + res = InAppWebView( + initialUrlRequest: URLRequest(url: Uri.parse(url)), + initialUserScripts: UnmodifiableListView([]), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + useShouldOverrideUrlLoading: true, // 是否需要跳转 + mediaPlaybackRequiresUserGesture: false, // 设置为true,方式H5的音频自动播放 + transparentBackground: true + ), + android: AndroidInAppWebViewOptions( + useHybridComposition: true, + ), + ios: IOSInAppWebViewOptions( + allowsInlineMediaPlayback: true, + ), ), - ); - await controller.loadRequest(Uri.parse(url)); - res = WebViewWidget(controller: controller); + onWebViewCreated: (controller) { + // _inAppWebViewController = controller; + }, + ); } catch (error) { res = Text("加载失败:${error.toString()}"); debugPrint("WebViewController加载失败:${error.toString()}"); diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index dee90db..c56e788 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -1,7 +1,11 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_app_update/azhon_app_update.dart'; import 'package:flutter_app_update/update_model.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:limiting_direction_csx/limiting_direction_csx.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:wow_english/common/core/app_config_helper.dart'; import 'package:wow_english/common/core/app_consts.dart'; @@ -112,18 +116,28 @@ class _HomePageView extends StatelessWidget { builder: (context, userState) { return GestureDetector( onTap: () { - _checkPermission(() async { - await AudioPlayerUtil.getInstance().pause(); - Navigator.of(context).pushNamed( - AppRouteName.webView, - arguments: { - 'urlStr': AppConsts.xiaoeShopUrl, - 'webViewTitle': 'Wow精选' - }).then((value) async => { - await AudioPlayerUtil.getInstance().playAudio( - AudioPlayerUtilType.touch), - }); - }, bloc); + _checkPermission(() async { + await AudioPlayerUtil.getInstance().pause(); + if (Platform.isIOS) { + await LimitingDirectionCsx.setScreenDirection(DeviceDirectionMask.Portrait); + } else { + await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + } + Navigator.of(context).pushNamed( + AppRouteName.webView, + arguments: { + 'urlStr': AppConsts.xiaoeShopUrl, + 'webViewTitle': 'Wow精选' + }).then((value) async => { + if (Platform.isIOS) { + await LimitingDirectionCsx.setScreenDirection(DeviceDirectionMask.Landscape), + } else { + await SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]), + }, + await AudioPlayerUtil.getInstance().playAudio( + AudioPlayerUtilType.touch), + }); + }, bloc); }, child: Offstage( offstage: AppConfigHelper.shouldHidePay() || diff --git a/pubspec.yaml b/pubspec.yaml index 316d642..777a4f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,7 +49,9 @@ dependencies: #Url跳转 https://pub.dev/packages/url_launcher url_launcher: ^6.1.11 #网页加载 https://pub.dev/packages/webview_flutter - webview_flutter: ^4.2.2 +# webview_flutter: ^4.8.0 + #https://pub.dev/packages/flutter_inappwebview + flutter_inappwebview: 5.8.0 #下拉刷新 https://pub.dev/packages/pull_to_refresh pull_to_refresh: ^2.0.0 # 数据持久化 https://pub.dev/packages/shared_preferences -- libgit2 0.22.2