Files
ignis_app/lib/screens/home_edit_screen.dart
2026-04-22 21:08:02 +07:00

289 lines
9.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/home_config.dart';
import '../providers/providers.dart';
/// Экран создания или редактирования "дома" (сервера Ignis).
class HomeEditScreen extends ConsumerStatefulWidget {
final HomeConfig? home; // null -- создание, иначе редактирование
const HomeEditScreen({super.key, this.home});
@override
ConsumerState<HomeEditScreen> createState() => _HomeEditScreenState();
}
class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
final _nameCtrl = TextEditingController();
final _urlCtrl = TextEditingController();
final _keyCtrl = TextEditingController();
final _latCtrl = TextEditingController();
final _lonCtrl = TextEditingController();
bool _geofenceEnabled = false;
bool _saving = false;
bool get _isEdit => widget.home != null;
/// Координаты заполнены (оба поля непустые)
bool get _hasCoordinates =>
_latCtrl.text.trim().isNotEmpty && _lonCtrl.text.trim().isNotEmpty;
@override
void initState() {
super.initState();
if (_isEdit) {
_nameCtrl.text = widget.home!.name;
_urlCtrl.text = widget.home!.url;
_keyCtrl.text = widget.home!.apiKey;
if (widget.home!.latitude != null) {
_latCtrl.text = widget.home!.latitude.toString();
}
if (widget.home!.longitude != null) {
_lonCtrl.text = widget.home!.longitude.toString();
}
_geofenceEnabled = widget.home!.geofenceEnabled;
}
// Следим за полями координат чтобы обновлять доступность Switch
_latCtrl.addListener(_onCoordsChanged);
_lonCtrl.addListener(_onCoordsChanged);
}
void _onCoordsChanged() {
// Если координаты очистили -- выключаем геофенс
if (!_hasCoordinates && _geofenceEnabled) {
setState(() => _geofenceEnabled = false);
} else {
setState(() {}); // перерисовать Switch enabled/disabled
}
}
@override
void dispose() {
_latCtrl.removeListener(_onCoordsChanged);
_lonCtrl.removeListener(_onCoordsChanged);
_nameCtrl.dispose();
_urlCtrl.dispose();
_keyCtrl.dispose();
_latCtrl.dispose();
_lonCtrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_isEdit ? 'РЕДАКТИРОВАТЬ ДОМ' : 'НОВЫЙ ДОМ'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _nameCtrl,
decoration: const InputDecoration(
labelText: 'Название (например "Квартира")',
prefixIcon: Icon(Icons.home),
),
textCapitalization: TextCapitalization.sentences,
),
const SizedBox(height: 12),
TextField(
controller: _urlCtrl,
decoration: const InputDecoration(
labelText: 'Адрес сервера (например ignis.akokos.ru)',
prefixIcon: Icon(Icons.dns),
),
keyboardType: TextInputType.url,
),
const SizedBox(height: 12),
TextField(
controller: _keyCtrl,
decoration: const InputDecoration(
labelText: 'API Key',
prefixIcon: Icon(Icons.key),
),
obscureText: true,
),
const SizedBox(height: 24),
// ─── GPS-координаты (опционально) ───
const Text(
'Координаты дома (опционально)',
style: TextStyle(
color: Colors.white54,
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
const Text(
'Для автоматизации по геолокации',
style: TextStyle(color: Colors.white30, fontSize: 12),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: TextField(
controller: _latCtrl,
decoration: const InputDecoration(
labelText: 'Широта',
prefixIcon: Icon(Icons.location_on, size: 20),
hintText: '51.128',
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true, signed: true),
),
),
const SizedBox(width: 12),
Expanded(
child: TextField(
controller: _lonCtrl,
decoration: const InputDecoration(
labelText: 'Долгота',
prefixIcon: Icon(Icons.location_on, size: 20),
hintText: '71.430',
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true, signed: true),
),
),
],
),
const SizedBox(height: 16),
// ─── Геофенс ───
SwitchListTile(
title: const Text('Выключать свет при уходе'),
subtitle: Text(
_hasCoordinates
? 'Автовыключение при удалении на 500 м'
: 'Задайте координаты для активации',
style: TextStyle(
fontSize: 12,
color: _hasCoordinates ? Colors.white38 : Colors.white24,
),
),
value: _geofenceEnabled,
activeThumbColor: Colors.deepOrange,
onChanged: _hasCoordinates
? (v) => setState(() => _geofenceEnabled = v)
: null,
contentPadding: EdgeInsets.zero,
secondary: Icon(
Icons.directions_walk,
color: _geofenceEnabled && _hasCoordinates
? Colors.deepOrange
: Colors.white24,
),
),
if (_geofenceEnabled && _hasCoordinates)
const Padding(
padding: EdgeInsets.only(left: 40, bottom: 4),
child: Text(
'Проверка раз в ~15 мин (ограничение Android).\n'
'Работает в фоне, без постоянной нотификации.',
style: TextStyle(fontSize: 11, color: Colors.white24),
),
),
const SizedBox(height: 24),
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,
),
)
: Text(_isEdit ? 'СОХРАНИТЬ' : 'ДОБАВИТЬ'),
),
),
// Отступ внизу для системных кнопок
SizedBox(height: MediaQuery.of(context).padding.bottom + 16),
],
),
),
);
}
Future<void> _save() async {
final name = _nameCtrl.text.trim();
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
final latText = _latCtrl.text.trim();
final lonText = _lonCtrl.text.trim();
if (name.isEmpty || url.isEmpty || key.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Заполните все обязательные поля')),
);
return;
}
double? lat;
double? lon;
if (latText.isNotEmpty && lonText.isNotEmpty) {
lat = double.tryParse(latText);
lon = double.tryParse(lonText);
if (lat == null || lon == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Некорректные координаты')),
);
return;
}
}
setState(() => _saving = true);
final clearCoords = latText.isEmpty && lonText.isEmpty;
final home = _isEdit
? widget.home!.copyWith(
name: name,
url: url,
apiKey: key,
latitude: lat,
longitude: lon,
geofenceEnabled: clearCoords ? false : _geofenceEnabled,
clearCoordinates: clearCoords,
)
: HomeConfig(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: name,
url: url,
apiKey: key,
latitude: lat,
longitude: lon,
geofenceEnabled: _geofenceEnabled,
);
if (_isEdit) {
await ref.read(homesProvider.notifier).update(home);
} else {
await ref.read(homesProvider.notifier).add(home);
}
// Синхронизировать фоновый таск с новыми настройками
final allHomes = ref.read(homesProvider);
await syncGeofenceTask(allHomes);
if (mounted) Navigator.of(context).pop();
}
}