feat: harden geofence and distance diagnostics
This commit is contained in:
226
lib/features/homes/providers/geofence_providers.dart
Normal file
226
lib/features/homes/providers/geofence_providers.dart
Normal file
@@ -0,0 +1,226 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
|
||||
import '../../../app/load_state.dart';
|
||||
import '../../../models/home_config.dart';
|
||||
import '../geofence_logic.dart';
|
||||
import '../models/geofence_diagnostics.dart';
|
||||
import '../models/geofence_runtime_state.dart';
|
||||
import '../services/geofence_notifications_service.dart';
|
||||
import '../services/geofence_runtime_store.dart';
|
||||
import 'homes_providers.dart';
|
||||
|
||||
final geofenceRuntimeStoreProvider = Provider((ref) => GeofenceRuntimeStore());
|
||||
|
||||
final geofenceNotificationsServiceProvider = Provider(
|
||||
(ref) => GeofenceNotificationsService(),
|
||||
);
|
||||
|
||||
final geofenceDiagnosticsProvider =
|
||||
NotifierProvider<
|
||||
GeofenceDiagnosticsNotifier,
|
||||
LoadState<GeofenceDiagnostics>
|
||||
>(GeofenceDiagnosticsNotifier.new);
|
||||
|
||||
class GeofenceDiagnosticsNotifier
|
||||
extends Notifier<LoadState<GeofenceDiagnostics>> {
|
||||
bool _refreshing = false;
|
||||
|
||||
@override
|
||||
LoadState<GeofenceDiagnostics> build() {
|
||||
return const LoadState.idle(GeofenceDiagnostics.initial());
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
if (_refreshing) return;
|
||||
_refreshing = true;
|
||||
|
||||
final previous = state.data;
|
||||
state = LoadState.loading(previous);
|
||||
|
||||
try {
|
||||
final currentHome = ref.read(currentHomeProvider);
|
||||
final runtime = await ref.read(geofenceRuntimeStoreProvider).load();
|
||||
|
||||
if (currentHome == null) {
|
||||
state = LoadState.data(
|
||||
GeofenceDiagnostics(
|
||||
activeHome: null,
|
||||
status: GeofenceStatusKind.noActiveHome,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: LocationPermission.denied,
|
||||
notificationsEnabled: true,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentHome.geofenceEnabled) {
|
||||
state = LoadState.data(
|
||||
GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.disabled,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: LocationPermission.denied,
|
||||
notificationsEnabled: true,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentHome.hasCoordinates) {
|
||||
state = LoadState.data(
|
||||
GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.missingCoordinates,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: LocationPermission.denied,
|
||||
notificationsEnabled: true,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final locationServicesEnabled =
|
||||
await Geolocator.isLocationServiceEnabled();
|
||||
final locationPermission = await Geolocator.checkPermission();
|
||||
final notificationsEnabled = await ref
|
||||
.read(geofenceNotificationsServiceProvider)
|
||||
.areNotificationsEnabled();
|
||||
|
||||
final diagnostics = _buildDiagnostics(
|
||||
currentHome: currentHome,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: locationServicesEnabled,
|
||||
locationPermission: locationPermission,
|
||||
notificationsEnabled: notificationsEnabled,
|
||||
);
|
||||
state = LoadState.data(diagnostics);
|
||||
} catch (error) {
|
||||
state = LoadState.error(
|
||||
previous,
|
||||
'Не удалось обновить состояние geofence: $error',
|
||||
);
|
||||
} finally {
|
||||
_refreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> requestLocationPermission() async {
|
||||
await Geolocator.requestPermission();
|
||||
await refresh();
|
||||
}
|
||||
|
||||
Future<void> requestBackgroundLocationPermission() async {
|
||||
final result = await Geolocator.requestPermission();
|
||||
if (!hasBackgroundLocationAccess(result)) {
|
||||
await Geolocator.openAppSettings();
|
||||
}
|
||||
await refresh();
|
||||
}
|
||||
|
||||
Future<void> requestNotificationPermission() async {
|
||||
await ref
|
||||
.read(geofenceNotificationsServiceProvider)
|
||||
.requestNotificationsPermission();
|
||||
await refresh();
|
||||
}
|
||||
|
||||
Future<void> openAppSettings() async {
|
||||
await Geolocator.openAppSettings();
|
||||
}
|
||||
|
||||
Future<void> openLocationSettings() async {
|
||||
await Geolocator.openLocationSettings();
|
||||
}
|
||||
|
||||
GeofenceDiagnostics _buildDiagnostics({
|
||||
required HomeConfig currentHome,
|
||||
required GeofenceRuntimeState runtime,
|
||||
required bool locationServicesEnabled,
|
||||
required LocationPermission locationPermission,
|
||||
required bool notificationsEnabled,
|
||||
}) {
|
||||
if (!locationServicesEnabled) {
|
||||
return GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.locationServicesDisabled,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: false,
|
||||
locationPermission: locationPermission,
|
||||
notificationsEnabled: notificationsEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasForegroundLocationAccess(locationPermission)) {
|
||||
return GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.locationPermissionDenied,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: locationPermission,
|
||||
notificationsEnabled: notificationsEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasBackgroundLocationAccess(locationPermission)) {
|
||||
return GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.backgroundPermissionDenied,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: locationPermission,
|
||||
notificationsEnabled: notificationsEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
if (!notificationsEnabled) {
|
||||
return GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.notificationsPermissionDenied,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: locationPermission,
|
||||
notificationsEnabled: false,
|
||||
);
|
||||
}
|
||||
|
||||
final failureAt = runtime.failureAtFor(currentHome.id);
|
||||
final retryRemaining = geofenceRetryRemaining(failureAt);
|
||||
if (retryRemaining != null) {
|
||||
return GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.cooldown,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: locationPermission,
|
||||
notificationsEnabled: true,
|
||||
retryRemaining: retryRemaining,
|
||||
detail: runtime.failureMessageFor(currentHome.id),
|
||||
);
|
||||
}
|
||||
|
||||
if (runtime.isTriggeredFor(currentHome.id)) {
|
||||
return GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.triggered,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: locationPermission,
|
||||
notificationsEnabled: true,
|
||||
);
|
||||
}
|
||||
|
||||
return GeofenceDiagnostics(
|
||||
activeHome: currentHome,
|
||||
status: GeofenceStatusKind.ready,
|
||||
runtime: runtime,
|
||||
locationServicesEnabled: true,
|
||||
locationPermission: locationPermission,
|
||||
notificationsEnabled: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user