131 lines
4.2 KiB
Dart
131 lines
4.2 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
|
||
/// WebView 容器(替代 Android AgentWeb)
|
||
/// 支持:进度条 / 标题自动同步 / 返回键关闭 / 拦截外链 / JS 桥
|
||
class AgentWebPage extends StatefulWidget {
|
||
final String url;
|
||
final String title;
|
||
const AgentWebPage({
|
||
super.key,
|
||
required this.url,
|
||
required this.title,
|
||
});
|
||
|
||
@override
|
||
State<AgentWebPage> createState() => _AgentWebPageState();
|
||
}
|
||
|
||
class _AgentWebPageState extends State<AgentWebPage> {
|
||
InAppWebViewController? _controller;
|
||
double _progress = 0;
|
||
String _title = '';
|
||
bool _canGoBack = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_title = widget.title;
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: Text(_title.isEmpty ? '加载中…' : _title),
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.close),
|
||
onPressed: () => context.pop(),
|
||
),
|
||
actions: [
|
||
if (_canGoBack)
|
||
IconButton(
|
||
icon: const Icon(Icons.arrow_back),
|
||
onPressed: () => _controller?.goBack(),
|
||
),
|
||
IconButton(
|
||
icon: const Icon(Icons.refresh),
|
||
onPressed: () => _controller?.reload(),
|
||
),
|
||
PopupMenuButton<String>(
|
||
onSelected: (v) async {
|
||
switch (v) {
|
||
case 'copy':
|
||
// ignore: deprecated_member_use
|
||
// await Clipboard.setData(ClipboardData(text: widget.url));
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('链接已复制')),
|
||
);
|
||
}
|
||
break;
|
||
case 'browser':
|
||
// ignore: deprecated_member_use
|
||
// await launchUrl(Uri.parse(widget.url));
|
||
break;
|
||
}
|
||
},
|
||
itemBuilder: (_) => const [
|
||
PopupMenuItem(value: 'copy', child: Text('复制链接')),
|
||
PopupMenuItem(value: 'browser', child: Text('浏览器打开')),
|
||
],
|
||
),
|
||
],
|
||
bottom: _progress < 1
|
||
? PreferredSize(
|
||
preferredSize: const Size.fromHeight(2),
|
||
child: LinearProgressIndicator(
|
||
value: _progress,
|
||
backgroundColor: Colors.white24,
|
||
valueColor: const AlwaysStoppedAnimation(Colors.white),
|
||
),
|
||
)
|
||
: null,
|
||
),
|
||
body: InAppWebView(
|
||
initialUrlRequest: URLRequest(url: WebUri(widget.url)),
|
||
initialSettings: InAppWebViewSettings(
|
||
javaScriptEnabled: true,
|
||
javaScriptCanOpenWindowsAutomatically: true,
|
||
useShouldOverrideUrlLoading: true,
|
||
mediaPlaybackRequiresUserGesture: false,
|
||
allowFileAccess: true,
|
||
domStorageEnabled: true,
|
||
databaseEnabled: true,
|
||
cacheEnabled: true,
|
||
supportZoom: true,
|
||
transparentBackground: false,
|
||
),
|
||
onWebViewCreated: (c) => _controller = c,
|
||
onProgressChanged: (_, p) {
|
||
if (!mounted) return;
|
||
setState(() => _progress = p / 100);
|
||
},
|
||
onTitleChanged: (_, t) {
|
||
if (!mounted) return;
|
||
setState(() => _title = t ?? widget.title);
|
||
},
|
||
onUpdateVisitedHistory: (_, url, __) {
|
||
_controller?.canGoBack().then((v) {
|
||
if (mounted) setState(() => _canGoBack = v);
|
||
});
|
||
},
|
||
shouldOverrideUrlLoading: (_, nav) async {
|
||
final uri = nav.request.url;
|
||
if (uri == null) return NavigationActionPolicy.ALLOW;
|
||
// 拦截 tel:/mailto:/weixin:/alipays:
|
||
final s = uri.toString();
|
||
if (s.startsWith('tel:') ||
|
||
s.startsWith('mailto:') ||
|
||
s.startsWith('weixin://') ||
|
||
s.startsWith('alipays://')) {
|
||
return NavigationActionPolicy.CANCEL;
|
||
}
|
||
return NavigationActionPolicy.ALLOW;
|
||
},
|
||
),
|
||
);
|
||
}
|
||
}
|