import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart' as desktop; import 'package:webview_flutter/webview_flutter.dart' as mobile; bool get _useDesktopWebView => !kIsWeb && (Platform.isWindows || Platform.isLinux); class AnnouncementDialog extends StatefulWidget { final String title; final String html; final String baseUrl; const AnnouncementDialog({ super.key, required this.title, required this.html, required this.baseUrl, }); static Future show( BuildContext context, { required String title, required String html, required String baseUrl, }) async { if (html.trim().isEmpty) return; await showDialog( context: context, barrierDismissible: true, builder: (_) => AnnouncementDialog(title: title, html: html, baseUrl: baseUrl), ); } @override State createState() => _AnnouncementDialogState(); } class _AnnouncementDialogState extends State { mobile.WebViewController? _mobileController; desktop.InAppWebViewController? _desktopController; late final String _html; late final String _baseUrl; bool _loading = true; @override void initState() { super.initState(); final origin = Uri.parse(widget.baseUrl).origin; _html = _wrapHtml(widget.html); _baseUrl = '$origin/'; if (!_useDesktopWebView) { _mobileController = mobile.WebViewController() ..setJavaScriptMode(mobile.JavaScriptMode.unrestricted) ..setBackgroundColor(Colors.transparent) ..setNavigationDelegate( mobile.NavigationDelegate( onPageFinished: (_) { if (mounted) setState(() => _loading = false); }, ), ) ..loadHtmlString(_html, baseUrl: _baseUrl); } } @override void dispose() { _desktopController?.dispose(); super.dispose(); } String _wrapHtml(String body) { final encodedTitle = const HtmlEscape().convert(widget.title); return ''' $encodedTitle $body '''; } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Dialog( insetPadding: const EdgeInsets.symmetric(horizontal: 18, vertical: 28), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)), child: ClipRRect( borderRadius: BorderRadius.circular(22), child: SizedBox( width: double.infinity, height: MediaQuery.of(context).size.height * 0.72, child: Column( children: [ Container( padding: const EdgeInsets.fromLTRB(18, 14, 8, 10), decoration: BoxDecoration( color: theme.colorScheme.surface, border: Border( bottom: BorderSide( color: theme.dividerColor.withValues(alpha: 0.45), ), ), ), child: Row( children: [ Expanded( child: Text( widget.title, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w800, ), ), ), IconButton( tooltip: '关闭', onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close), ), ], ), ), Expanded( child: Stack( children: [ _buildWebView(), if (_loading) const Center(child: CircularProgressIndicator()), ], ), ), ], ), ), ), ); } Widget _buildWebView() { if (_useDesktopWebView) { return desktop.InAppWebView( initialData: desktop.InAppWebViewInitialData( data: _html, baseUrl: desktop.WebUri(_baseUrl), ), initialSettings: desktop.InAppWebViewSettings( javaScriptEnabled: true, domStorageEnabled: true, transparentBackground: true, supportZoom: false, ), onWebViewCreated: (controller) { _desktopController = controller; }, onLoadStop: (_, url) { if (mounted) setState(() => _loading = false); }, onReceivedError: (_, request, error) { if (mounted) setState(() => _loading = false); }, ); } return mobile.WebViewWidget(controller: _mobileController!); } }