- Add flutter clean before build to prevent stale cached artifacts - Add VERSION build arg for explicit cache busting - Reorder build steps: clean → pub get → build_runner → health.json → build - Replace deprecated dart:html with package:web in iframe_view_web.dart - Add lint ignore to generate_health_json.dart Fixes issue where Docker layer caching kept old main.dart.js while regenerating health.json with new version number. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
146 lines
3.9 KiB
Dart
146 lines
3.9 KiB
Dart
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<IframeView> createState() => _IframeViewState();
|
|
}
|
|
|
|
class _IframeViewState extends State<IframeView> {
|
|
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<void> _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(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|