import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../providers/providers.dart'; /// Экран создания новой группы ламп. /// Загружает список устройств, позволяет выбрать нужные. class GroupEditScreen extends ConsumerStatefulWidget { const GroupEditScreen({super.key}); @override ConsumerState createState() => _GroupEditScreenState(); } class _GroupEditScreenState extends ConsumerState { final _idCtrl = TextEditingController(); final _nameCtrl = TextEditingController(); final Set _selectedMacs = {}; bool _loading = true; bool _saving = false; bool _rescanning = false; @override void initState() { super.initState(); _loadDevices(); } @override void dispose() { _idCtrl.dispose(); _nameCtrl.dispose(); super.dispose(); } Future _loadDevices() async { await ref.read(devicesProvider.notifier).load(); if (mounted) setState(() => _loading = false); } /// Пересканировать сеть и перезагрузить устройства Future _rescan() async { setState(() => _rescanning = true); try { await ref.read(apiProvider).rescanNetwork(); // Подождать немного -- сканирование асинхронное await Future.delayed(const Duration(seconds: 3)); await ref.read(devicesProvider.notifier).load(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ошибка сканирования: $e')), ); } } if (mounted) setState(() => _rescanning = false); } @override Widget build(BuildContext context) { final devices = ref.watch(devicesProvider); return Scaffold( appBar: AppBar( title: const Text('НОВАЯ ГРУППА'), actions: [ // Кнопка ресканирования сети IconButton( icon: _rescanning ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.refresh), tooltip: 'Пересканировать сеть', onPressed: _rescanning ? null : _rescan, ), ], ), body: _loading ? const Center(child: CircularProgressIndicator(color: Colors.deepOrange)) : Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ID группы TextField( controller: _idCtrl, decoration: const InputDecoration( labelText: 'ID группы (например "bedroom")', prefixIcon: Icon(Icons.tag), ), ), const SizedBox(height: 12), // Название группы TextField( controller: _nameCtrl, decoration: const InputDecoration( labelText: 'Название (например "Спальня")', prefixIcon: Icon(Icons.label), ), textCapitalization: TextCapitalization.sentences, ), const SizedBox(height: 16), // Заголовок списка устройств Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Устройства (${_selectedMacs.length} выбрано)', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white70, ), ), TextButton( onPressed: () { setState(() { if (_selectedMacs.length == devices.length) { _selectedMacs.clear(); } else { for (final d in devices) { final mac = _extractMac(d); if (mac != null) _selectedMacs.add(mac); } } }); }, child: Text( _selectedMacs.length == devices.length ? 'Снять все' : 'Выбрать все', style: const TextStyle(fontSize: 12), ), ), ], ), // Список устройств Expanded( child: devices.isEmpty ? const Center( child: Text( 'Устройства не найдены.\nПопробуйте пересканировать сеть.', textAlign: TextAlign.center, style: TextStyle(color: Colors.white38), ), ) : ListView.builder( itemCount: devices.length, itemBuilder: (context, index) { final d = devices[index]; final mac = _extractMac(d) ?? ''; final name = _extractName(d); final ip = _extractIp(d); final selected = _selectedMacs.contains(mac); return CheckboxListTile( value: selected, activeColor: Colors.deepOrange, title: Text(name), subtitle: Text( '$mac${ip != null ? ' - $ip' : ''}', style: const TextStyle( fontSize: 11, color: Colors.white38), ), onChanged: (v) { setState(() { if (v == true) { _selectedMacs.add(mac); } else { _selectedMacs.remove(mac); } }); }, ); }, ), ), // Кнопка сохранения Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom + 8, ), child: SizedBox( width: double.infinity, height: 48, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.deepOrange, foregroundColor: Colors.white, ), onPressed: _saving ? null : _save, child: _saving ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white), ) : const Text('СОЗДАТЬ ГРУППУ'), ), ), ), ], ), ), ); } /// Извлечь MAC-адрес из объекта устройства String? _extractMac(dynamic device) { if (device is Map) { return (device['mac'] ?? device['id'] ?? device['mac_address'])?.toString(); } return device?.toString(); } /// Извлечь имя устройства String _extractName(dynamic device) { if (device is Map) { return (device['name'] ?? device['model'] ?? device['mac'] ?? 'Лампа') .toString(); } return device?.toString() ?? 'Лампа'; } /// Извлечь IP-адрес String? _extractIp(dynamic device) { if (device is Map) { return (device['ip'] ?? device['address'])?.toString(); } return null; } Future _save() async { final id = _idCtrl.text.trim(); final name = _nameCtrl.text.trim(); if (id.isEmpty || name.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Укажите ID и название')), ); return; } if (_selectedMacs.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Выберите хотя бы одно устройство')), ); return; } setState(() => _saving = true); try { await ref.read(apiProvider).createGroup(id, name, _selectedMacs.toList()); // Обновить список групп await ref.read(groupsProvider.notifier).refresh(); if (mounted) Navigator.of(context).pop(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ошибка создания: $e')), ); } } if (mounted) setState(() => _saving = false); } }