import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../models/network/network_link.dart'; import '../../../models/network/network_port.dart'; import '../../../providers/network_map/network_devices_provider.dart'; /// Opens an M3 dialog to connect [portId] to a port on another device. /// /// Returns true if a link was successfully created. Future showLinkEditDialog({ required BuildContext context, required WidgetRef ref, required String portId, required String currentDeviceId, }) async { final result = await showDialog( context: context, builder: (_) => _LinkEditDialog( portId: portId, currentDeviceId: currentDeviceId, ), ); return result == true; } class _LinkEditDialog extends ConsumerStatefulWidget { const _LinkEditDialog({ required this.portId, required this.currentDeviceId, }); final String portId; final String currentDeviceId; @override ConsumerState<_LinkEditDialog> createState() => _LinkEditDialogState(); } class _LinkEditDialogState extends ConsumerState<_LinkEditDialog> { String? _selectedDeviceId; String? _selectedPortId; NetworkLinkKind? _linkKind; final _cableLabelCtrl = TextEditingController(); bool _saving = false; @override void dispose() { _cableLabelCtrl.dispose(); super.dispose(); } Future _save() async { final portA = widget.portId; final portB = _selectedPortId; if (portB == null) return; setState(() => _saving = true); try { ref.invalidate(networkLinksProvider); final controller = ref.read(networkDevicesControllerProvider); await controller.createLink( portA: portA, portB: portB, linkKind: _linkKind, cableLabel: _cableLabelCtrl.text.trim().isEmpty ? null : _cableLabelCtrl.text.trim(), ); if (mounted) Navigator.of(context).pop(true); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to create link: $e'), backgroundColor: Theme.of(context).colorScheme.error, ), ); } } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { final devicesAsync = ref.watch(networkDevicesProvider); final portsAsync = _selectedDeviceId != null ? ref.watch(networkPortsByDeviceProvider(_selectedDeviceId!)) : const AsyncValue>.data([]); final allLinksAsync = ref.watch(networkLinksProvider); final otherDevices = (devicesAsync.valueOrNull ?? const []) .where((d) => d.id != widget.currentDeviceId) .toList() ..sort((a, b) => a.name.compareTo(b.name)); final allLinks = allLinksAsync.valueOrNull ?? const []; final usedPortIds = { for (final link in allLinks) ...[link.portA, link.portB], }; final devicePorts = portsAsync.valueOrNull ?? const []; final availablePorts = devicePorts.where((p) => !usedPortIds.contains(p.id)).toList(); final canSave = _selectedPortId != null && !_saving; return AlertDialog( title: const Text('Connect port'), content: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ DropdownButtonFormField( initialValue: _selectedDeviceId, decoration: const InputDecoration(labelText: 'Remote device *'), items: [ for (final d in otherDevices) DropdownMenuItem(value: d.id, child: Text(d.name)), ], onChanged: (v) => setState(() { _selectedDeviceId = v; _selectedPortId = null; }), ), const SizedBox(height: 12), DropdownButtonFormField( key: ValueKey(_selectedDeviceId), initialValue: _selectedPortId, decoration: InputDecoration( labelText: 'Remote port *', helperText: _selectedDeviceId == null ? 'Select a device first' : availablePorts.isEmpty ? 'No available ports on this device' : null, ), items: [ for (final p in availablePorts) DropdownMenuItem( value: p.id, child: Text(p.portNumber), ), ], onChanged: _selectedDeviceId == null ? null : (v) => setState(() => _selectedPortId = v), ), const SizedBox(height: 12), DropdownButtonFormField( initialValue: _linkKind, decoration: const InputDecoration(labelText: 'Link type'), items: [ const DropdownMenuItem( value: null, child: Text('(unknown)'), ), for (final k in NetworkLinkKind.values) DropdownMenuItem(value: k, child: Text(k.label)), ], onChanged: (v) => setState(() => _linkKind = v), ), const SizedBox(height: 12), TextField( controller: _cableLabelCtrl, decoration: const InputDecoration( labelText: 'Cable label', helperText: 'e.g. "CAT6-01", "SMF-A3" (optional)', ), ), ], ), ), ), actions: [ TextButton( onPressed: _saving ? null : () => Navigator.pop(context, false), child: const Text('Cancel'), ), FilledButton( onPressed: canSave ? _save : null, child: Text(_saving ? 'Saving…' : 'Connect'), ), ], ); } }