// Version: 1.2.0 | Created: 2026-04-05 | Updated: 2026-04-10 // Full-screen Jitsi meeting embedded via HtmlElementView (web platform view). import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../data/jitsi_web_service.dart'; import '../domain/jitsi_link.dart'; class JitsiScreen extends StatefulWidget { const JitsiScreen({super.key, required this.meetingUrl}); /// The raw meeting link or room name provided by the user. final String meetingUrl; @override State createState() => _JitsiScreenState(); } class _JitsiScreenState extends State { late final JitsiLink? _link; bool _meetingStarted = false; bool _meetingEnded = false; @override void initState() { super.initState(); _link = JitsiLink.tryParse(widget.meetingUrl); if (_link != null) { JitsiWebService.instance.ensureViewRegistered(); } } @override void dispose() { JitsiWebService.instance.dispose(); super.dispose(); } void _startMeeting() { if (_link == null || _meetingStarted) return; _meetingStarted = true; // Post-frame so the HtmlElementView div is mounted in the DOM. WidgetsBinding.instance.addPostFrameCallback((_) { JitsiWebService.instance.joinMeeting( roomName: _link.roomName, jwt: _link.jwt, onReadyToClose: () { if (mounted) { setState(() => _meetingEnded = true); } }, ); }); } @override Widget build(BuildContext context) { if (_link == null) { return Scaffold( appBar: AppBar(title: const Text('Conference')), body: Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.link_off, size: 48), const SizedBox(height: 16), const Text('Could not parse the meeting link.'), const SizedBox(height: 24), ElevatedButton( onPressed: () => context.go('/rooms'), child: const Text('Back'), ), ], ), ), ), ); } if (_meetingEnded) { return Scaffold( body: Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.videocam_off_outlined, size: 64, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 16), Text( 'Meeting ended', style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 24), ElevatedButton( onPressed: () => context.go('/rooms'), child: const Text('Back to M8Chat'), ), ], ), ), ), ); } // Render the Jitsi iframe and kick off the meeting once mounted. _startMeeting(); return Scaffold( body: const HtmlElementView(viewType: JitsiWebService.viewType), ); } }