import 'dart:ui_web' as ui_web; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:web/web.dart' as web; /// Embedded iframe view for displaying external content (web only). /// /// Uses HtmlElementView for Flutter web to embed an iframe. /// Includes a header bar with title, refresh, open in new tab, and close actions. class IframeView extends StatefulWidget { const IframeView({ super.key, required this.url, required this.title, required this.onClose, }); /// URL to display in the iframe. final String url; /// Title displayed in the header bar. final String title; /// Callback when the close button is pressed. final VoidCallback onClose; @override State createState() => _IframeViewState(); } class _IframeViewState extends State { late final String _viewType; late web.HTMLIFrameElement _iframe; bool _isLoading = true; @override void initState() { super.initState(); _viewType = 'iframe-${widget.url.hashCode}-${DateTime.now().millisecondsSinceEpoch}'; _createIframe(); } void _createIframe() { _iframe = web.document.createElement('iframe') as web.HTMLIFrameElement ..src = widget.url ..style.border = 'none' ..style.width = '100%' ..style.height = '100%' ..allow = 'fullscreen'; _iframe.onLoad.listen((_) { if (mounted) { setState(() => _isLoading = false); } }); // Register the view factory ui_web.platformViewRegistry.registerViewFactory( _viewType, (int viewId) => _iframe, ); } void _refresh() { setState(() => _isLoading = true); // Reload the iframe by setting src again _iframe.src = widget.url; } Future _openInNewTab() async { final uri = Uri.tryParse(widget.url); if (uri != null && await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final textTheme = Theme.of(context).textTheme; return Column( children: [ // Header bar Container( height: 56, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest, border: Border( bottom: BorderSide(color: colorScheme.outlineVariant), ), ), child: Row( children: [ // Title Expanded( child: Text( widget.title, style: textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), ), // Actions IconButton( icon: const Icon(Icons.open_in_new, size: 20), tooltip: 'Open in new tab', onPressed: _openInNewTab, ), IconButton( icon: const Icon(Icons.refresh, size: 20), tooltip: 'Refresh', onPressed: _refresh, ), IconButton( icon: const Icon(Icons.close, size: 20), tooltip: 'Close', onPressed: widget.onClose, ), ], ), ), // Iframe content Expanded( child: Stack( children: [ HtmlElementView(viewType: _viewType), // Loading overlay if (_isLoading) Container( color: colorScheme.surface, child: const Center( child: CircularProgressIndicator(), ), ), ], ), ), ], ); } }