import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/project.dart'; import '../models/udp_package.dart'; import '../services/udp_service.dart'; import '../widgets/package_dialog.dart'; class ProjectScreen extends StatefulWidget { final Project project; const ProjectScreen({super.key, required this.project}); @override State createState() => _ProjectScreenState(); } class _ProjectScreenState extends State { late Project _project; final UdpService _udpService = UdpService(); String? _lastSentPackageId; bool _isSending = false; late TextEditingController _portController; @override void initState() { super.initState(); _project = widget.project; _portController = TextEditingController(text: _project.port.toString()); } @override void dispose() { _portController.dispose(); super.dispose(); } void _addPackage(UdpPackage package) { setState(() { _project = _project.copyWith( packages: [..._project.packages, package], ); }); } void _updatePackage(UdpPackage updatedPackage) { setState(() { final packages = _project.packages.map((p) { return p.id == updatedPackage.id ? updatedPackage : p; }).toList(); _project = _project.copyWith(packages: packages); }); } void _deletePackage(String packageId) { setState(() { final packages = _project.packages.where((p) => p.id != packageId).toList(); _project = _project.copyWith(packages: packages); }); } void _copyPackage(UdpPackage package) { final copiedPackage = UdpPackage( id: DateTime.now().millisecondsSinceEpoch.toString(), name: '${package.name} (Copy)', data: package.data, ipAddress: package.ipAddress, ); _addPackage(copiedPackage); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Package copied successfully!'), behavior: SnackBarBehavior.floating, ), ); } void _updatePort() { final port = int.tryParse(_portController.text); if (port != null && port >= 1 && port <= 65535) { setState(() { _project = _project.copyWith(port: port); }); } else { _portController.text = _project.port.toString(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Invalid port number (1-65535)'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); } } void _showPackageDialog({UdpPackage? package}) { showDialog( context: context, builder: (context) => PackageDialog( package: package, onSave: (newPackage) { if (package == null) { _addPackage(newPackage); } else { _updatePackage(newPackage); } }, ), ); } Future _sendPackage(UdpPackage package) async { if (kIsWeb) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('UDP is not supported in web browsers. Please run on desktop or mobile.'), backgroundColor: Colors.orange, behavior: SnackBarBehavior.floating, duration: Duration(seconds: 4), ), ); return; } setState(() { _isSending = true; }); final success = await _udpService.sendPackage( data: package.data, ipAddress: package.ipAddress, port: _project.port, ); setState(() { _isSending = false; if (success) { _lastSentPackageId = package.id; // Reset the indicator after 2 seconds Future.delayed(const Duration(seconds: 2), () { if (mounted && _lastSentPackageId == package.id) { setState(() { _lastSentPackageId = null; }); } }); } }); if (!mounted) return; if (success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Package "${package.name}" sent successfully!'), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, ), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to send package "${package.name}"'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); } } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (!didPop) { Navigator.of(context).pop(_project); } }, child: Scaffold( appBar: AppBar( title: Text(_project.name), ), body: Column( children: [ // Settings Card Container( width: double.infinity, margin: const EdgeInsets.all(16), child: Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.settings, size: 20, color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 8), Text( 'Project Settings', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: TextField( controller: _portController, decoration: const InputDecoration( labelText: 'UDP Port', prefixIcon: Icon(Icons.router), isDense: true, ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly ], onSubmitted: (_) => _updatePort(), ), ), const SizedBox(width: 8), FilledButton.icon( onPressed: _updatePort, icon: const Icon(Icons.check, size: 18), label: const Text('Apply'), ), ], ), ], ), ), ), ), // Packages List Expanded( child: _project.packages.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.inventory_2_outlined, size: 64, color: Theme.of(context).colorScheme.primary.withAlpha(128), ), const SizedBox(height: 16), Text( 'No packages yet', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 8), Text( 'Create your first UDP package', style: Theme.of(context).textTheme.bodyMedium, ), ], ), ) : ListView.builder( padding: const EdgeInsets.all(16), itemCount: _project.packages.length, itemBuilder: (context, index) { final package = _project.packages[index]; final wasSent = _lastSentPackageId == package.id; return Card( margin: const EdgeInsets.only(bottom: 12), color: wasSent ? Theme.of(context).colorScheme.primaryContainer : null, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( package.name, style: Theme.of(context) .textTheme .titleMedium ?.copyWith( fontWeight: FontWeight.bold, ), ), ), if (wasSent) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(12), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.check, size: 14, color: Colors.white, ), SizedBox(width: 4), Text( 'Sent', style: TextStyle( color: Colors.white, fontSize: 12, ), ), ], ), ), ], ), const SizedBox(height: 4), Text( 'To: ${package.ipAddress}', style: Theme.of(context).textTheme.bodySmall, ), ], ), ), PopupMenuButton( itemBuilder: (context) => [ const PopupMenuItem( value: 'edit', child: Row( children: [ Icon(Icons.edit), SizedBox(width: 8), Text('Edit'), ], ), ), const PopupMenuItem( value: 'copy', child: Row( children: [ Icon(Icons.copy), SizedBox(width: 8), Text('Copy'), ], ), ), const PopupMenuItem( value: 'delete', child: Row( children: [ Icon(Icons.delete, color: Colors.red), SizedBox(width: 8), Text('Delete', style: TextStyle(color: Colors.red)), ], ), ), ], onSelected: (value) { if (value == 'edit') { _showPackageDialog(package: package); } else if (value == 'copy') { _copyPackage(package); } else if (value == 'delete') { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Delete Package'), content: Text( 'Are you sure you want to delete "${package.name}"?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), TextButton( onPressed: () { _deletePackage(package.id); Navigator.pop(context); }, style: TextButton.styleFrom( foregroundColor: Colors.red, ), child: const Text('Delete'), ), ], ), ); } }, ), ], ), const SizedBox(height: 12), Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context) .colorScheme .surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), child: Text( package.data, style: const TextStyle( fontFamily: 'monospace', fontSize: 12, ), ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _isSending ? null : () => _sendPackage(package), icon: _isSending ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, ), ) : const Icon(Icons.send), label: Text(_isSending ? 'Sending...' : 'Send'), ), ), ], ), ), ); }, ), ), ], ), floatingActionButton: FloatingActionButton.extended( onPressed: () => _showPackageDialog(), icon: const Icon(Icons.add), label: const Text('New Package'), ), ), ); } }