Commit 4b858f67533b5f1a7472cc27c4861f37b4630e4d

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 +
6 class WowWebViewPage extends StatefulWidget { 12 class WowWebViewPage extends StatefulWidget {
7 - const WowWebViewPage({super.key, required this.urlStr, required this.webViewTitle}); 13 + WowWebViewPage({super.key, required this.urlStr, required this.webViewTitle});
8 14
9 final String urlStr; 15 final String urlStr;
10 - final String webViewTitle; 16 + String webViewTitle;
11 17
12 @override 18 @override
13 State<StatefulWidget> createState() { 19 State<StatefulWidget> createState() {
@@ -15,9 +21,60 @@ class WowWebViewPage extends StatefulWidget { @@ -15,9 +21,60 @@ class WowWebViewPage extends StatefulWidget {
15 } 21 }
16 } 22 }
17 23
  24 +/// 生成进度条组件,进度从0 ~ 1
  25 +_createProgressBar(double progress, BuildContext context) {
  26 + return LinearProgressIndicator(
  27 + backgroundColor: Colors.white70.withOpacity(0),
  28 + value: progress == 1.0 ? 0 : progress,
  29 + valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue),
  30 + );
  31 +}
  32 +
18 class _WowWebViewPageState extends State<WowWebViewPage> { 33 class _WowWebViewPageState extends State<WowWebViewPage> {
  34 + late InAppWebViewController? _inAppWebViewController;
  35 + final GlobalKey webViewKey = GlobalKey();
  36 + double _progress = 0;
  37 + bool isCanGoBack = false;
  38 + bool isCanForward = false;
  39 + late final String defaultWebViewTitle = widget.webViewTitle;
  40 + final String TAG = "WowWebViewPage";
19 41
20 - late WebViewController _controller; 42 + // InAppWebViewSettings webViewSettings = InAppWebViewSettings(
  43 + // useShouldOverrideUrlLoading: true,
  44 + // mediaPlaybackRequiresUserGesture: true,
  45 + //
  46 + // /// android 支持HybridComposition
  47 + // useHybridComposition: true,
  48 + // allowsInlineMediaPlayback: true,
  49 + // );
  50 + InAppWebViewGroupOptions webViewSettings = InAppWebViewGroupOptions(
  51 + crossPlatform: InAppWebViewOptions(
  52 + useShouldOverrideUrlLoading: true, // 是否需要跳转
  53 + mediaPlaybackRequiresUserGesture: false, // 设置为true,防止H5的音频自动播放
  54 + transparentBackground: true,
  55 + ),
  56 + android: AndroidInAppWebViewOptions(
  57 + useHybridComposition: true,
  58 + mixedContentMode: AndroidMixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW),
  59 + ios: IOSInAppWebViewOptions(
  60 + allowsInlineMediaPlayback: true,
  61 + ),
  62 + );
  63 +
  64 + Future<String?> getUrl() {
  65 + if (_inAppWebViewController == null) {
  66 + return Future.sync(() => null);
  67 + }
  68 + return _inAppWebViewController!.getUrl().then((uri) => uri.toString());
  69 + }
  70 +
  71 + Future<void> loadUrl(String url) {
  72 + if (_inAppWebViewController == null) {
  73 + return Future.sync(() => null);
  74 + }
  75 + return _inAppWebViewController!
  76 + .loadUrl(urlRequest: URLRequest(url: Uri.parse(url)));
  77 + }
21 78
22 @override 79 @override
23 void initState() { 80 void initState() {
@@ -28,44 +85,214 @@ class _WowWebViewPageState extends State&lt;WowWebViewPage&gt; { @@ -28,44 +85,214 @@ class _WowWebViewPageState extends State&lt;WowWebViewPage&gt; {
28 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); 85 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
29 }*/ 86 }*/
30 87
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)); 88 + // _controller = WebViewController()
  89 + // ..setJavaScriptMode(JavaScriptMode.unrestricted)
  90 + // ..setBackgroundColor(const Color(0x00000000))
  91 + // ..setNavigationDelegate(
  92 + // NavigationDelegate(
  93 + // onProgress: (int progress) {
  94 + // // Update loading bar.
  95 + // },
  96 + // onPageStarted: (String url) {
  97 + // Log.d("$TAG onPageStarted $url");
  98 + // EasyLoading.show();
  99 + // },
  100 + // onPageFinished: (String url) {
  101 + // Log.d("$TAG onPageFinished $url");
  102 + // EasyLoading.dismiss();
  103 + // },
  104 + // onWebResourceError: (WebResourceError error) {
  105 + // Log.d("$TAG onWebResourceError ${error.description}");
  106 + // EasyLoading.showError(error.description);
  107 + // },
  108 + // onUrlChange: (UrlChange change) {
  109 + // Log.d("$TAG onUrlChange ${change.url}");
  110 + // },
  111 + // onNavigationRequest: (NavigationRequest request) async {
  112 + // var url = request.url;
  113 + // Log.d("$TAG onNavigationRequest $url");
  114 + //
  115 + // /// Allow the navigation
  116 + // // return NavigationDecision.navigate;
  117 + // /// Block the navigation
  118 + // /// return NavigationDecision.prevent;
  119 + // if (url.startsWith("alipay:") || url.startsWith("alipays")) {
  120 + // Log.d("$TAG onNavigationRequest 支付宝 $url");
  121 + // launch(request.url);
  122 + // return NavigationDecision.navigate;
  123 + // }
  124 + // if (url.startsWith('http:') || url.startsWith('https:')) {
  125 + // return NavigationDecision.navigate;
  126 + // } else {
  127 + // try {
  128 + // await launch(url);
  129 + // } catch (e) {
  130 + // print('Could not launch $request.url: $e');
  131 + // }
  132 + // return NavigationDecision.prevent;
  133 + // }
  134 + // },
  135 + // ),
  136 + // )
  137 + // ..loadRequest(Uri.parse(widget.urlStr));
54 } 138 }
55 139
56 @override 140 @override
57 Widget build(BuildContext context) { 141 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 - ), 142 + return WillPopScope(
  143 + onWillPop: () {
  144 + Future<bool> canGoBack = _inAppWebViewController!.canGoBack();
  145 + return canGoBack.then((isCanGoBack) {
  146 + if (isCanGoBack) {
  147 + _inAppWebViewController!.goBack();
  148 + return false;
  149 + } else {
  150 + return true;
  151 + }
  152 + });
  153 + },
  154 + // return PopScope(
  155 + // canPop: () async {
  156 + // if (_controller != null) {
  157 + // bool canGoBack = await _controller!.canGoBack();
  158 + // return !canGoBack;
  159 + // }
  160 + // return true;
  161 + // },
  162 + // onPopInvoked: (PopDisposition disposition) async {
  163 + // if (_controller != null) {
  164 + // bool canGoBack = await _controller!.canGoBack();
  165 + // if (canGoBack) {
  166 + // _controller!.goBack();
  167 + // return PopDisposition.popCancelled;
  168 + // }
  169 + // }
  170 + // return PopDisposition.pop;
  171 + // },
  172 + child: Scaffold(
  173 + backgroundColor: Colors.red,
  174 + appBar: WEAppBar(
  175 + titleText: widget.webViewTitle,
  176 + leadingWidth: 96,
  177 + leading: Row(
  178 + children: <Widget>[
  179 + IconButton(
  180 + icon: Image.asset(
  181 + 'back_around'.assetPng,
  182 + height: 40.h,
  183 + width: 40.w,
  184 + ),
  185 + onPressed: () {
  186 + if (isCanGoBack) {
  187 + _inAppWebViewController!.goBack();
  188 + } else {
  189 + Navigator.pop(context);
  190 + }
  191 + },
  192 + ),
  193 + Visibility(
  194 + visible: isCanGoBack,
  195 + child: IconButton(
  196 + // icon: Image.asset(
  197 + // 'back_around'.assetPng,
  198 + // height: 40.h,
  199 + // width: 40.w,
  200 + // ),
  201 + icon: Icon(Icons.close),
  202 + onPressed: () {
  203 + Navigator.pop(context);
  204 + },
  205 + ),
  206 + ),
  207 + ],
  208 + ),
  209 + ),
  210 + body: Container(
  211 + color: Colors.white,
  212 + // child: SafeArea(
  213 + child: InAppWebView(
  214 + key: webViewKey,
  215 + initialUrlRequest: URLRequest(
  216 + url: Uri.parse(widget.urlStr),
  217 + ),
  218 + initialUserScripts: UnmodifiableListView<UserScript>([]),
  219 + initialOptions: webViewSettings,
  220 + onWebViewCreated: (controller) {
  221 + _inAppWebViewController = controller;
  222 + },
  223 + onTitleChanged:
  224 + (InAppWebViewController controller, String? title) {
  225 + Log.d("$TAG onTitleChanged title=$title`");
  226 + setState(() {
  227 + if (title?.endsWith(".com") == true) {
  228 + widget.webViewTitle = defaultWebViewTitle;
  229 + } else {
  230 + widget.webViewTitle = title ?? defaultWebViewTitle;
  231 + }
  232 + });
  233 + },
  234 + shouldOverrideUrlLoading: (controller, navigationAction) async {
  235 + var url = navigationAction.request.url;
  236 + Log.d(
  237 + "$TAG shouldOverrideUrlLoading url=$url scheme=${url?.scheme}");
  238 + if (![
  239 + "http",
  240 + "https",
  241 + "file",
  242 + "chrome",
  243 + "data",
  244 + "javascript",
  245 + "about",
  246 + ].contains(url?.scheme ?? "")) {
  247 + Log.d("$TAG canLaunchUrl(url)=${canLaunchUrl(url!)}");
  248 + await canLaunchUrl(url)
  249 + ? await launchUrl(url)
  250 + : Log.e("对不起,打不开链接地址:$url");
  251 + return NavigationActionPolicy.CANCEL;
  252 + }
  253 + return NavigationActionPolicy.ALLOW;
  254 + },
  255 + onLoadStop: (controller, url) async {
  256 + Log.d("$TAG onLoadStop url=$url");
  257 + //页面加载完毕,显示隐藏AppBar的返回键
  258 + _inAppWebViewController!.canGoBack().then((canGoBack) => {
  259 + setState(() {
  260 + isCanGoBack = canGoBack;
  261 + })
  262 + });
  263 + _inAppWebViewController!.canGoForward().then((canForward) => {
  264 + setState(() {
  265 + isCanForward = canForward;
  266 + })
  267 + });
  268 + },
  269 + onLoadError: (controller, request, code, message) {
  270 + Log.d(
  271 + "$TAG onReceivedError request=$request error=$code message=$message");
  272 + },
  273 + onLoadHttpError: (controller, request, errorResponse, message) {
  274 + Log.d(
  275 + "$TAG onReceivedError request=$request errorResponse=$errorResponse message=$message");
  276 + },
  277 + onProgressChanged: (controller, progress) {
  278 + Log.d("$TAG onProgressChanged progress=$progress");
  279 + //进度从0 ~ 100
  280 + setState(() {
  281 + _progress = progress / 100.0;
  282 + });
  283 + },
  284 + onUpdateVisitedHistory: (controller, url, androidIsReload) {
  285 + Log.d("$TAG onUpdateVisitedHistory url=$url");
  286 + },
  287 + // onLoadResourceWithCustomScheme: (controller, request) {
  288 + // Log.d("$TAG onLoadResourceWithCustomScheme request=$request");
  289 + // },
  290 + onConsoleMessage: (controller, consoleMessage) {
  291 + // Log.d("$TAG onConsoleMessage consoleMessage=$consoleMessage");
  292 + },
  293 + ),
  294 + )),
  295 + // ),
69 ); 296 );
70 } 297 }
71 } 298 }
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