multicast/broadcast support
This commit is contained in:
@ -20,15 +20,18 @@ class _PackageDialogState extends State<PackageDialog> {
|
||||
late TextEditingController _ipController;
|
||||
late TextEditingController _dataController;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
late bool _isBroadcast;
|
||||
late List<String> _ipAddresses;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.package?.name ?? '');
|
||||
_ipController = TextEditingController(
|
||||
text: widget.package?.ipAddress ?? '127.0.0.1',
|
||||
);
|
||||
_ipController = TextEditingController();
|
||||
_dataController = TextEditingController(text: widget.package?.data ?? '');
|
||||
_isBroadcast = widget.package?.isBroadcast ?? false;
|
||||
_ipAddresses = widget.package?.ipAddresses.toList() ?? ['127.0.0.1'];
|
||||
}
|
||||
|
||||
@override
|
||||
@ -49,12 +52,62 @@ class _PackageDialogState extends State<PackageDialog> {
|
||||
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(),
|
||||
ipAddress: _ipController.text.trim(),
|
||||
ipAddresses: _isBroadcast ? [] : _ipAddresses,
|
||||
isBroadcast: _isBroadcast,
|
||||
data: _dataController.text,
|
||||
);
|
||||
widget.onSave(package);
|
||||
@ -71,7 +124,9 @@ class _PackageDialogState extends State<PackageDialog> {
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Package Name
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
@ -87,25 +142,103 @@ class _PackageDialogState extends State<PackageDialog> {
|
||||
autofocus: true,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _ipController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'IP Address',
|
||||
prefixIcon: Icon(Icons.computer),
|
||||
hintText: '192.168.1.100',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Please enter an IP address';
|
||||
}
|
||||
if (!_isValidIp(value.trim())) {
|
||||
return 'Invalid IP address';
|
||||
}
|
||||
return null;
|
||||
|
||||
// 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: 16),
|
||||
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(
|
||||
|
||||
@ -27,7 +27,7 @@ class _ProjectDialogState extends State<ProjectDialog> {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.project?.name ?? '');
|
||||
_ipController = TextEditingController(
|
||||
text: widget.project?.ipAddress ?? '127.0.0.1',
|
||||
text: widget.project?.ipAddresses.firstOrNull ?? '127.0.0.1',
|
||||
);
|
||||
_portController = TextEditingController(
|
||||
text: widget.project?.port.toString() ?? '8888',
|
||||
@ -47,7 +47,8 @@ class _ProjectDialogState extends State<ProjectDialog> {
|
||||
final project = Project(
|
||||
id: widget.project?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
name: _nameController.text.trim(),
|
||||
ipAddress: _ipController.text.trim(),
|
||||
ipAddresses: [_ipController.text.trim()],
|
||||
isBroadcast: false,
|
||||
port: int.parse(_portController.text),
|
||||
packages: widget.project?.packages ?? [],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user