mnemo_cards/games/apps/host_app/lib/main.dart
2025-11-11 02:55:41 +03:00

478 lines
16 KiB
Dart

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<HostAppHome> createState() => _HostAppHomeState();
}
class _HostAppHomeState extends State<HostAppHome> {
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<Map<String, dynamic>> _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<String>(
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,
),
),
],
),
);
}
}