feat: harden geofence and distance diagnostics
This commit is contained in:
@@ -5,15 +5,28 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
|
||||
enum UserLocationIssue {
|
||||
servicesDisabled,
|
||||
permissionDenied,
|
||||
permissionDeniedForever,
|
||||
unavailable,
|
||||
}
|
||||
|
||||
/// Состояние геолокации: позиция или причина отсутствия.
|
||||
/// Запрашивается один раз, кешируется до перезапуска провайдера.
|
||||
class UserLocation {
|
||||
final Position? position;
|
||||
final String? error; // null -- всё ок, иначе причина
|
||||
final UserLocationIssue? issue;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
const UserLocation({this.position, this.error});
|
||||
const UserLocation({this.position, this.error, this.issue, this.updatedAt});
|
||||
|
||||
bool get hasPosition => position != null;
|
||||
bool get needsAppSettings =>
|
||||
issue == UserLocationIssue.permissionDeniedForever;
|
||||
bool get needsLocationSettings => issue == UserLocationIssue.servicesDisabled;
|
||||
bool get canRequestPermission => issue == UserLocationIssue.permissionDenied;
|
||||
|
||||
/// Расстояние в км до точки. Возвращает null если нет позиции
|
||||
/// или у цели нет координат.
|
||||
@@ -50,30 +63,7 @@ class UserLocationNotifier extends Notifier<UserLocation> {
|
||||
/// и отдаёт lastKnown мгновенно (если есть).
|
||||
Future<void> fetch() async {
|
||||
if (state.hasPosition) return;
|
||||
|
||||
final err = await _ensurePermission();
|
||||
if (err != null) {
|
||||
state = UserLocation(error: err);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final last = await Geolocator.getLastKnownPosition();
|
||||
if (last != null) {
|
||||
state = UserLocation(position: last);
|
||||
return;
|
||||
}
|
||||
|
||||
final pos = await Geolocator.getCurrentPosition(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.low,
|
||||
timeLimit: Duration(seconds: 10),
|
||||
),
|
||||
);
|
||||
state = UserLocation(position: pos);
|
||||
} catch (e) {
|
||||
state = UserLocation(error: 'Ошибка: $e');
|
||||
}
|
||||
await refresh();
|
||||
}
|
||||
|
||||
/// Начать непрерывное отслеживание. Вызывать из initState экрана.
|
||||
@@ -83,9 +73,9 @@ class UserLocationNotifier extends Notifier<UserLocation> {
|
||||
_watchers++;
|
||||
if (_sub != null) return;
|
||||
|
||||
final err = await _ensurePermission();
|
||||
if (err != null) {
|
||||
state = UserLocation(error: err);
|
||||
final permissionState = await _ensurePermission();
|
||||
if (!permissionState.isGranted) {
|
||||
state = permissionState.toLocation();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,7 +83,7 @@ class UserLocationNotifier extends Notifier<UserLocation> {
|
||||
try {
|
||||
final last = await Geolocator.getLastKnownPosition();
|
||||
if (last != null) {
|
||||
state = UserLocation(position: last);
|
||||
state = _fromPosition(last);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
@@ -104,9 +94,14 @@ class UserLocationNotifier extends Notifier<UserLocation> {
|
||||
);
|
||||
|
||||
_sub = Geolocator.getPositionStream(locationSettings: settings).listen(
|
||||
(pos) => state = UserLocation(position: pos),
|
||||
(pos) => state = _fromPosition(pos),
|
||||
onError: (e) {
|
||||
debugPrint('Ошибка стрима геолокации: $e');
|
||||
state = UserLocation(
|
||||
error: 'Не удалось отслеживать позицию: $e',
|
||||
issue: UserLocationIssue.unavailable,
|
||||
updatedAt: state.updatedAt,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -120,11 +115,61 @@ class UserLocationNotifier extends Notifier<UserLocation> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
final permissionState = await _ensurePermission();
|
||||
if (!permissionState.isGranted) {
|
||||
state = permissionState.toLocation();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final last = await Geolocator.getLastKnownPosition();
|
||||
if (last != null) {
|
||||
state = _fromPosition(last);
|
||||
return;
|
||||
}
|
||||
|
||||
final pos = await Geolocator.getCurrentPosition(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.low,
|
||||
timeLimit: Duration(seconds: 10),
|
||||
),
|
||||
);
|
||||
state = _fromPosition(pos);
|
||||
} catch (e) {
|
||||
state = UserLocation(
|
||||
error: 'Не удалось получить позицию: $e',
|
||||
issue: UserLocationIssue.unavailable,
|
||||
updatedAt: state.updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> requestPermission() async {
|
||||
await Geolocator.requestPermission();
|
||||
if (_watchers > 0 && _sub == null) {
|
||||
await startWatching();
|
||||
return;
|
||||
}
|
||||
await refresh();
|
||||
}
|
||||
|
||||
Future<void> openAppSettings() async {
|
||||
await Geolocator.openAppSettings();
|
||||
}
|
||||
|
||||
Future<void> openLocationSettings() async {
|
||||
await Geolocator.openLocationSettings();
|
||||
}
|
||||
|
||||
/// Проверить сервис и пермишены. Возвращает null если всё ок,
|
||||
/// иначе строку с причиной ошибки.
|
||||
Future<String?> _ensurePermission() async {
|
||||
Future<_LocationPermissionState> _ensurePermission() async {
|
||||
if (!await Geolocator.isLocationServiceEnabled()) {
|
||||
return 'Геолокация выключена';
|
||||
return const _LocationPermissionState(
|
||||
issue: UserLocationIssue.servicesDisabled,
|
||||
message: 'Геолокация выключена',
|
||||
);
|
||||
}
|
||||
|
||||
var perm = await Geolocator.checkPermission();
|
||||
@@ -132,12 +177,35 @@ class UserLocationNotifier extends Notifier<UserLocation> {
|
||||
perm = await Geolocator.requestPermission();
|
||||
}
|
||||
if (perm == LocationPermission.denied) {
|
||||
return 'Нет разрешения';
|
||||
return const _LocationPermissionState(
|
||||
issue: UserLocationIssue.permissionDenied,
|
||||
message: 'Нет разрешения на геолокацию',
|
||||
);
|
||||
}
|
||||
if (perm == LocationPermission.deniedForever) {
|
||||
return 'Разрешение запрещено навсегда';
|
||||
return const _LocationPermissionState(
|
||||
issue: UserLocationIssue.permissionDeniedForever,
|
||||
message: 'Доступ к геолокации запрещён навсегда',
|
||||
);
|
||||
}
|
||||
return null;
|
||||
return const _LocationPermissionState();
|
||||
}
|
||||
|
||||
UserLocation _fromPosition(Position position) {
|
||||
return UserLocation(position: position, updatedAt: position.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
class _LocationPermissionState {
|
||||
final UserLocationIssue? issue;
|
||||
final String? message;
|
||||
|
||||
const _LocationPermissionState({this.issue, this.message});
|
||||
|
||||
bool get isGranted => issue == null;
|
||||
|
||||
UserLocation toLocation() {
|
||||
return UserLocation(error: message, issue: issue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user