Commit 1e868ad23eb6c25d011b992ccb18a7edec17ed44

Authored by 吴启风
1 parent 2d6bce99

feat:webview库替换

lib/common/pages/wow_web_page.dart
  1 +import 'dart:collection';
  2 +
1 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
2 -import 'package:flutter_easyloading/flutter_easyloading.dart';  
3 -import 'package:webview_flutter/webview_flutter.dart'; 4 +import 'package:flutter_inappwebview/flutter_inappwebview.dart';
  5 +import 'package:flutter_screenutil/flutter_screenutil.dart';
  6 +import 'package:url_launcher/url_launcher.dart';
  7 +import 'package:wow_english/common/extension/string_extension.dart';
4 import 'package:wow_english/common/widgets/we_app_bar.dart'; 8 import 'package:wow_english/common/widgets/we_app_bar.dart';
5 9
  10 +import '../../utils/log_util.dart';
  11 +import '../../utils/toast_util.dart';
  12 +
6 class WowWebViewPage extends StatefulWidget { 13 class WowWebViewPage extends StatefulWidget {
7 - const WowWebViewPage({super.key, required this.urlStr, required this.webViewTitle}); 14 + WowWebViewPage(
  15 + {super.key, required this.urlStr, required this.webViewTitle});
8 16
9 final String urlStr; 17 final String urlStr;
10 - final String webViewTitle; 18 + String webViewTitle;
11 19
12 @override 20 @override
13 State<StatefulWidget> createState() { 21 State<StatefulWidget> createState() {
@@ -15,9 +23,59 @@ class WowWebViewPage extends StatefulWidget { @@ -15,9 +23,59 @@ class WowWebViewPage extends StatefulWidget {
15 } 23 }
16 } 24 }
17 25
  26 +/// 生成进度条组件,进度从0 ~ 1
  27 +_createProgressBar(double progress, BuildContext context) {
  28 + return LinearProgressIndicator(
  29 + backgroundColor: Colors.white70.withOpacity(0),
  30 + value: progress == 1.0 ? 0 : progress,
  31 + valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue),
  32 + );
  33 +}
  34 +
18 class _WowWebViewPageState extends State<WowWebViewPage> { 35 class _WowWebViewPageState extends State<WowWebViewPage> {
  36 + late InAppWebViewController? _inAppWebViewController;
  37 + final GlobalKey webViewKey = GlobalKey();
  38 + double _progress = 0;
  39 + bool isCanGoBack = false;
  40 + bool isCanForward = false;
  41 + late final String defaultWebViewTitle = widget.webViewTitle;
  42 +
  43 + // InAppWebViewSettings webViewSettings = InAppWebViewSettings(
  44 + // useShouldOverrideUrlLoading: true,
  45 + // mediaPlaybackRequiresUserGesture: true,
  46 + //
  47 + // /// android 支持HybridComposition
  48 + // useHybridComposition: true,
  49 + // allowsInlineMediaPlayback: true,
  50 + // );
  51 + InAppWebViewGroupOptions webViewSettings = InAppWebViewGroupOptions(
  52 + crossPlatform: InAppWebViewOptions(
  53 + useShouldOverrideUrlLoading: true, // 是否需要跳转
  54 + mediaPlaybackRequiresUserGesture: false, // 设置为true,防止H5的音频自动播放
  55 + transparentBackground: true,
  56 + ),
  57 + android: AndroidInAppWebViewOptions(
  58 + useHybridComposition: true,
  59 + mixedContentMode: AndroidMixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW),
  60 + ios: IOSInAppWebViewOptions(
  61 + allowsInlineMediaPlayback: true,
  62 + ),
  63 + );
19 64
20 - late WebViewController _controller; 65 + Future<String?> getUrl() {
  66 + if (_inAppWebViewController == null) {
  67 + return Future.sync(() => null);
  68 + }
  69 + return _inAppWebViewController!.getUrl().then((uri) => uri.toString());
  70 + }
  71 +
  72 + Future<void> loadUrl(String url) {
  73 + if (_inAppWebViewController == null) {
  74 + return Future.sync(() => null);
  75 + }
  76 + return _inAppWebViewController!
  77 + .loadUrl(urlRequest: URLRequest(url: Uri.parse(url)));
  78 + }
21 79
22 @override 80 @override
23 void initState() { 81 void initState() {
@@ -28,44 +86,217 @@ class _WowWebViewPageState extends State&lt;WowWebViewPage&gt; { @@ -28,44 +86,217 @@ class _WowWebViewPageState extends State&lt;WowWebViewPage&gt; {
28 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); 86 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
29 }*/ 87 }*/
30 88
31 - _controller =WebViewController()  
32 - ..setJavaScriptMode(JavaScriptMode.unrestricted)  
33 - ..setBackgroundColor(const Color(0x00000000))  
34 - ..setNavigationDelegate(  
35 - NavigationDelegate(  
36 - onProgress: (int progress) {  
37 - // Update loading bar.  
38 - },  
39 - onPageStarted: (String url) {  
40 - EasyLoading.show();  
41 - },  
42 - onPageFinished: (String url) {  
43 - EasyLoading.dismiss();  
44 - },  
45 - onWebResourceError: (WebResourceError error) {  
46 - EasyLoading.showError(error.description);  
47 - },  
48 - onNavigationRequest: (NavigationRequest request) {  
49 - return NavigationDecision.navigate;  
50 - },  
51 - ),  
52 - )  
53 - ..loadRequest(Uri.parse(widget.urlStr)); 89 + // _controller = WebViewController()
  90 + // ..setJavaScriptMode(JavaScriptMode.unrestricted)
  91 + // ..setBackgroundColor(const Color(0x00000000))
  92 + // ..setNavigationDelegate(
  93 + // NavigationDelegate(
  94 + // onProgress: (int progress) {
  95 + // // Update loading bar.
  96 + // },
  97 + // onPageStarted: (String url) {
  98 + // Log.d("WQF onPageStarted $url");
  99 + // EasyLoading.show();
  100 + // },
  101 + // onPageFinished: (String url) {
  102 + // Log.d("WQF onPageFinished $url");
  103 + // EasyLoading.dismiss();
  104 + // },
  105 + // onWebResourceError: (WebResourceError error) {
  106 + // Log.d("WQF onWebResourceError ${error.description}");
  107 + // EasyLoading.showError(error.description);
  108 + // },
  109 + // onUrlChange: (UrlChange change) {
  110 + // Log.d("WQF onUrlChange ${change.url}");
  111 + // },
  112 + // onNavigationRequest: (NavigationRequest request) async {
  113 + // var url = request.url;
  114 + // Log.d("WQF onNavigationRequest $url");
  115 + //
  116 + // /// Allow the navigation
  117 + // // return NavigationDecision.navigate;
  118 + // /// Block the navigation
  119 + // /// return NavigationDecision.prevent;
  120 + // if (url.startsWith("alipay:") || url.startsWith("alipays")) {
  121 + // Log.d("WQF onNavigationRequest 支付宝 $url");
  122 + // launch(request.url);
  123 + // return NavigationDecision.navigate;
  124 + // }
  125 + // if (url.startsWith('http:') || url.startsWith('https:')) {
  126 + // return NavigationDecision.navigate;
  127 + // } else {
  128 + // try {
  129 + // await launch(url);
  130 + // } catch (e) {
  131 + // print('Could not launch $request.url: $e');
  132 + // }
  133 + // return NavigationDecision.prevent;
  134 + // }
  135 + // },
  136 + // ),
  137 + // )
  138 + // ..loadRequest(Uri.parse(widget.urlStr));
54 } 139 }
55 140
56 @override 141 @override
57 Widget build(BuildContext context) { 142 Widget build(BuildContext context) {
58 - return Scaffold(  
59 - backgroundColor: Colors.white,  
60 - appBar: WEAppBar(  
61 - titleText: widget.webViewTitle,  
62 - ),  
63 - body: Container(  
64 - color: Colors.white,  
65 - child: SafeArea(  
66 - child: WebViewWidget(controller: _controller,),  
67 - )  
68 - ), 143 + return WillPopScope(
  144 + onWillPop: () {
  145 + Future<bool> canGoBack = _inAppWebViewController!.canGoBack();
  146 + return canGoBack.then((isCanGoBack) {
  147 + if (isCanGoBack) {
  148 + _inAppWebViewController!.goBack();
  149 + return false;
  150 + } else {
  151 + return true;
  152 + }
  153 + });
  154 + },
  155 + // return PopScope(
  156 + // canPop: () async {
  157 + // if (_controller != null) {
  158 + // bool canGoBack = await _controller!.canGoBack();
  159 + // return !canGoBack;
  160 + // }
  161 + // return true;
  162 + // },
  163 + // onPopInvoked: (PopDisposition disposition) async {
  164 + // if (_controller != null) {
  165 + // bool canGoBack = await _controller!.canGoBack();
  166 + // if (canGoBack) {
  167 + // _controller!.goBack();
  168 + // return PopDisposition.popCancelled;
  169 + // }
  170 + // }
  171 + // return PopDisposition.pop;
  172 + // },
  173 + child: Scaffold(
  174 + backgroundColor: Colors.red,
  175 + appBar: WEAppBar(
  176 + titleText: widget.webViewTitle,
  177 + leadingWidth: 96,
  178 + leading: Row(
  179 + children: <Widget>[
  180 + IconButton(
  181 + icon: Image.asset(
  182 + 'back_around'.assetPng,
  183 + height: 40.h,
  184 + width: 40.w,
  185 + ),
  186 + onPressed: () {
  187 + if (isCanGoBack) {
  188 + _inAppWebViewController!.goBack();
  189 + } else {
  190 + Navigator.pop(context);
  191 + }
  192 + },
  193 + ),
  194 + Visibility(
  195 + visible: isCanGoBack,
  196 + child: IconButton(
  197 + // icon: Image.asset(
  198 + // 'back_around'.assetPng,
  199 + // height: 40.h,
  200 + // width: 40.w,
  201 + // ),
  202 + icon: Icon(Icons.close),
  203 + onPressed: () {
  204 + Navigator.pop(context);
  205 + },
  206 + ),
  207 + ),
  208 + ],
  209 + ),
  210 + ),
  211 + body: Container(
  212 + color: Colors.white,
  213 + // child: SafeArea(
  214 + child: InAppWebView(
  215 + key: webViewKey,
  216 + initialUrlRequest: URLRequest(
  217 + url: Uri.parse(widget.urlStr),
  218 + ),
  219 + initialUserScripts: UnmodifiableListView<UserScript>([]),
  220 + initialOptions: webViewSettings,
  221 + onWebViewCreated: (controller) {
  222 + _inAppWebViewController = controller;
  223 + },
  224 + onTitleChanged:
  225 + (InAppWebViewController controller, String? title) {
  226 + Log.d(
  227 + "WQF onTitleChanged title=$title`");
  228 + setState(() {
  229 + if (title?.endsWith(".com") == true) {
  230 + widget.webViewTitle = defaultWebViewTitle;
  231 + } else {
  232 + widget.webViewTitle = title ?? defaultWebViewTitle;
  233 + }
  234 + });
  235 + },
  236 + shouldOverrideUrlLoading: (controller, navigationAction) async {
  237 + var url = navigationAction.request.url;
  238 + Log.d(
  239 + "WQF shouldOverrideUrlLoading url=$url scheme=${url?.scheme}");
  240 + if (![
  241 + "http",
  242 + "https",
  243 + "file",
  244 + "chrome",
  245 + "data",
  246 + "javascript",
  247 + "about",
  248 + ].contains(url?.scheme ?? "")) {
  249 + Log.d(
  250 + "WQF shouldOverrideUrlLoading contains scheme=${url?.scheme} uri=${url?.data?.uri} url=$url");
  251 + Log.d("WQF canLaunchUrl(url)=${canLaunchUrl(url!)}");
  252 + await canLaunchUrl(url)
  253 + ? await launchUrl(url)
  254 + : showToast("对不起,打不开链接地址:$url");
  255 + return NavigationActionPolicy.CANCEL;
  256 + }
  257 + return NavigationActionPolicy.ALLOW;
  258 + },
  259 + onLoadStop: (controller, url) async {
  260 + Log.d("WQF onLoadStop url=$url");
  261 + //页面加载完毕,显示隐藏AppBar的返回键
  262 + _inAppWebViewController!.canGoBack().then((canGoBack) => {
  263 + setState(() {
  264 + isCanGoBack = canGoBack;
  265 + })
  266 + });
  267 + _inAppWebViewController!.canGoForward().then((canForward) => {
  268 + setState(() {
  269 + isCanForward = canForward;
  270 + })
  271 + });
  272 + },
  273 + onLoadError: (controller, request, code, message) {
  274 + Log.d(
  275 + "WQF onReceivedError request=$request error=$code message=$message");
  276 + },
  277 + onLoadHttpError: (controller, request, errorResponse, message) {
  278 + Log.d(
  279 + "WQF onReceivedError request=$request errorResponse=$errorResponse message=$message");
  280 + },
  281 + onProgressChanged: (controller, progress) {
  282 + Log.d("WQF onProgressChanged progress=$progress");
  283 + //进度从0 ~ 100
  284 + setState(() {
  285 + _progress = progress / 100.0;
  286 + });
  287 + },
  288 + onUpdateVisitedHistory: (controller, url, androidIsReload) {
  289 + Log.d("WQF onUpdateVisitedHistory url=$url");
  290 + },
  291 + // onLoadResourceWithCustomScheme: (controller, request) {
  292 + // Log.d("WQF onLoadResourceWithCustomScheme request=$request");
  293 + // },
  294 + onConsoleMessage: (controller, consoleMessage) {
  295 + // Log.d("WQF onConsoleMessage consoleMessage=$consoleMessage");
  296 + },
  297 + ),
  298 + )),
  299 + // ),
69 ); 300 );
70 } 301 }
71 } 302 }
lib/common/widgets/we_app_bar.dart
@@ -9,17 +9,18 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -9,17 +9,18 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget {
9 final Color? backgroundColor; 9 final Color? backgroundColor;
10 final PreferredSizeWidget? bottom; 10 final PreferredSizeWidget? bottom;
11 final Widget? leading; 11 final Widget? leading;
  12 + final double? leadingWidth;
12 final List<Widget>? actions; 13 final List<Widget>? actions;
13 14
14 - const WEAppBar(  
15 - {this.titleText,  
16 - this.centerTitle = true,  
17 - this.onBack,  
18 - this.backgroundColor,  
19 - this.bottom,  
20 - this.leading,  
21 - this.actions,  
22 - super.key}); 15 + const WEAppBar({this.titleText,
  16 + this.centerTitle = true,
  17 + this.onBack,
  18 + this.backgroundColor,
  19 + this.bottom,
  20 + this.leading,
  21 + this.leadingWidth,
  22 + this.actions,
  23 + super.key});
23 24
24 @override 25 @override
25 Widget build(BuildContext context) { 26 Widget build(BuildContext context) {
@@ -33,6 +34,7 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -33,6 +34,7 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget {
33 fontWeight: FontWeight.w700, 34 fontWeight: FontWeight.w700,
34 ), 35 ),
35 ), 36 ),
  37 + leadingWidth: leadingWidth,
