Replace geofence polling with native Android geofence

This commit is contained in:
Artem Kokos
2026-05-12 11:23:44 +07:00
parent 0a5ef9af17
commit 1963488479
38 changed files with 1099 additions and 1931 deletions

View File

@@ -22,6 +22,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
final _keyCtrl = TextEditingController();
final _latCtrl = TextEditingController();
final _lonCtrl = TextEditingController();
final _radiusCtrl = TextEditingController();
bool _geofenceEnabled = false;
bool _saving = false;
@@ -43,8 +44,11 @@ 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
@@ -79,6 +83,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
_keyCtrl.dispose();
_latCtrl.dispose();
_lonCtrl.dispose();
_radiusCtrl.dispose();
super.dispose();
}
@@ -224,12 +229,34 @@ 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
? 'Автовыключение при удалении на 500 м'
? 'Автовыключение после выхода за радиус geofence'
: 'Задайте координаты для активации',
style: TextStyle(
fontSize: 12,
@@ -253,9 +280,9 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
const Padding(
padding: EdgeInsets.only(left: 40, bottom: 4),
child: Text(
'Проверка раз в ~15 мин (ограничение Android).\n'
'Работает только для текущего активного дома.\n'
'Нужны фоновые разрешения на геолокацию и уведомления.',
'Использует системный Android geofence, а не polling.\n'
'Нужны фоновые разрешения на геолокацию.',
style: TextStyle(fontSize: 11, color: Colors.white24),
),
),
@@ -300,8 +327,9 @@ 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) {
if (name.isEmpty || rawUrl.isEmpty || key.isEmpty || radiusText.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Заполните все обязательные поля')),
);
@@ -348,6 +376,14 @@ 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;
@@ -359,6 +395,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
latitude: lat,
longitude: lon,
geofenceEnabled: clearCoords ? false : _geofenceEnabled,
geofenceRadiusMeters: radiusMeters,
clearCoordinates: clearCoords,
)
: HomeConfig(
@@ -368,6 +405,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
latitude: lat,
longitude: lon,
geofenceEnabled: _geofenceEnabled,
geofenceRadiusMeters: radiusMeters,
);
try {
@@ -384,13 +422,6 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
await ref.read(currentHomeProvider.notifier).select(home);
}
// Синхронизировать фоновый таск с новыми настройками
final allHomes = ref.read(homesProvider);
await syncGeofenceTask(
allHomes,
currentHome: ref.read(currentHomeProvider),
);
if (mounted) Navigator.of(context).pop();
} catch (e) {
if (mounted) {