import 'package:flutter/material.dart'; import 'package:payloads_shared/payloads_shared.dart'; import 'package:payloads_host/payloads_host.dart'; import 'package:payloads_app1/payloads_app1.dart'; import 'src/bridge_webview_controller.dart'; import 'src/bridge_webview.dart'; void main() { runApp(const HostApp()); } class HostApp extends StatelessWidget { const HostApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Host App - Bridge Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const HostAppHome(), ); } } class HostAppHome extends StatefulWidget { const HostAppHome({super.key}); @override State createState() => _HostAppHomeState(); } class _HostAppHomeState extends State { late BridgeWebViewController _bridgeController; final TextEditingController _urlController = TextEditingController(); // Default URL to web_app1 String _currentUrl = 'http://192.168.31.142:8080'; String _status = 'Ready'; bool _isWebViewReady = false; // Events list final List> _events = []; static const int _maxEvents = 10; @override void initState() { super.initState(); _bridgeController = BridgeWebViewController(); _urlController.text = _currentUrl; // Register all payload types _registerPayloads(); // Set up bridge controller _setupBridgeController(); } void _registerPayloads() { // Register shared payloads registerSharedPayloads(); // Register host-specific payloads registerHostPayloads(); // Register app1-specific payloads registerApp1Payloads(); } void _setupBridgeController() { // Listen for web view ready state _bridgeController.addListener(() { if (_bridgeController.isWebViewReady && !_isWebViewReady) { setState(() { _isWebViewReady = true; _status = 'Web App loaded and ready'; }); } }); // Listen for incoming payloads _bridgeController.onPayloadReceived = (payload) { _addEvent('Received', payload.runtimeType.toString(), payload.toString()); }; } void _addEvent(String type, String payloadType, String details) { setState(() { _events.insert(0, { 'timestamp': DateTime.now(), 'type': type, 'payloadType': payloadType, 'details': details, }); // Keep only last N events if (_events.length > _maxEvents) { _events.removeRange(_maxEvents, _events.length); } }); } @override void dispose() { _bridgeController.dispose(); _urlController.dispose(); super.dispose(); } void _loadUrl() { final url = _urlController.text.trim(); if (url.isNotEmpty) { setState(() { _currentUrl = url; _status = 'Loading...'; _isWebViewReady = false; }); _bridgeController.loadUrl(url); } } void _loadWebApp1() { setState(() { _currentUrl = 'http://192.168.31.142:8080'; _urlController.text = _currentUrl; _status = 'Loading Web App 1...'; _isWebViewReady = false; }); _bridgeController.loadUrl(_currentUrl); } void _sendTestPing() { if (!_isWebViewReady) { setState(() => _status = 'Web App not ready'); return; } setState(() => _status = 'Sending ping...'); final pingPayload = PingPayload(message: 'Hello from Flutter Host!'); _bridgeController.sendPayload(pingPayload); setState(() => _status = 'Ping sent'); } void _showTestDialog() { if (!_isWebViewReady) { setState(() => _status = 'Web App not ready'); return; } setState(() => _status = 'Showing dialog...'); final dialogPayload = ShowNativeDialogPayload( title: 'Test Dialog', message: 'This is a test dialog from Flutter Host', dialogType: DialogType.alert, ); _bridgeController.sendPayload(dialogPayload); setState(() => _status = 'Dialog request sent'); } void _sendAdminCommand() { if (!_isWebViewReady) { setState(() => _status = 'Web App not ready'); return; } setState(() => _status = 'Sending admin command...'); final adminPayload = SecretAdminCommandPayload( command: 'get_system_info', parameters: {'detail_level': 'full'}, ); _bridgeController.sendPayload(adminPayload); setState(() => _status = 'Admin command sent'); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text('Flutter Host App - Bridge Demo'), actions: [ Icon( _isWebViewReady ? Icons.check_circle : Icons.error, color: _isWebViewReady ? Colors.green : Colors.red, ), const SizedBox(width: 8), IconButton( icon: const Icon(Icons.refresh), onPressed: _loadUrl, tooltip: 'Reload', ), ], ), body: Column( children: [ // Compact control panel Container( padding: const EdgeInsets.all(4.0), color: Colors.grey.shade50, child: Column( children: [ // Status and URL row Row( children: [ // Status indicator Container( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 4.0, ), decoration: BoxDecoration( color: _isWebViewReady ? Colors.green.shade100 : Colors.orange.shade100, borderRadius: BorderRadius.circular(4), ), child: Text( _status, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: _isWebViewReady ? Colors.green.shade800 : Colors.orange.shade800, fontWeight: FontWeight.w500, ), ), ), const SizedBox(width: 8), // URL input Expanded( child: TextField( controller: _urlController, style: Theme.of(context).textTheme.bodySmall, decoration: InputDecoration( isDense: true, contentPadding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(4), borderSide: BorderSide(color: Colors.grey.shade300), ), hintText: 'URL', hintStyle: Theme.of(context).textTheme.bodySmall, ), onSubmitted: (_) => _loadUrl(), ), ), const SizedBox(width: 4), // Load button SizedBox( height: 32, child: ElevatedButton( onPressed: _loadUrl, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 8), ), child: const Text( 'Load', style: TextStyle(fontSize: 12), ), ), ), ], ), const SizedBox(height: 4), // Quick actions row Row( children: [ // Load Web App 1 Expanded( child: SizedBox( height: 28, child: ElevatedButton.icon( onPressed: _loadWebApp1, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 4), ), icon: const Icon(Icons.web, size: 16), label: const Text( 'Web App 1', style: TextStyle(fontSize: 11), ), ), ), ), const SizedBox(width: 4), // Flutter.dev Expanded( child: SizedBox( height: 28, child: ElevatedButton.icon( onPressed: () { _urlController.text = 'https://flutter.dev'; _loadUrl(); }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 4), ), icon: const Icon(Icons.flutter_dash, size: 16), label: const Text( 'Flutter.dev', style: TextStyle(fontSize: 11), ), ), ), ), const SizedBox(width: 4), // Test functions dropdown PopupMenuButton( child: Container( height: 28, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(4), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.science, size: 16), const SizedBox(width: 4), const Text('Test', style: TextStyle(fontSize: 11)), const Icon(Icons.arrow_drop_down, size: 16), ], ), ), onSelected: (value) { switch (value) { case 'ping': _sendTestPing(); break; case 'dialog': _showTestDialog(); break; case 'admin': _sendAdminCommand(); break; } }, itemBuilder: (context) => [ PopupMenuItem( value: 'ping', enabled: _isWebViewReady, child: const Row( children: [ Icon(Icons.send, size: 16), SizedBox(width: 8), Text('Send Ping'), ], ), ), PopupMenuItem( value: 'dialog', enabled: _isWebViewReady, child: const Row( children: [ Icon(Icons.message, size: 16), SizedBox(width: 8), Text('Show Dialog'), ], ), ), PopupMenuItem( value: 'admin', enabled: _isWebViewReady, child: const Row( children: [ Icon(Icons.admin_panel_settings, size: 16), SizedBox(width: 8), Text('Admin Command'), ], ), ), ], ), ], ), ], ), ), // Events panel if (_events.isNotEmpty) Container( height: 120, padding: const EdgeInsets.all(4.0), color: Colors.blue.shade50, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.history, size: 16), const SizedBox(width: 4), Text( 'Received Events (${_events.length})', style: Theme.of(context).textTheme.bodySmall?.copyWith( fontWeight: FontWeight.bold, ), ), const Spacer(), TextButton( onPressed: () => setState(() => _events.clear()), child: const Text( 'Clear', style: TextStyle(fontSize: 10), ), ), ], ), Expanded( child: ListView.builder( itemCount: _events.length, itemBuilder: (context, index) { final event = _events[index]; final timestamp = event['timestamp'] as DateTime; final payloadType = event['payloadType'] as String; return Container( margin: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: Colors.green.shade100, borderRadius: BorderRadius.circular(3), border: Border.all( color: Colors.grey.shade300, width: 0.5, ), ), child: Row( children: [ Icon( Icons.download, size: 12, color: Colors.green.shade700, ), const SizedBox(width: 4), Expanded( child: Text( '${timestamp.hour.toString().padLeft(2, '0')}:${timestamp.minute.toString().padLeft(2, '0')}:${timestamp.second.toString().padLeft(2, '0')} - $payloadType', style: Theme.of(context).textTheme.bodySmall ?.copyWith( fontSize: 10, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), ], ), ); }, ), ), ], ), ), // WebView Expanded( child: BridgeWebView( initialUrl: _currentUrl, controller: _bridgeController, ), ), ], ), ); } }