36 leading: leading ?? 38 leading: leading ??
37 GestureDetector( 39 GestureDetector(
38 onTap: () { 40 onTap: () {
@@ -58,5 +60,7 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -58,5 +60,7 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget {
58 60
59 @override 61 @override
60 // TODO: implement preferredSize 62 // TODO: implement preferredSize
61 - Size get preferredSize => Size.fromHeight(kToolbarHeight + (bottom == null ? 0.0 : bottom!.preferredSize.height)); 63 + Size get preferredSize =>
  64 + Size.fromHeight(kToolbarHeight +
  65 + (bottom == null ? 0.0 : bottom!.preferredSize.height));
62 } 66 }
lib/common/widgets/webview_dialog.dart
  1 +import 'dart:collection';
1 import 'package:flutter/material.dart'; 2 import 'package:flutter/material.dart';
2 -import 'package:flutter_easyloading/flutter_easyloading.dart';  
3 -import 'package:webview_flutter/webview_flutter.dart'; 3 +import 'package:flutter_inappwebview/flutter_inappwebview.dart';
4 4
5 class WebviewDialog extends StatelessWidget { 5 class WebviewDialog extends StatelessWidget {
6 final String title; 6 final String title;
@@ -45,8 +45,8 @@ class WebviewDialog extends StatelessWidget { @@ -45,8 +45,8 @@ class WebviewDialog extends StatelessWidget {
45 mainAxisAlignment: MainAxisAlignment.spaceBetween, 45 mainAxisAlignment: MainAxisAlignment.spaceBetween,
46 children: [ 46 children: [
47 TextButton( 47 TextButton(
48 - child:  
49 - const Text('同意并继续', style: TextStyle(color: Color(0xFFFBB621))), 48 + child: const Text('同意并继续',
  49 + style: TextStyle(color: Color(0xFFFBB621))),
50 onPressed: () { 50 onPressed: () {
51 // 处理接受按钮的点击事件 51 // 处理接受按钮的点击事件
52 leftTap(); // 关闭对话框 52 leftTap(); // 关闭对话框
@@ -68,30 +68,51 @@ class WebviewDialog extends StatelessWidget { @@ -68,30 +68,51 @@ class WebviewDialog extends StatelessWidget {
68 Future<Widget> buildWebViewController(String url) async { 68 Future<Widget> buildWebViewController(String url) async {
69 Widget res; 69 Widget res;
70 try { 70 try {
71 - WebViewController controller = WebViewController()  
72 - ..setJavaScriptMode(JavaScriptMode.unrestricted)  
73 - ..setBackgroundColor(const Color(0x00000000))  
74 - ..setNavigationDelegate(  
75 - NavigationDelegate(  
76 - onProgress: (int progress) {  
77 - // Update loading bar.  
78 - },  
79 - onPageStarted: (String url) {  
80 - EasyLoading.show();  
81 - },  
82 - onPageFinished: (String url) {  
83 - EasyLoading.dismiss();  
84 - },  
85 - onWebResourceError: (WebResourceError error) {  
86 - EasyLoading.showError(error.description);  
87 - },  
88 - onNavigationRequest: (NavigationRequest request) {  
89 - return NavigationDecision.navigate;  
90 - }, 71 + // WebViewController controller = WebViewController()
  72 + // ..setJavaScriptMode(JavaScriptMode.unrestricted)
  73 + // ..setBackgroundColor(const Color(0x00000000))
  74 + // ..setNavigationDelegate(
  75 + // NavigationDelegate(
  76 + // onProgress: (int progress) {
  77 + // // Update loading bar.
  78 + // },
  79 + // onPageStarted: (String url) {
  80 + // EasyLoading.show();
  81 + // },
  82 + // onPageFinished: (String url) {
  83 + // EasyLoading.dismiss();
  84 + // },
  85 + // onWebResourceError: (WebResourceError error) {
  86 + // EasyLoading.showError(error.description);
  87 + // },
  88 + // onNavigationRequest: (NavigationRequest request) {
  89 + // return NavigationDecision.navigate;
  90 + // },
  91 + // ),
  92 + // );
  93 + // await controller.loadRequest(Uri.parse(url));
  94 + // res = WebViewWidget(controller: controller);
  95 +
  96 + res = InAppWebView(
  97 + initialUrlRequest: URLRequest(url: Uri.parse(url)),
  98 + initialUserScripts: UnmodifiableListView<UserScript>([]),
  99 + initialOptions: InAppWebViewGroupOptions(
  100 + crossPlatform: InAppWebViewOptions(
  101 + useShouldOverrideUrlLoading: true, // 是否需要跳转
  102 + mediaPlaybackRequiresUserGesture: false, // 设置为true,方式H5的音频自动播放
  103 + transparentBackground: true
  104 + ),
  105 + android: AndroidInAppWebViewOptions(
  106 + useHybridComposition: true,
  107 + ),
  108 + ios: IOSInAppWebViewOptions(
  109 + allowsInlineMediaPlayback: true,
  110 + ),
91 ), 111 ),
92 - );  
93 - await controller.loadRequest(Uri.parse(url));  
94 - res = WebViewWidget(controller: controller); 112 + onWebViewCreated: (controller) {
  113 + // _inAppWebViewController = controller;
  114 + },
  115 + );
95 } catch (error) { 116 } catch (error) {
96 res = Text("加载失败:${error.toString()}"); 117 res = Text("加载失败:${error.toString()}");
97 debugPrint("WebViewController加载失败:${error.toString()}"); 118 debugPrint("WebViewController加载失败:${error.toString()}");
lib/pages/home/view.dart
  1 +import 'dart:io';
  2 +
1 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
  4 +import 'package:flutter/services.dart';
2 import 'package:flutter_app_update/azhon_app_update.dart'; 5 import 'package:flutter_app_update/azhon_app_update.dart';
3 import 'package:flutter_app_update/update_model.dart'; 6 import 'package:flutter_app_update/update_model.dart';
4 import 'package:flutter_bloc/flutter_bloc.dart'; 7 import 'package:flutter_bloc/flutter_bloc.dart';
  8 +import 'package:limiting_direction_csx/limiting_direction_csx.dart';
5 import 'package:url_launcher/url_launcher.dart'; 9 import 'package:url_launcher/url_launcher.dart';
6 import 'package:wow_english/common/core/app_config_helper.dart'; 10 import 'package:wow_english/common/core/app_config_helper.dart';
7 import 'package:wow_english/common/core/app_consts.dart'; 11 import 'package:wow_english/common/core/app_consts.dart';
@@ -112,18 +116,28 @@ class _HomePageView extends StatelessWidget { @@ -112,18 +116,28 @@ class _HomePageView extends StatelessWidget {
112 builder: (context, userState) { 116 builder: (context, userState) {
113 return GestureDetector( 117 return GestureDetector(
114 onTap: () { 118 onTap: () {
115 - _checkPermission(() async {  
116 - await AudioPlayerUtil.getInstance().pause();  
117 - Navigator.of(context).pushNamed(  
118 - AppRouteName.webView,  
119 - arguments: {  
120 - 'urlStr': AppConsts.xiaoeShopUrl,  
121 - 'webViewTitle': 'Wow精选'  
122 - }).then((value) async => {  
123 - await AudioPlayerUtil.getInstance().playAudio(  
124 - AudioPlayerUtilType.touch),  
125 - });  
126 - }, bloc); 119 + _checkPermission(() async {
  120 + await AudioPlayerUtil.getInstance().pause();
  121 + if (Platform.isIOS) {
  122 + await LimitingDirectionCsx.setScreenDirection(DeviceDirectionMask.Portrait);
  123 + } else {
  124 + await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
  125 + }
  126 + Navigator.of(context).pushNamed(
  127 + AppRouteName.webView,
  128 + arguments: {
  129 + 'urlStr': AppConsts.xiaoeShopUrl,
  130 + 'webViewTitle': 'Wow精选'
  131 + }).then((value) async => {
  132 + if (Platform.isIOS) {
  133 + await LimitingDirectionCsx.setScreenDirection(DeviceDirectionMask.Landscape),
  134 + } else {
  135 + await SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]),
  136 + },
  137 + await AudioPlayerUtil.getInstance().playAudio(
  138 + AudioPlayerUtilType.touch),
  139 + });
  140 + }, bloc);
127 }, 141 },
128 child: Offstage( 142 child: Offstage(
129 offstage: AppConfigHelper.shouldHidePay() || 143 offstage: AppConfigHelper.shouldHidePay() ||
pubspec.yaml
@@ -49,7 +49,9 @@ dependencies: @@ -49,7 +49,9 @@ dependencies:
49 #Url跳转 https://pub.dev/packages/url_launcher 49 #Url跳转 https://pub.dev/packages/url_launcher
50 url_launcher: ^6.1.11 50 url_launcher: ^6.1.11
51 #网页加载 https://pub.dev/packages/webview_flutter 51 #网页加载 https://pub.dev/packages/webview_flutter
52 - webview_flutter: ^4.2.2 52 +# webview_flutter: ^4.8.0
  53 + #https://pub.dev/packages/flutter_inappwebview
  54 + flutter_inappwebview: 5.8.0
53 #下拉刷新 https://pub.dev/packages/pull_to_refresh 55 #下拉刷新 https://pub.dev/packages/pull_to_refresh
54 pull_to_refresh: ^2.0.0 56 pull_to_refresh: ^2.0.0
55 # 数据持久化 https://pub.dev/packages/shared_preferences 57 # 数据持久化 https://pub.dev/packages/shared_preferences