feat: harden geofence and distance diagnostics

This commit is contained in:
Artem Kokos
2026-05-01 09:13:23 +07:00
parent 872ddf9513
commit 91a494adf5
20 changed files with 1639 additions and 260 deletions

View File

@@ -0,0 +1,123 @@
import 'package:geolocator/geolocator.dart';
import '../../../models/home_config.dart';
import '../geofence_logic.dart';
import 'geofence_runtime_state.dart';
enum GeofenceStatusKind {
noActiveHome,
disabled,
missingCoordinates,
locationServicesDisabled,
locationPermissionDenied,
backgroundPermissionDenied,
notificationsPermissionDenied,
cooldown,
triggered,
ready,
}
class GeofenceDiagnostics {
final HomeConfig? activeHome;
final GeofenceStatusKind status;
final GeofenceRuntimeState runtime;
final bool locationServicesEnabled;
final LocationPermission locationPermission;
final bool notificationsEnabled;
final Duration? retryRemaining;
final String? detail;
const GeofenceDiagnostics({
required this.activeHome,
required this.status,
required this.runtime,
required this.locationServicesEnabled,
required this.locationPermission,
required this.notificationsEnabled,
this.retryRemaining,
this.detail,
});
const GeofenceDiagnostics.initial()
: activeHome = null,
status = GeofenceStatusKind.noActiveHome,
runtime = const GeofenceRuntimeState(),
locationServicesEnabled = true,
locationPermission = LocationPermission.denied,
notificationsEnabled = true,
retryRemaining = null,
detail = null;
String get title {
return switch (status) {
GeofenceStatusKind.noActiveHome => 'Геофенс не активен',
GeofenceStatusKind.disabled => 'Автовыключение выключено',
GeofenceStatusKind.missingCoordinates => 'Нет координат дома',
GeofenceStatusKind.locationServicesDisabled => 'Геолокация выключена',
GeofenceStatusKind.locationPermissionDenied => 'Нет доступа к геолокации',
GeofenceStatusKind.backgroundPermissionDenied =>
'Нет фонового доступа к геолокации',
GeofenceStatusKind.notificationsPermissionDenied =>
'Нет доступа к уведомлениям',
GeofenceStatusKind.cooldown => 'Повтор отложен',
GeofenceStatusKind.triggered => 'Геофенс уже сработал',
GeofenceStatusKind.ready => 'Геофенс активен',
};
}
String get message {
final homeName = activeHome?.name ?? 'активного дома';
return switch (status) {
GeofenceStatusKind.noActiveHome =>
'Сначала выберите активный дом. Без этого фоновой автоматике нечего отслеживать.',
GeofenceStatusKind.disabled =>
'Для дома "$homeName" автовыключение пока отключено.',
GeofenceStatusKind.missingCoordinates =>
'У дома "$homeName" не заданы координаты. Без них расстояние считать не из чего.',
GeofenceStatusKind.locationServicesDisabled =>
'На устройстве выключена геолокация. И карта расстояний, и фоновый geofence сейчас слепые как кроты.',
GeofenceStatusKind.locationPermissionDenied =>
'Приложению не дали доступ к геолокации. Разрешите доступ хотя бы во время использования.',
GeofenceStatusKind.backgroundPermissionDenied =>
'Для фонового geofence нужен доступ к локации "Всегда". Иначе в фоне Android эту магию задушит.',
GeofenceStatusKind.notificationsPermissionDenied =>
'Свет выключить мы ещё попробуем, но честно отчитаться пользователю не сможем, пока уведомления запрещены.',
GeofenceStatusKind.cooldown =>
'Последняя попытка выключить свет сорвалась. Повторим позже, чтобы не долбить backend без остановки.',
GeofenceStatusKind.triggered =>
'Свет уже был автоматически выключен. Повторно бахать не будем, пока вы не вернётесь в домашний радиус.',
GeofenceStatusKind.ready =>
'Фоновая автоматика готова следить за домом "$homeName" и выключить свет после ухода.',
};
}
String? get secondaryMessage {
if (status == GeofenceStatusKind.cooldown && retryRemaining != null) {
return 'Повтор через ${formatGeofenceRetry(retryRemaining!)}.';
}
if (status == GeofenceStatusKind.triggered &&
runtime.lastSuccessAt != null &&
runtime.lastSuccessHomeId == activeHome?.id) {
return 'Последнее успешное срабатывание уже зафиксировано.';
}
if (detail != null && detail!.trim().isNotEmpty) {
return detail;
}
return null;
}
bool get canRequestLocation =>
status == GeofenceStatusKind.locationPermissionDenied;
bool get canRequestBackgroundLocation =>
status == GeofenceStatusKind.backgroundPermissionDenied;
bool get canOpenLocationSettings =>
status == GeofenceStatusKind.locationServicesDisabled;
bool get canRequestNotifications =>
status == GeofenceStatusKind.notificationsPermissionDenied;
}