import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../features/provisioning/models/wiz_provisioning_environment.dart'; import '../features/provisioning/models/wiz_provisioning_state.dart'; import '../features/provisioning/providers/wiz_provisioning_providers.dart'; class WizProvisioningScreen extends ConsumerStatefulWidget { const WizProvisioningScreen({super.key}); @override ConsumerState createState() => _WizProvisioningScreenState(); } class _WizProvisioningScreenState extends ConsumerState with WidgetsBindingObserver { final _formKey = GlobalKey(); final _ssidCtrl = TextEditingController(); final _bssidCtrl = TextEditingController(); final _passwordCtrl = TextEditingController(); bool _ssidTouched = false; bool _bssidTouched = false; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); Future.microtask( () => ref.read(wizProvisioningProvider.notifier).initialize(), ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); ref .read(wizProvisioningProvider.notifier) .cancelProvisioning(keepCurrentState: false); ref.invalidate(wizProvisioningProvider); _ssidCtrl.dispose(); _bssidCtrl.dispose(); _passwordCtrl.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { Future.microtask( () => ref.read(wizProvisioningProvider.notifier).initialize(), ); } } @override Widget build(BuildContext context) { final provisioningState = ref.watch(wizProvisioningProvider); _syncControllers(provisioningState.environment); final bottomInset = MediaQuery.paddingOf(context).bottom; final environment = provisioningState.environment; final failure = provisioningState.failure; final canRequestPermissions = !environment.permissionsGranted && environment.permissionRequestable; final canOpenAppSettings = environment.requiresAppSettings && environment.appSettingsSupported; final needsWifiSettings = !environment.connectedToWifi && environment.wifiSettingsSupported; return Scaffold( appBar: AppBar(title: const Text('ПОДКЛЮЧЕНИЕ WIZ')), body: SafeArea( top: false, bottom: true, child: RefreshIndicator( color: Colors.deepOrange, onRefresh: () => ref.read(wizProvisioningProvider.notifier).initialize(), child: ListView( padding: EdgeInsets.fromLTRB(16, 16, 16, bottomInset + 24), children: [ _SectionCard( title: 'Что делает мастер', child: const Text( 'Эта версия использует smart pairing: телефон остаётся в домашней Wi-Fi сети и передаёт её настройки новой лампе. Это Android-only поток и он лучше всего работает, когда телефон уже сидит на 2.4 GHz.', style: TextStyle(color: Colors.white70), ), ), _SectionCard( title: 'Активный дом', child: Text( provisioningState.activeHomeName == null ? 'Не выбран' : provisioningState.activeHomeName!, style: TextStyle( color: provisioningState.activeHomeName == null ? Colors.redAccent : Colors.white, fontWeight: FontWeight.w600, ), ), ), _SectionCard( title: 'Окружение', child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _InfoRow( label: 'Платформа', value: environment.isAndroid ? 'Android ${environment.androidApiLevel ?? '?'}' : environment.platform, ), _InfoRow( label: 'Разрешения', value: _permissionStatusLabel( environment.permissionStatus, ), ), _InfoRow( label: 'Wi-Fi', value: environment.connectedToWifi ? (environment.ssid ?? 'Подключено') : 'Нет подключения', ), _InfoRow( label: 'BSSID', value: environment.bssid ?? 'Не удалось определить', ), _InfoRow( label: 'Диапазон', value: environment.frequencyMhz == null ? 'Неизвестно' : '${environment.frequencyMhz} MHz', ), if (environment.isLikelyOn5Ghz) const Padding( padding: EdgeInsets.only(top: 8), child: Text( 'Сейчас похоже активен 5 GHz. Для WiZ лучше заранее переключиться на 2.4 GHz.', style: TextStyle(color: Colors.amberAccent), ), ), if (!environment.locationServicesEnabled) const Padding( padding: EdgeInsets.only(top: 8), child: Text( 'На Android системная геолокация должна быть включена, иначе SSID/BSSID часто скрываются системой.', style: TextStyle(color: Colors.amberAccent), ), ), ], ), ), if (failure != null) _SectionCard( title: 'Проблема', child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( failure.message, style: const TextStyle(color: Colors.redAccent), ), if (failure.details != null) ...[ const SizedBox(height: 8), Text( failure.details!, style: const TextStyle(color: Colors.white54), ), ], ], ), ), if (provisioningState.notice != null) _SectionCard( title: 'Примечание', child: Text( provisioningState.notice!, style: const TextStyle(color: Colors.white70), ), ), _SectionCard( title: 'Шаги перед стартом', child: const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '1. Убедитесь, что телефон подключён к домашней 2.4 GHz Wi-Fi.', style: TextStyle(color: Colors.white70), ), SizedBox(height: 6), Text( '2. Переведите лампу в pairing mode: если нужно, несколько раз выключите и включите питание до пульсации.', style: TextStyle(color: Colors.white70), ), SizedBox(height: 6), Text( '3. Держите телефон рядом с лампой и не сворачивайте приложение до конца pairing.', style: TextStyle(color: Colors.white70), ), ], ), ), _SectionCard( title: 'Домашняя Wi-Fi', child: Form( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, child: Column( children: [ TextFormField( controller: _ssidCtrl, decoration: const InputDecoration( labelText: 'SSID', hintText: 'Например: Home-2G', prefixIcon: Icon(Icons.wifi), ), onChanged: (_) => _ssidTouched = true, validator: (value) { if ((value?.trim().isEmpty ?? true)) { return 'Укажите SSID'; } return null; }, ), const SizedBox(height: 12), TextFormField( controller: _bssidCtrl, decoration: const InputDecoration( labelText: 'BSSID (опционально)', hintText: 'aa:bb:cc:dd:ee:ff', prefixIcon: Icon(Icons.router_outlined), ), onChanged: (_) => _bssidTouched = true, ), const SizedBox(height: 12), TextFormField( controller: _passwordCtrl, decoration: const InputDecoration( labelText: 'Пароль Wi-Fi', hintText: 'Оставьте пустым для открытой сети', prefixIcon: Icon(Icons.key), ), obscureText: true, ), ], ), ), ), _ActionSection( state: provisioningState, canRequestPermissions: canRequestPermissions, canOpenAppSettings: canOpenAppSettings, needsWifiSettings: needsWifiSettings, onRequestPermissions: () => ref .read(wizProvisioningProvider.notifier) .requestPermissions(), onOpenAppSettings: () => ref .read(wizProvisioningProvider.notifier) .openAppSettings(), onOpenWifiSettings: () => ref .read(wizProvisioningProvider.notifier) .openWifiSettings(), onRefresh: () => ref.read(wizProvisioningProvider.notifier).initialize(), onStart: _startProvisioning, onCancel: () => ref .read(wizProvisioningProvider.notifier) .cancelProvisioning(), ), if (provisioningState.provisionedDevices.isNotEmpty) _SectionCard( title: 'Ответившие устройства', child: Column( children: [ for (final device in provisioningState.provisionedDevices) ListTile( dense: true, contentPadding: EdgeInsets.zero, leading: const Icon( Icons.lightbulb_outline, color: Colors.deepOrange, ), title: Text(device.bssid), subtitle: device.ipAddress == null ? null : Text(device.ipAddress!), ), ], ), ), if (provisioningState.rescanSummary != null) _SectionCard( title: 'Результат discovery', child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _InfoRow( label: 'Найдено', value: '${provisioningState.rescanSummary!.found}', ), _InfoRow( label: 'Добавлено', value: '${provisioningState.rescanSummary!.added}', ), _InfoRow( label: 'Обновлено', value: '${provisioningState.rescanSummary!.updated}', ), _InfoRow( label: 'Онлайн', value: '${provisioningState.rescanSummary!.online}', ), ], ), ), if (provisioningState.timeline.isNotEmpty) _SectionCard( title: 'Ход выполнения', child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ for (final event in provisioningState.timeline) Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( '• $event', style: const TextStyle(color: Colors.white60), ), ), ], ), ), ], ), ), ), ); } void _syncControllers(WizProvisioningEnvironment environment) { if (!_ssidTouched && environment.ssid != null && environment.ssid != _ssidCtrl.text) { _ssidCtrl.text = environment.ssid!; } if (!_bssidTouched && environment.bssid != null && environment.bssid != _bssidCtrl.text) { _bssidCtrl.text = environment.bssid!; } } Future _startProvisioning() async { if (!_formKey.currentState!.validate()) { return; } await ref .read(wizProvisioningProvider.notifier) .startProvisioning( ssid: _ssidCtrl.text, password: _passwordCtrl.text, bssid: _bssidCtrl.text.trim().isEmpty ? null : _bssidCtrl.text, ); } String _permissionStatusLabel(WizProvisioningPermissionStatus status) { switch (status) { case WizProvisioningPermissionStatus.granted: return 'Выданы'; case WizProvisioningPermissionStatus.requestable: return 'Нужно запросить'; case WizProvisioningPermissionStatus.settingsRequired: return 'Нужно открыть настройки приложения'; } } } class _ActionSection extends StatelessWidget { final WizProvisioningState state; final bool canRequestPermissions; final bool canOpenAppSettings; final bool needsWifiSettings; final VoidCallback onRequestPermissions; final VoidCallback onOpenAppSettings; final VoidCallback onOpenWifiSettings; final VoidCallback onRefresh; final VoidCallback onStart; final VoidCallback onCancel; const _ActionSection({ required this.state, required this.canRequestPermissions, required this.canOpenAppSettings, required this.needsWifiSettings, required this.onRequestPermissions, required this.onOpenAppSettings, required this.onOpenWifiSettings, required this.onRefresh, required this.onStart, required this.onCancel, }); @override Widget build(BuildContext context) { final canStartProvisioning = !state.isBusy && state.activeHomeName != null && state.environment.permissionsGranted && state.environment.locationServicesEnabled && state.environment.connectedToWifi && state.status != WizProvisioningStatus.unsupported; return _SectionCard( title: 'Действия', child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (state.isBusy) const Padding( padding: EdgeInsets.only(bottom: 12), child: LinearProgressIndicator(color: Colors.deepOrange), ), FilledButton.icon( onPressed: canStartProvisioning ? onStart : null, icon: const Icon(Icons.flash_on), label: Text( state.status == WizProvisioningStatus.success ? 'Повторить pairing' : 'Запустить smart pairing', ), ), const SizedBox(height: 8), OutlinedButton.icon( onPressed: state.isBusy ? onCancel : onRefresh, icon: Icon( state.isBusy ? Icons.stop_circle_outlined : Icons.refresh, ), label: Text(state.isBusy ? 'Остановить' : 'Переобновить окружение'), ), if (canRequestPermissions) ...[ const SizedBox(height: 8), OutlinedButton.icon( onPressed: state.isBusy ? null : onRequestPermissions, icon: const Icon(Icons.privacy_tip_outlined), label: const Text('Выдать разрешения'), ), ], if (canOpenAppSettings) ...[ const SizedBox(height: 8), OutlinedButton.icon( onPressed: state.isBusy ? null : onOpenAppSettings, icon: const Icon(Icons.settings_applications_outlined), label: const Text('Открыть настройки приложения'), ), ], if (needsWifiSettings) ...[ const SizedBox(height: 8), OutlinedButton.icon( onPressed: state.isBusy ? null : onOpenWifiSettings, icon: const Icon(Icons.wifi_find_outlined), label: const Text('Открыть настройки Wi-Fi'), ), ], ], ), ); } } class _SectionCard extends StatelessWidget { final String title; final Widget child; const _SectionCard({required this.title, required this.child}); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.only(bottom: 12), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 12), child, ], ), ), ); } } class _InfoRow extends StatelessWidget { final String label; final String value; const _InfoRow({required this.label, required this.value}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 112, child: Text(label, style: const TextStyle(color: Colors.white38)), ), Expanded( child: Text(value, style: const TextStyle(color: Colors.white70)), ), ], ), ); } }