import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../../design/pencil_theme.dart'; /// 通用应用内 Web 页(任意 HTTPS/HTTP 链接),供用户协议、隐私政策及其他 H5 复用。 class AppWebViewScreen extends StatefulWidget { const AppWebViewScreen({ super.key, required this.title, required this.initialUrl, }); final String title; /// 完整 URL(含 `http`/`https`)。 final String initialUrl; @override State createState() => _AppWebViewScreenState(); } class _AppWebViewScreenState extends State { WebViewController? _controller; bool _loading = true; String? _loadError; @override void initState() { super.initState(); final uri = _parseHttpUrl(widget.initialUrl); if (uri == null) { _loadError = 'Invalid link'; _loading = false; return; } _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate( onPageStarted: (_) { if (mounted) setState(() => _loading = true); }, onPageFinished: (_) { if (mounted) setState(() => _loading = false); }, onWebResourceError: (WebResourceError e) { if (mounted) { setState(() { _loading = false; _loadError = e.description.isNotEmpty ? e.description : 'Failed to load page'; }); } }, ), ) ..loadRequest(uri); } static Uri? _parseHttpUrl(String raw) { final u = Uri.tryParse(raw.trim()); if (u == null || !u.hasScheme) return null; if (u.scheme != 'http' && u.scheme != 'https') return null; return u; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, surfaceTintColor: Colors.transparent, elevation: 0, foregroundColor: PencilTheme.stone900, title: Text( widget.title, style: GoogleFonts.inter( fontSize: 17, fontWeight: FontWeight.w600, color: PencilTheme.stone900, ), ), leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20), onPressed: () => Navigator.of(context).pop(), ), ), body: _buildBody(), ); } Widget _buildBody() { if (_loadError != null && _controller == null) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Text( _loadError!, textAlign: TextAlign.center, style: GoogleFonts.inter( fontSize: 15, color: PencilTheme.stone600, ), ), ), ); } final c = _controller; if (c == null) { return const SizedBox.shrink(); } return Stack( fit: StackFit.expand, children: [ WebViewWidget(controller: c), if (_loading) const ColoredBox( color: Color(0x0F000000), child: Center( child: CircularProgressIndicator( color: PencilTheme.underlineGold, ), ), ), if (_loadError != null && _controller != null) Positioned( left: 0, right: 0, bottom: 0, child: Material( color: const Color(0xFFFFF7ED), child: SafeArea( top: false, child: Padding( padding: const EdgeInsets.all(12), child: Text( _loadError!, style: GoogleFonts.inter( fontSize: 13, color: const Color(0xFF9A3412), ), ), ), ), ), ), ], ); } }