Extract settings and harden geofence automation

This commit is contained in:
Artem Kokos
2026-05-15 10:18:46 +07:00
parent 1963488479
commit d796537917
21 changed files with 1392 additions and 278 deletions

View File

@@ -22,8 +22,6 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
final _keyCtrl = TextEditingController();
final _latCtrl = TextEditingController();
final _lonCtrl = TextEditingController();
final _radiusCtrl = TextEditingController();
bool _geofenceEnabled = false;
bool _saving = false;
bool get _isEdit => widget.home != null;
@@ -44,14 +42,10 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
if (widget.home!.longitude != null) {
_lonCtrl.text = widget.home!.longitude.toString();
}
_radiusCtrl.text = widget.home!.geofenceRadiusMeters.toString();
_geofenceEnabled = widget.home!.geofenceEnabled;
_loadApiKey();
} else {
_radiusCtrl.text = HomeConfig.defaultGeofenceRadiusMeters.toString();
}
// Следим за полями координат чтобы обновлять доступность Switch
// Следим за полями координат, чтобы обновлять подсказки экрана.
_latCtrl.addListener(_onCoordsChanged);
_lonCtrl.addListener(_onCoordsChanged);
}
@@ -66,12 +60,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
}
void _onCoordsChanged() {
// Если координаты очистили -- выключаем геофенс
if (!_hasCoordinates && _geofenceEnabled) {
setState(() => _geofenceEnabled = false);
} else {
setState(() {}); // перерисовать Switch enabled/disabled
}
setState(() {});
}
@override
@@ -83,7 +72,6 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
_keyCtrl.dispose();
_latCtrl.dispose();
_lonCtrl.dispose();
_radiusCtrl.dispose();
super.dispose();
}
@@ -229,64 +217,17 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
),
],
),
const SizedBox(height: 12),
TextFormField(
controller: _radiusCtrl,
decoration: const InputDecoration(
labelText: 'Радиус geofence, м',
hintText: '500',
helperText: 'Автовыключение сработает после выхода за этот радиус',
prefixIcon: Icon(Icons.radar),
),
keyboardType: TextInputType.number,
validator: (value) {
final normalized = value?.trim() ?? '';
final radius = int.tryParse(normalized);
if (radius == null) {
return 'Введите радиус в метрах';
}
if (radius < 100 || radius > 5000) {
return 'От 100 до 5000 м';
}
return null;
},
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text('Выключать свет при уходе'),
subtitle: Text(
_hasCoordinates
? 'Автовыключение после выхода за радиус geofence'
: 'Задайте координаты для активации',
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)
if (_hasCoordinates)
const Padding(
padding: EdgeInsets.only(left: 40, bottom: 4),
padding: EdgeInsets.only(bottom: 24),
child: Text(
'Работает только для текущего активного дома.\n'
'Использует системный Android geofence, а не polling.\n'
'Нужны фоновые разрешения на геолокацию.',
style: TextStyle(fontSize: 11, color: Colors.white24),
'Geofence и радиус настраиваются отдельно на экране настроек.',
style: TextStyle(fontSize: 12, color: Colors.white38),
),
),
const SizedBox(height: 24),
)
else
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 48,
@@ -327,9 +268,8 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
final key = _keyCtrl.text.trim();
final latText = _latCtrl.text.trim();
final lonText = _lonCtrl.text.trim();
final radiusText = _radiusCtrl.text.trim();
if (name.isEmpty || rawUrl.isEmpty || key.isEmpty || radiusText.isEmpty) {
if (name.isEmpty || rawUrl.isEmpty || key.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Заполните все обязательные поля')),
);
@@ -376,14 +316,6 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
}
}
final radiusMeters = int.tryParse(radiusText);
if (radiusMeters == null || radiusMeters < 100 || radiusMeters > 5000) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Радиус geofence должен быть от 100 до 5000 м')),
);
return;
}
setState(() => _saving = true);
final clearCoords = latText.isEmpty && lonText.isEmpty;
@@ -394,8 +326,6 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
url: url,
latitude: lat,
longitude: lon,
geofenceEnabled: clearCoords ? false : _geofenceEnabled,
geofenceRadiusMeters: radiusMeters,
clearCoordinates: clearCoords,
)
: HomeConfig(
@@ -404,8 +334,6 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
url: url,
latitude: lat,
longitude: lon,
geofenceEnabled: _geofenceEnabled,
geofenceRadiusMeters: radiusMeters,
);
try {