import 'package:flutter/material.dart'; import '../models/udp_package.dart'; class PackageDialog extends StatefulWidget { final UdpPackage? package; final Function(UdpPackage) onSave; const PackageDialog({ super.key, this.package, required this.onSave, }); @override State createState() => _PackageDialogState(); } class _PackageDialogState extends State { late TextEditingController _nameController; late TextEditingController _ipController; late TextEditingController _dataController; final _formKey = GlobalKey(); late bool _isBroadcast; late List _ipAddresses; @override void initState() { super.initState(); _nameController = TextEditingController(text: widget.package?.name ?? ''); _ipController = TextEditingController(); _dataController = TextEditingController(text: widget.package?.data ?? ''); _isBroadcast = widget.package?.isBroadcast ?? false; _ipAddresses = widget.package?.ipAddresses.toList() ?? ['127.0.0.1']; } @override void dispose() { _nameController.dispose(); _ipController.dispose(); _dataController.dispose(); super.dispose(); } bool _isValidIp(String ip) { final parts = ip.split('.'); if (parts.length != 4) return false; for (final part in parts) { final num = int.tryParse(part); if (num == null || num < 0 || num > 255) return false; } return true; } void _addIpAddress() { final ip = _ipController.text.trim(); if (ip.isNotEmpty && _isValidIp(ip)) { if (!_ipAddresses.contains(ip)) { setState(() { _ipAddresses.add(ip); _ipController.clear(); }); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('IP address already added'), behavior: SnackBarBehavior.floating, ), ); } } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Invalid IP address'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); } } void _removeIpAddress(String ip) { setState(() { _ipAddresses.remove(ip); // Ensure at least one IP if not in broadcast mode if (_ipAddresses.isEmpty && !_isBroadcast) { _ipAddresses.add('127.0.0.1'); } }); } void _save() { if (_formKey.currentState!.validate()) { // Validate we have IPs if not in broadcast mode if (!_isBroadcast && _ipAddresses.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Please add at least one IP address'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); return; } final package = UdpPackage( id: widget.package?.id ?? DateTime.now().millisecondsSinceEpoch.toString(), name: _nameController.text.trim(), ipAddresses: _isBroadcast ? [] : _ipAddresses, isBroadcast: _isBroadcast, data: _dataController.text, ); widget.onSave(package); Navigator.pop(context); } } @override Widget build(BuildContext context) { return AlertDialog( title: Text(widget.package == null ? 'New Package' : 'Edit Package'), content: SingleChildScrollView( child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Package Name TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: 'Package Name', prefixIcon: Icon(Icons.label), ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Please enter a package name'; } return null; }, autofocus: true, ), const SizedBox(height: 16), // Broadcast Mode Toggle SwitchListTile( title: const Text('Broadcast Mode'), subtitle: const Text('Send to all devices on network'), value: _isBroadcast, onChanged: (value) { setState(() { _isBroadcast = value; }); }, secondary: const Icon(Icons.sensors), ), const SizedBox(height: 8), // IP Addresses Section (only shown when not in broadcast mode) if (!_isBroadcast) ...[ Text( 'Target IP Addresses', style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 8), // IP Address Input Row( children: [ Expanded( child: TextField( controller: _ipController, decoration: const InputDecoration( labelText: 'IP Address', prefixIcon: Icon(Icons.computer), hintText: '192.168.1.100', isDense: true, ), keyboardType: TextInputType.number, onSubmitted: (_) => _addIpAddress(), ), ), const SizedBox(width: 8), IconButton.filled( onPressed: _addIpAddress, icon: const Icon(Icons.add), tooltip: 'Add IP', ), ], ), const SizedBox(height: 12), // IP Address Chips if (_ipAddresses.isNotEmpty) Wrap( spacing: 8, runSpacing: 8, children: _ipAddresses.map((ip) { return Chip( label: Text(ip), deleteIcon: const Icon(Icons.close, size: 18), onDeleted: _ipAddresses.length > 1 ? () => _removeIpAddress(ip) : null, ); }).toList(), ), const SizedBox(height: 8), ] else ...[ // Broadcast Info Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon( Icons.info_outline, size: 20, color: Theme.of(context).colorScheme.onPrimaryContainer, ), const SizedBox(width: 8), Expanded( child: Text( 'This package will be broadcast to all devices on your local network', style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), ), ], ), ), const SizedBox(height: 16), ], // Data Field TextFormField( controller: _dataController, decoration: const InputDecoration( labelText: 'Data', prefixIcon: Icon(Icons.data_object), hintText: 'Enter the data to send', ), maxLines: 4, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter data to send'; } return null; }, ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), FilledButton( onPressed: _save, child: const Text('Save'), ), ], ); } }