Compare commits
7 Commits
6ed36dba75
...
e130f4a037
| Author | SHA1 | Date | |
|---|---|---|---|
| e130f4a037 | |||
| 5c30dd9224 | |||
| 836aea707a | |||
| e0e7815ad8 | |||
| e0dde50eba | |||
| 54ddf68c22 | |||
| b5bba037f1 |
@@ -73,6 +73,7 @@ class _ActivityMeterBody extends ConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
firstDate: now.subtract(const Duration(days: 365)),
|
firstDate: now.subtract(const Duration(days: 365)),
|
||||||
lastDate: now,
|
lastDate: now,
|
||||||
|
builder: legacyPickerThemeBuilder,
|
||||||
);
|
);
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
notifier.selectCustomRange(picked.start, picked.end);
|
notifier.selectCustomRange(picked.start, picked.end);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:design_system/design_system.dart';
|
|||||||
import 'package:device_management/src/core/domain/entities/app_usage_schedule_entity.dart';
|
import 'package:device_management/src/core/domain/entities/app_usage_schedule_entity.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:legacy_theme/legacy_theme.dart';
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
|
import 'package:legacy_ui/legacy_ui.dart';
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
import 'package:utils/utils.dart';
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ class _EditSchedulePeriodSheetState extends State<_EditSchedulePeriodSheet> {
|
|||||||
final picked = await showTimePicker(
|
final picked = await showTimePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialTime: isStart ? _start : _end,
|
initialTime: isStart ? _start : _end,
|
||||||
|
builder: legacyPickerThemeBuilder,
|
||||||
);
|
);
|
||||||
if (picked == null) return;
|
if (picked == null) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class AppsUseScreen extends ConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
firstDate: now.subtract(const Duration(days: 365)),
|
firstDate: now.subtract(const Duration(days: 365)),
|
||||||
lastDate: now,
|
lastDate: now,
|
||||||
|
builder: legacyPickerThemeBuilder,
|
||||||
);
|
);
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
notifier.selectCustomRange(picked.start, picked.end);
|
notifier.selectCustomRange(picked.start, picked.end);
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ class _FilterBar extends StatelessWidget {
|
|||||||
? FontWeight.w600
|
? FontWeight.w600
|
||||||
: FontWeight.w500,
|
: FontWeight.w500,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Colors.black87
|
? Theme.of(context).colorScheme.onSurface
|
||||||
: Theme.of(context)
|
: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSurfaceVariant,
|
.onSurfaceVariant,
|
||||||
@@ -324,7 +324,7 @@ class _CallTile extends StatelessWidget {
|
|||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: isAccepted
|
color: isAccepted
|
||||||
? Colors.black87
|
? Theme.of(context).colorScheme.onSurface
|
||||||
: Theme.of(context).colorScheme.error,
|
: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class _CallWatchDialogState extends ConsumerState<CallWatchDialog> {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
color: Colors.black54,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
|
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:design_system/design_system.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:legacy_theme/legacy_theme.dart';
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
|
import 'package:legacy_ui/legacy_ui.dart';
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
import 'package:utils/utils.dart';
|
import 'package:utils/utils.dart';
|
||||||
|
|
||||||
@@ -110,6 +111,7 @@ class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
|
|||||||
final picked = await showTimePicker(
|
final picked = await showTimePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialTime: start,
|
initialTime: start,
|
||||||
|
builder: legacyPickerThemeBuilder,
|
||||||
);
|
);
|
||||||
if (picked != null) _start.value = picked;
|
if (picked != null) _start.value = picked;
|
||||||
},
|
},
|
||||||
@@ -128,6 +130,7 @@ class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
|
|||||||
final picked = await showTimePicker(
|
final picked = await showTimePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialTime: end,
|
initialTime: end,
|
||||||
|
builder: legacyPickerThemeBuilder,
|
||||||
);
|
);
|
||||||
if (picked != null) _end.value = picked;
|
if (picked != null) _end.value = picked;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class _HealthScreenState extends ConsumerState<HealthScreen>
|
|||||||
context: context,
|
context: context,
|
||||||
firstDate: now.subtract(const Duration(days: 365)),
|
firstDate: now.subtract(const Duration(days: 365)),
|
||||||
lastDate: now,
|
lastDate: now,
|
||||||
|
builder: legacyPickerThemeBuilder,
|
||||||
);
|
);
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
notifier.selectCustomRange(picked.start, picked.end);
|
notifier.selectCustomRange(picked.start, picked.end);
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class _SpyCallDialogState extends ConsumerState<SpyCallDialog> {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||||
color: Colors.black54,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
|
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:legacy_ui/legacy_ui.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:legacy_auth/src/features/device_setup/presentation/providers/device_setup_controller.dart';
|
import 'package:legacy_auth/src/features/device_setup/presentation/providers/device_setup_controller.dart';
|
||||||
import 'package:legacy_auth/src/features/device_setup/presentation/providers/device_setup_form_controllers.dart';
|
import 'package:legacy_auth/src/features/device_setup/presentation/providers/device_setup_form_controllers.dart';
|
||||||
@@ -21,6 +22,7 @@ Future<void> _pickBornAt(
|
|||||||
initialDate: safeInitial,
|
initialDate: safeInitial,
|
||||||
firstDate: DateTime(1900, 1, 1),
|
firstDate: DateTime(1900, 1, 1),
|
||||||
lastDate: now,
|
lastDate: now,
|
||||||
|
builder: legacyPickerThemeBuilder,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (picked != null) onPicked(picked);
|
if (picked != null) onPicked(picked);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ final class LocationControllerProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$locationControllerHash() =>
|
String _$locationControllerHash() =>
|
||||||
r'43534cd92b74ec5fabc9a43c6ef8398846855cf4';
|
r'324461865e3f1deb94ef6bba29cb3a200e5fe14f';
|
||||||
|
|
||||||
abstract class _$LocationController extends $AsyncNotifier<LocationState> {
|
abstract class _$LocationController extends $AsyncNotifier<LocationState> {
|
||||||
FutureOr<LocationState> build();
|
FutureOr<LocationState> build();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||||
@@ -15,12 +16,22 @@ class LocationMapController extends _$LocationMapController {
|
|||||||
late final SfTrackingRepository _tracking;
|
late final SfTrackingRepository _tracking;
|
||||||
|
|
||||||
Timer? _zoomDebounce;
|
Timer? _zoomDebounce;
|
||||||
|
Timer? _playbackTimer;
|
||||||
|
Timer? _recenterTimer;
|
||||||
static const Duration _zoomDebounceDelay = Duration(seconds: 1);
|
static const Duration _zoomDebounceDelay = Duration(seconds: 1);
|
||||||
|
static const int _playbackSeconds = 3;
|
||||||
|
static const int _recenterSeconds = 3;
|
||||||
|
|
||||||
|
VoidCallback? onPlaybackTick;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LocationMapState build() {
|
LocationMapState build() {
|
||||||
_tracking = ref.read(sfTrackingProvider);
|
_tracking = ref.read(sfTrackingProvider);
|
||||||
ref.onDispose(() => _zoomDebounce?.cancel());
|
ref.onDispose(() {
|
||||||
|
_zoomDebounce?.cancel();
|
||||||
|
_playbackTimer?.cancel();
|
||||||
|
_recenterTimer?.cancel();
|
||||||
|
});
|
||||||
return const LocationMapState();
|
return const LocationMapState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +67,8 @@ class LocationMapController extends _$LocationMapController {
|
|||||||
placingMode: PlacingMode.none,
|
placingMode: PlacingMode.none,
|
||||||
adjustingRadius: false,
|
adjustingRadius: false,
|
||||||
previewPoint: null,
|
previewPoint: null,
|
||||||
|
editingGeofence: null,
|
||||||
|
editingFrequentPlace: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,13 +88,17 @@ class LocationMapController extends _$LocationMapController {
|
|||||||
placingMode: PlacingMode.none,
|
placingMode: PlacingMode.none,
|
||||||
adjustingRadius: true,
|
adjustingRadius: true,
|
||||||
previewPoint: center,
|
previewPoint: center,
|
||||||
previewRadius: 200,
|
previewRadius: state.editingGeofence?.radius ?? 200,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void confirmFrequentPlacePlacement() {
|
void confirmFrequentPlacePlacement() {
|
||||||
unawaited(_tracking.legacyLocationPointConfirmed('frequent_place'));
|
unawaited(_tracking.legacyLocationPointConfirmed('frequent_place'));
|
||||||
state = state.copyWith(placingMode: PlacingMode.none, previewPoint: null);
|
state = state.copyWith(
|
||||||
|
placingMode: PlacingMode.none,
|
||||||
|
previewPoint: null,
|
||||||
|
editingFrequentPlace: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updatePreviewRadius(double radius) {
|
void updatePreviewRadius(double radius) {
|
||||||
@@ -123,9 +140,19 @@ class LocationMapController extends _$LocationMapController {
|
|||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
selectedGeofence: null,
|
selectedGeofence: null,
|
||||||
editingGeofence: geofence,
|
editingGeofence: geofence,
|
||||||
|
placingMode: PlacingMode.geofence,
|
||||||
previewPoint: LatLng(geofence.latitude, geofence.longitude),
|
previewPoint: LatLng(geofence.latitude, geofence.longitude),
|
||||||
previewRadius: geofence.radius,
|
previewRadius: geofence.radius,
|
||||||
adjustingRadius: true,
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startEditingFrequentPlace(FrequentPlaceEntity place) {
|
||||||
|
unawaited(_tracking.legacyLocationFrequentPlaceSelected());
|
||||||
|
state = state.copyWith(
|
||||||
|
selectedFrequentPlace: null,
|
||||||
|
editingFrequentPlace: place,
|
||||||
|
placingMode: PlacingMode.frequentPlace,
|
||||||
|
previewPoint: LatLng(place.lat, place.lng),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +218,96 @@ class LocationMapController extends _$LocationMapController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void startReveal() {
|
||||||
|
state = state.copyWith(isRevealAnimating: true, historyRevealCount: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateRevealCount(int count) {
|
||||||
|
state = state.copyWith(historyRevealCount: count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishReveal() {
|
||||||
|
state = state.copyWith(isRevealAnimating: false, historyRevealCount: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startHistoryNavigation() {
|
||||||
|
state = state.copyWith(historyNavigationIndex: 0, historyPlaying: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopHistoryNavigation() {
|
||||||
|
state = state.copyWith(historyNavigationIndex: -1, historyPlaying: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHistoryIndex(int index) {
|
||||||
|
state = state.copyWith(historyNavigationIndex: index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nextHistoryPosition(int total) {
|
||||||
|
if (state.historyNavigationIndex < total - 1) {
|
||||||
|
state = state.copyWith(
|
||||||
|
historyNavigationIndex: state.historyNavigationIndex + 1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state = state.copyWith(historyPlaying: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void previousHistoryPosition() {
|
||||||
|
if (state.historyNavigationIndex > 0) {
|
||||||
|
state = state.copyWith(
|
||||||
|
historyNavigationIndex: state.historyNavigationIndex - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleHistoryPlayback(int total) {
|
||||||
|
if (state.historyPlaying) {
|
||||||
|
stopPlaybackTimer();
|
||||||
|
state = state.copyWith(historyPlaying: false);
|
||||||
|
} else {
|
||||||
|
final atEnd = state.historyNavigationIndex >= total - 1;
|
||||||
|
state = state.copyWith(
|
||||||
|
historyNavigationIndex: atEnd ? 0 : state.historyNavigationIndex,
|
||||||
|
historyPlaying: true,
|
||||||
|
);
|
||||||
|
startPlaybackTimer(total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void startPlaybackTimer(int total) {
|
||||||
|
_playbackTimer?.cancel();
|
||||||
|
_playbackTimer = Timer.periodic(const Duration(seconds: _playbackSeconds), (
|
||||||
|
_,
|
||||||
|
) {
|
||||||
|
if (!state.historyPlaying) {
|
||||||
|
_playbackTimer?.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextHistoryPosition(total);
|
||||||
|
onPlaybackTick?.call();
|
||||||
|
if (!state.historyPlaying) {
|
||||||
|
_playbackTimer?.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopPlaybackTimer() {
|
||||||
|
_playbackTimer?.cancel();
|
||||||
|
_playbackTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scheduleRecenter(VoidCallback onRecenter) {
|
||||||
|
if (!state.isFollowing) return;
|
||||||
|
_recenterTimer?.cancel();
|
||||||
|
_recenterTimer = Timer(const Duration(seconds: _recenterSeconds), () {
|
||||||
|
if (state.isFollowing) onRecenter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelRecenter() {
|
||||||
|
_recenterTimer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
String _modeName(PlacingMode mode) {
|
String _modeName(PlacingMode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case PlacingMode.geofence:
|
case PlacingMode.geofence:
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ final class LocationMapControllerProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$locationMapControllerHash() =>
|
String _$locationMapControllerHash() =>
|
||||||
r'c6eea4cec7a9a66546e9b66baf384edbb6e320f2';
|
r'565ab5147dd5ac16cf80210c55e28316bafe2f5a';
|
||||||
|
|
||||||
abstract class _$LocationMapController extends $Notifier<LocationMapState> {
|
abstract class _$LocationMapController extends $Notifier<LocationMapState> {
|
||||||
LocationMapState build();
|
LocationMapState build();
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ abstract class LocationMapState with _$LocationMapState {
|
|||||||
GeofenceEntity? selectedGeofence,
|
GeofenceEntity? selectedGeofence,
|
||||||
GeofenceEntity? editingGeofence,
|
GeofenceEntity? editingGeofence,
|
||||||
FrequentPlaceEntity? selectedFrequentPlace,
|
FrequentPlaceEntity? selectedFrequentPlace,
|
||||||
|
FrequentPlaceEntity? editingFrequentPlace,
|
||||||
PositionEntity? selectedHistoryPosition,
|
PositionEntity? selectedHistoryPosition,
|
||||||
@Default(false) bool isFollowing,
|
@Default(false) bool isFollowing,
|
||||||
@Default(false) bool actionsExpanded,
|
@Default(false) bool actionsExpanded,
|
||||||
@Default(false) bool frequencyExpanded,
|
@Default(false) bool frequencyExpanded,
|
||||||
@Default(_defaultZoom) double mapZoom,
|
@Default(_defaultZoom) double mapZoom,
|
||||||
|
@Default(-1) int historyNavigationIndex,
|
||||||
|
@Default(false) bool historyPlaying,
|
||||||
|
@Default(false) bool isRevealAnimating,
|
||||||
|
int? historyRevealCount,
|
||||||
}) = _LocationMapState;
|
}) = _LocationMapState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$LocationMapState {
|
mixin _$LocationMapState {
|
||||||
|
|
||||||
bool get showGeofences; bool get showFrequentPlaces; PlacingMode get placingMode; bool get adjustingRadius; double get previewRadius; LatLng? get previewPoint; GeofenceEntity? get selectedGeofence; GeofenceEntity? get editingGeofence; FrequentPlaceEntity? get selectedFrequentPlace; PositionEntity? get selectedHistoryPosition; bool get isFollowing; bool get actionsExpanded; bool get frequencyExpanded; double get mapZoom;
|
bool get showGeofences; bool get showFrequentPlaces; PlacingMode get placingMode; bool get adjustingRadius; double get previewRadius; LatLng? get previewPoint; GeofenceEntity? get selectedGeofence; GeofenceEntity? get editingGeofence; FrequentPlaceEntity? get selectedFrequentPlace; FrequentPlaceEntity? get editingFrequentPlace; PositionEntity? get selectedHistoryPosition; bool get isFollowing; bool get actionsExpanded; bool get frequencyExpanded; double get mapZoom; int get historyNavigationIndex; bool get historyPlaying; bool get isRevealAnimating; int? get historyRevealCount;
|
||||||
/// Create a copy of LocationMapState
|
/// Create a copy of LocationMapState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -25,16 +25,16 @@ $LocationMapStateCopyWith<LocationMapState> get copyWith => _$LocationMapStateCo
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocationMapState&&(identical(other.showGeofences, showGeofences) || other.showGeofences == showGeofences)&&(identical(other.showFrequentPlaces, showFrequentPlaces) || other.showFrequentPlaces == showFrequentPlaces)&&(identical(other.placingMode, placingMode) || other.placingMode == placingMode)&&(identical(other.adjustingRadius, adjustingRadius) || other.adjustingRadius == adjustingRadius)&&(identical(other.previewRadius, previewRadius) || other.previewRadius == previewRadius)&&(identical(other.previewPoint, previewPoint) || other.previewPoint == previewPoint)&&(identical(other.selectedGeofence, selectedGeofence) || other.selectedGeofence == selectedGeofence)&&(identical(other.editingGeofence, editingGeofence) || other.editingGeofence == editingGeofence)&&(identical(other.selectedFrequentPlace, selectedFrequentPlace) || other.selectedFrequentPlace == selectedFrequentPlace)&&(identical(other.selectedHistoryPosition, selectedHistoryPosition) || other.selectedHistoryPosition == selectedHistoryPosition)&&(identical(other.isFollowing, isFollowing) || other.isFollowing == isFollowing)&&(identical(other.actionsExpanded, actionsExpanded) || other.actionsExpanded == actionsExpanded)&&(identical(other.frequencyExpanded, frequencyExpanded) || other.frequencyExpanded == frequencyExpanded)&&(identical(other.mapZoom, mapZoom) || other.mapZoom == mapZoom));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocationMapState&&(identical(other.showGeofences, showGeofences) || other.showGeofences == showGeofences)&&(identical(other.showFrequentPlaces, showFrequentPlaces) || other.showFrequentPlaces == showFrequentPlaces)&&(identical(other.placingMode, placingMode) || other.placingMode == placingMode)&&(identical(other.adjustingRadius, adjustingRadius) || other.adjustingRadius == adjustingRadius)&&(identical(other.previewRadius, previewRadius) || other.previewRadius == previewRadius)&&(identical(other.previewPoint, previewPoint) || other.previewPoint == previewPoint)&&(identical(other.selectedGeofence, selectedGeofence) || other.selectedGeofence == selectedGeofence)&&(identical(other.editingGeofence, editingGeofence) || other.editingGeofence == editingGeofence)&&(identical(other.selectedFrequentPlace, selectedFrequentPlace) || other.selectedFrequentPlace == selectedFrequentPlace)&&(identical(other.editingFrequentPlace, editingFrequentPlace) || other.editingFrequentPlace == editingFrequentPlace)&&(identical(other.selectedHistoryPosition, selectedHistoryPosition) || other.selectedHistoryPosition == selectedHistoryPosition)&&(identical(other.isFollowing, isFollowing) || other.isFollowing == isFollowing)&&(identical(other.actionsExpanded, actionsExpanded) || other.actionsExpanded == actionsExpanded)&&(identical(other.frequencyExpanded, frequencyExpanded) || other.frequencyExpanded == frequencyExpanded)&&(identical(other.mapZoom, mapZoom) || other.mapZoom == mapZoom)&&(identical(other.historyNavigationIndex, historyNavigationIndex) || other.historyNavigationIndex == historyNavigationIndex)&&(identical(other.historyPlaying, historyPlaying) || other.historyPlaying == historyPlaying)&&(identical(other.isRevealAnimating, isRevealAnimating) || other.isRevealAnimating == isRevealAnimating)&&(identical(other.historyRevealCount, historyRevealCount) || other.historyRevealCount == historyRevealCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,showGeofences,showFrequentPlaces,placingMode,adjustingRadius,previewRadius,previewPoint,selectedGeofence,editingGeofence,selectedFrequentPlace,selectedHistoryPosition,isFollowing,actionsExpanded,frequencyExpanded,mapZoom);
|
int get hashCode => Object.hashAll([runtimeType,showGeofences,showFrequentPlaces,placingMode,adjustingRadius,previewRadius,previewPoint,selectedGeofence,editingGeofence,selectedFrequentPlace,editingFrequentPlace,selectedHistoryPosition,isFollowing,actionsExpanded,frequencyExpanded,mapZoom,historyNavigationIndex,historyPlaying,isRevealAnimating,historyRevealCount]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'LocationMapState(showGeofences: $showGeofences, showFrequentPlaces: $showFrequentPlaces, placingMode: $placingMode, adjustingRadius: $adjustingRadius, previewRadius: $previewRadius, previewPoint: $previewPoint, selectedGeofence: $selectedGeofence, editingGeofence: $editingGeofence, selectedFrequentPlace: $selectedFrequentPlace, selectedHistoryPosition: $selectedHistoryPosition, isFollowing: $isFollowing, actionsExpanded: $actionsExpanded, frequencyExpanded: $frequencyExpanded, mapZoom: $mapZoom)';
|
return 'LocationMapState(showGeofences: $showGeofences, showFrequentPlaces: $showFrequentPlaces, placingMode: $placingMode, adjustingRadius: $adjustingRadius, previewRadius: $previewRadius, previewPoint: $previewPoint, selectedGeofence: $selectedGeofence, editingGeofence: $editingGeofence, selectedFrequentPlace: $selectedFrequentPlace, editingFrequentPlace: $editingFrequentPlace, selectedHistoryPosition: $selectedHistoryPosition, isFollowing: $isFollowing, actionsExpanded: $actionsExpanded, frequencyExpanded: $frequencyExpanded, mapZoom: $mapZoom, historyNavigationIndex: $historyNavigationIndex, historyPlaying: $historyPlaying, isRevealAnimating: $isRevealAnimating, historyRevealCount: $historyRevealCount)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,11 +45,11 @@ abstract mixin class $LocationMapStateCopyWith<$Res> {
|
|||||||
factory $LocationMapStateCopyWith(LocationMapState value, $Res Function(LocationMapState) _then) = _$LocationMapStateCopyWithImpl;
|
factory $LocationMapStateCopyWith(LocationMapState value, $Res Function(LocationMapState) _then) = _$LocationMapStateCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom
|
bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, FrequentPlaceEntity? editingFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom, int historyNavigationIndex, bool historyPlaying, bool isRevealAnimating, int? historyRevealCount
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$GeofenceEntityCopyWith<$Res>? get selectedGeofence;$GeofenceEntityCopyWith<$Res>? get editingGeofence;$FrequentPlaceEntityCopyWith<$Res>? get selectedFrequentPlace;$PositionEntityCopyWith<$Res>? get selectedHistoryPosition;
|
$GeofenceEntityCopyWith<$Res>? get selectedGeofence;$GeofenceEntityCopyWith<$Res>? get editingGeofence;$FrequentPlaceEntityCopyWith<$Res>? get selectedFrequentPlace;$FrequentPlaceEntityCopyWith<$Res>? get editingFrequentPlace;$PositionEntityCopyWith<$Res>? get selectedHistoryPosition;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -62,7 +62,7 @@ class _$LocationMapStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of LocationMapState
|
/// Create a copy of LocationMapState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? showGeofences = null,Object? showFrequentPlaces = null,Object? placingMode = null,Object? adjustingRadius = null,Object? previewRadius = null,Object? previewPoint = freezed,Object? selectedGeofence = freezed,Object? editingGeofence = freezed,Object? selectedFrequentPlace = freezed,Object? selectedHistoryPosition = freezed,Object? isFollowing = null,Object? actionsExpanded = null,Object? frequencyExpanded = null,Object? mapZoom = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? showGeofences = null,Object? showFrequentPlaces = null,Object? placingMode = null,Object? adjustingRadius = null,Object? previewRadius = null,Object? previewPoint = freezed,Object? selectedGeofence = freezed,Object? editingGeofence = freezed,Object? selectedFrequentPlace = freezed,Object? editingFrequentPlace = freezed,Object? selectedHistoryPosition = freezed,Object? isFollowing = null,Object? actionsExpanded = null,Object? frequencyExpanded = null,Object? mapZoom = null,Object? historyNavigationIndex = null,Object? historyPlaying = null,Object? isRevealAnimating = null,Object? historyRevealCount = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
showGeofences: null == showGeofences ? _self.showGeofences : showGeofences // ignore: cast_nullable_to_non_nullable
|
showGeofences: null == showGeofences ? _self.showGeofences : showGeofences // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,showFrequentPlaces: null == showFrequentPlaces ? _self.showFrequentPlaces : showFrequentPlaces // ignore: cast_nullable_to_non_nullable
|
as bool,showFrequentPlaces: null == showFrequentPlaces ? _self.showFrequentPlaces : showFrequentPlaces // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -73,12 +73,17 @@ as double,previewPoint: freezed == previewPoint ? _self.previewPoint : previewPo
|
|||||||
as LatLng?,selectedGeofence: freezed == selectedGeofence ? _self.selectedGeofence : selectedGeofence // ignore: cast_nullable_to_non_nullable
|
as LatLng?,selectedGeofence: freezed == selectedGeofence ? _self.selectedGeofence : selectedGeofence // ignore: cast_nullable_to_non_nullable
|
||||||
as GeofenceEntity?,editingGeofence: freezed == editingGeofence ? _self.editingGeofence : editingGeofence // ignore: cast_nullable_to_non_nullable
|
as GeofenceEntity?,editingGeofence: freezed == editingGeofence ? _self.editingGeofence : editingGeofence // ignore: cast_nullable_to_non_nullable
|
||||||
as GeofenceEntity?,selectedFrequentPlace: freezed == selectedFrequentPlace ? _self.selectedFrequentPlace : selectedFrequentPlace // ignore: cast_nullable_to_non_nullable
|
as GeofenceEntity?,selectedFrequentPlace: freezed == selectedFrequentPlace ? _self.selectedFrequentPlace : selectedFrequentPlace // ignore: cast_nullable_to_non_nullable
|
||||||
|
as FrequentPlaceEntity?,editingFrequentPlace: freezed == editingFrequentPlace ? _self.editingFrequentPlace : editingFrequentPlace // ignore: cast_nullable_to_non_nullable
|
||||||
as FrequentPlaceEntity?,selectedHistoryPosition: freezed == selectedHistoryPosition ? _self.selectedHistoryPosition : selectedHistoryPosition // ignore: cast_nullable_to_non_nullable
|
as FrequentPlaceEntity?,selectedHistoryPosition: freezed == selectedHistoryPosition ? _self.selectedHistoryPosition : selectedHistoryPosition // ignore: cast_nullable_to_non_nullable
|
||||||
as PositionEntity?,isFollowing: null == isFollowing ? _self.isFollowing : isFollowing // ignore: cast_nullable_to_non_nullable
|
as PositionEntity?,isFollowing: null == isFollowing ? _self.isFollowing : isFollowing // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,actionsExpanded: null == actionsExpanded ? _self.actionsExpanded : actionsExpanded // ignore: cast_nullable_to_non_nullable
|
as bool,actionsExpanded: null == actionsExpanded ? _self.actionsExpanded : actionsExpanded // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,frequencyExpanded: null == frequencyExpanded ? _self.frequencyExpanded : frequencyExpanded // ignore: cast_nullable_to_non_nullable
|
as bool,frequencyExpanded: null == frequencyExpanded ? _self.frequencyExpanded : frequencyExpanded // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,mapZoom: null == mapZoom ? _self.mapZoom : mapZoom // ignore: cast_nullable_to_non_nullable
|
as bool,mapZoom: null == mapZoom ? _self.mapZoom : mapZoom // ignore: cast_nullable_to_non_nullable
|
||||||
as double,
|
as double,historyNavigationIndex: null == historyNavigationIndex ? _self.historyNavigationIndex : historyNavigationIndex // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,historyPlaying: null == historyPlaying ? _self.historyPlaying : historyPlaying // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isRevealAnimating: null == isRevealAnimating ? _self.isRevealAnimating : isRevealAnimating // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,historyRevealCount: freezed == historyRevealCount ? _self.historyRevealCount : historyRevealCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/// Create a copy of LocationMapState
|
/// Create a copy of LocationMapState
|
||||||
@@ -121,6 +126,18 @@ $FrequentPlaceEntityCopyWith<$Res>? get selectedFrequentPlace {
|
|||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
|
$FrequentPlaceEntityCopyWith<$Res>? get editingFrequentPlace {
|
||||||
|
if (_self.editingFrequentPlace == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $FrequentPlaceEntityCopyWith<$Res>(_self.editingFrequentPlace!, (value) {
|
||||||
|
return _then(_self.copyWith(editingFrequentPlace: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of LocationMapState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
$PositionEntityCopyWith<$Res>? get selectedHistoryPosition {
|
$PositionEntityCopyWith<$Res>? get selectedHistoryPosition {
|
||||||
if (_self.selectedHistoryPosition == null) {
|
if (_self.selectedHistoryPosition == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -211,10 +228,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, FrequentPlaceEntity? editingFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom, int historyNavigationIndex, bool historyPlaying, bool isRevealAnimating, int? historyRevealCount)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _LocationMapState() when $default != null:
|
case _LocationMapState() when $default != null:
|
||||||
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom);case _:
|
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.editingFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom,_that.historyNavigationIndex,_that.historyPlaying,_that.isRevealAnimating,_that.historyRevealCount);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -232,10 +249,10 @@ return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, FrequentPlaceEntity? editingFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom, int historyNavigationIndex, bool historyPlaying, bool isRevealAnimating, int? historyRevealCount) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _LocationMapState():
|
case _LocationMapState():
|
||||||
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom);case _:
|
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.editingFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom,_that.historyNavigationIndex,_that.historyPlaying,_that.isRevealAnimating,_that.historyRevealCount);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -252,10 +269,10 @@ return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, FrequentPlaceEntity? editingFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom, int historyNavigationIndex, bool historyPlaying, bool isRevealAnimating, int? historyRevealCount)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _LocationMapState() when $default != null:
|
case _LocationMapState() when $default != null:
|
||||||
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom);case _:
|
return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_that.adjustingRadius,_that.previewRadius,_that.previewPoint,_that.selectedGeofence,_that.editingGeofence,_that.selectedFrequentPlace,_that.editingFrequentPlace,_that.selectedHistoryPosition,_that.isFollowing,_that.actionsExpanded,_that.frequencyExpanded,_that.mapZoom,_that.historyNavigationIndex,_that.historyPlaying,_that.isRevealAnimating,_that.historyRevealCount);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -267,7 +284,7 @@ return $default(_that.showGeofences,_that.showFrequentPlaces,_that.placingMode,_
|
|||||||
|
|
||||||
|
|
||||||
class _LocationMapState implements LocationMapState {
|
class _LocationMapState implements LocationMapState {
|
||||||
const _LocationMapState({this.showGeofences = true, this.showFrequentPlaces = true, this.placingMode = PlacingMode.none, this.adjustingRadius = false, this.previewRadius = 200.0, this.previewPoint, this.selectedGeofence, this.editingGeofence, this.selectedFrequentPlace, this.selectedHistoryPosition, this.isFollowing = false, this.actionsExpanded = false, this.frequencyExpanded = false, this.mapZoom = _defaultZoom});
|
const _LocationMapState({this.showGeofences = true, this.showFrequentPlaces = true, this.placingMode = PlacingMode.none, this.adjustingRadius = false, this.previewRadius = 200.0, this.previewPoint, this.selectedGeofence, this.editingGeofence, this.selectedFrequentPlace, this.editingFrequentPlace, this.selectedHistoryPosition, this.isFollowing = false, this.actionsExpanded = false, this.frequencyExpanded = false, this.mapZoom = _defaultZoom, this.historyNavigationIndex = -1, this.historyPlaying = false, this.isRevealAnimating = false, this.historyRevealCount});
|
||||||
|
|
||||||
|
|
||||||
@override@JsonKey() final bool showGeofences;
|
@override@JsonKey() final bool showGeofences;
|
||||||
@@ -279,11 +296,16 @@ class _LocationMapState implements LocationMapState {
|
|||||||
@override final GeofenceEntity? selectedGeofence;
|
@override final GeofenceEntity? selectedGeofence;
|
||||||
@override final GeofenceEntity? editingGeofence;
|
@override final GeofenceEntity? editingGeofence;
|
||||||
@override final FrequentPlaceEntity? selectedFrequentPlace;
|
@override final FrequentPlaceEntity? selectedFrequentPlace;
|
||||||
|
@override final FrequentPlaceEntity? editingFrequentPlace;
|
||||||
@override final PositionEntity? selectedHistoryPosition;
|
@override final PositionEntity? selectedHistoryPosition;
|
||||||
@override@JsonKey() final bool isFollowing;
|
@override@JsonKey() final bool isFollowing;
|
||||||
@override@JsonKey() final bool actionsExpanded;
|
@override@JsonKey() final bool actionsExpanded;
|
||||||
@override@JsonKey() final bool frequencyExpanded;
|
@override@JsonKey() final bool frequencyExpanded;
|
||||||
@override@JsonKey() final double mapZoom;
|
@override@JsonKey() final double mapZoom;
|
||||||
|
@override@JsonKey() final int historyNavigationIndex;
|
||||||
|
@override@JsonKey() final bool historyPlaying;
|
||||||
|
@override@JsonKey() final bool isRevealAnimating;
|
||||||
|
@override final int? historyRevealCount;
|
||||||
|
|
||||||
/// Create a copy of LocationMapState
|
/// Create a copy of LocationMapState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -295,16 +317,16 @@ _$LocationMapStateCopyWith<_LocationMapState> get copyWith => __$LocationMapStat
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LocationMapState&&(identical(other.showGeofences, showGeofences) || other.showGeofences == showGeofences)&&(identical(other.showFrequentPlaces, showFrequentPlaces) || other.showFrequentPlaces == showFrequentPlaces)&&(identical(other.placingMode, placingMode) || other.placingMode == placingMode)&&(identical(other.adjustingRadius, adjustingRadius) || other.adjustingRadius == adjustingRadius)&&(identical(other.previewRadius, previewRadius) || other.previewRadius == previewRadius)&&(identical(other.previewPoint, previewPoint) || other.previewPoint == previewPoint)&&(identical(other.selectedGeofence, selectedGeofence) || other.selectedGeofence == selectedGeofence)&&(identical(other.editingGeofence, editingGeofence) || other.editingGeofence == editingGeofence)&&(identical(other.selectedFrequentPlace, selectedFrequentPlace) || other.selectedFrequentPlace == selectedFrequentPlace)&&(identical(other.selectedHistoryPosition, selectedHistoryPosition) || other.selectedHistoryPosition == selectedHistoryPosition)&&(identical(other.isFollowing, isFollowing) || other.isFollowing == isFollowing)&&(identical(other.actionsExpanded, actionsExpanded) || other.actionsExpanded == actionsExpanded)&&(identical(other.frequencyExpanded, frequencyExpanded) || other.frequencyExpanded == frequencyExpanded)&&(identical(other.mapZoom, mapZoom) || other.mapZoom == mapZoom));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LocationMapState&&(identical(other.showGeofences, showGeofences) || other.showGeofences == showGeofences)&&(identical(other.showFrequentPlaces, showFrequentPlaces) || other.showFrequentPlaces == showFrequentPlaces)&&(identical(other.placingMode, placingMode) || other.placingMode == placingMode)&&(identical(other.adjustingRadius, adjustingRadius) || other.adjustingRadius == adjustingRadius)&&(identical(other.previewRadius, previewRadius) || other.previewRadius == previewRadius)&&(identical(other.previewPoint, previewPoint) || other.previewPoint == previewPoint)&&(identical(other.selectedGeofence, selectedGeofence) || other.selectedGeofence == selectedGeofence)&&(identical(other.editingGeofence, editingGeofence) || other.editingGeofence == editingGeofence)&&(identical(other.selectedFrequentPlace, selectedFrequentPlace) || other.selectedFrequentPlace == selectedFrequentPlace)&&(identical(other.editingFrequentPlace, editingFrequentPlace) || other.editingFrequentPlace == editingFrequentPlace)&&(identical(other.selectedHistoryPosition, selectedHistoryPosition) || other.selectedHistoryPosition == selectedHistoryPosition)&&(identical(other.isFollowing, isFollowing) || other.isFollowing == isFollowing)&&(identical(other.actionsExpanded, actionsExpanded) || other.actionsExpanded == actionsExpanded)&&(identical(other.frequencyExpanded, frequencyExpanded) || other.frequencyExpanded == frequencyExpanded)&&(identical(other.mapZoom, mapZoom) || other.mapZoom == mapZoom)&&(identical(other.historyNavigationIndex, historyNavigationIndex) || other.historyNavigationIndex == historyNavigationIndex)&&(identical(other.historyPlaying, historyPlaying) || other.historyPlaying == historyPlaying)&&(identical(other.isRevealAnimating, isRevealAnimating) || other.isRevealAnimating == isRevealAnimating)&&(identical(other.historyRevealCount, historyRevealCount) || other.historyRevealCount == historyRevealCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,showGeofences,showFrequentPlaces,placingMode,adjustingRadius,previewRadius,previewPoint,selectedGeofence,editingGeofence,selectedFrequentPlace,selectedHistoryPosition,isFollowing,actionsExpanded,frequencyExpanded,mapZoom);
|
int get hashCode => Object.hashAll([runtimeType,showGeofences,showFrequentPlaces,placingMode,adjustingRadius,previewRadius,previewPoint,selectedGeofence,editingGeofence,selectedFrequentPlace,editingFrequentPlace,selectedHistoryPosition,isFollowing,actionsExpanded,frequencyExpanded,mapZoom,historyNavigationIndex,historyPlaying,isRevealAnimating,historyRevealCount]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'LocationMapState(showGeofences: $showGeofences, showFrequentPlaces: $showFrequentPlaces, placingMode: $placingMode, adjustingRadius: $adjustingRadius, previewRadius: $previewRadius, previewPoint: $previewPoint, selectedGeofence: $selectedGeofence, editingGeofence: $editingGeofence, selectedFrequentPlace: $selectedFrequentPlace, selectedHistoryPosition: $selectedHistoryPosition, isFollowing: $isFollowing, actionsExpanded: $actionsExpanded, frequencyExpanded: $frequencyExpanded, mapZoom: $mapZoom)';
|
return 'LocationMapState(showGeofences: $showGeofences, showFrequentPlaces: $showFrequentPlaces, placingMode: $placingMode, adjustingRadius: $adjustingRadius, previewRadius: $previewRadius, previewPoint: $previewPoint, selectedGeofence: $selectedGeofence, editingGeofence: $editingGeofence, selectedFrequentPlace: $selectedFrequentPlace, editingFrequentPlace: $editingFrequentPlace, selectedHistoryPosition: $selectedHistoryPosition, isFollowing: $isFollowing, actionsExpanded: $actionsExpanded, frequencyExpanded: $frequencyExpanded, mapZoom: $mapZoom, historyNavigationIndex: $historyNavigationIndex, historyPlaying: $historyPlaying, isRevealAnimating: $isRevealAnimating, historyRevealCount: $historyRevealCount)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -315,11 +337,11 @@ abstract mixin class _$LocationMapStateCopyWith<$Res> implements $LocationMapSta
|
|||||||
factory _$LocationMapStateCopyWith(_LocationMapState value, $Res Function(_LocationMapState) _then) = __$LocationMapStateCopyWithImpl;
|
factory _$LocationMapStateCopyWith(_LocationMapState value, $Res Function(_LocationMapState) _then) = __$LocationMapStateCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom
|
bool showGeofences, bool showFrequentPlaces, PlacingMode placingMode, bool adjustingRadius, double previewRadius, LatLng? previewPoint, GeofenceEntity? selectedGeofence, GeofenceEntity? editingGeofence, FrequentPlaceEntity? selectedFrequentPlace, FrequentPlaceEntity? editingFrequentPlace, PositionEntity? selectedHistoryPosition, bool isFollowing, bool actionsExpanded, bool frequencyExpanded, double mapZoom, int historyNavigationIndex, bool historyPlaying, bool isRevealAnimating, int? historyRevealCount
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@override $GeofenceEntityCopyWith<$Res>? get selectedGeofence;@override $GeofenceEntityCopyWith<$Res>? get editingGeofence;@override $FrequentPlaceEntityCopyWith<$Res>? get selectedFrequentPlace;@override $PositionEntityCopyWith<$Res>? get selectedHistoryPosition;
|
@override $GeofenceEntityCopyWith<$Res>? get selectedGeofence;@override $GeofenceEntityCopyWith<$Res>? get editingGeofence;@override $FrequentPlaceEntityCopyWith<$Res>? get selectedFrequentPlace;@override $FrequentPlaceEntityCopyWith<$Res>? get editingFrequentPlace;@override $PositionEntityCopyWith<$Res>? get selectedHistoryPosition;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -332,7 +354,7 @@ class __$LocationMapStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of LocationMapState
|
/// Create a copy of LocationMapState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? showGeofences = null,Object? showFrequentPlaces = null,Object? placingMode = null,Object? adjustingRadius = null,Object? previewRadius = null,Object? previewPoint = freezed,Object? selectedGeofence = freezed,Object? editingGeofence = freezed,Object? selectedFrequentPlace = freezed,Object? selectedHistoryPosition = freezed,Object? isFollowing = null,Object? actionsExpanded = null,Object? frequencyExpanded = null,Object? mapZoom = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? showGeofences = null,Object? showFrequentPlaces = null,Object? placingMode = null,Object? adjustingRadius = null,Object? previewRadius = null,Object? previewPoint = freezed,Object? selectedGeofence = freezed,Object? editingGeofence = freezed,Object? selectedFrequentPlace = freezed,Object? editingFrequentPlace = freezed,Object? selectedHistoryPosition = freezed,Object? isFollowing = null,Object? actionsExpanded = null,Object? frequencyExpanded = null,Object? mapZoom = null,Object? historyNavigationIndex = null,Object? historyPlaying = null,Object? isRevealAnimating = null,Object? historyRevealCount = freezed,}) {
|
||||||
return _then(_LocationMapState(
|
return _then(_LocationMapState(
|
||||||
showGeofences: null == showGeofences ? _self.showGeofences : showGeofences // ignore: cast_nullable_to_non_nullable
|
showGeofences: null == showGeofences ? _self.showGeofences : showGeofences // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,showFrequentPlaces: null == showFrequentPlaces ? _self.showFrequentPlaces : showFrequentPlaces // ignore: cast_nullable_to_non_nullable
|
as bool,showFrequentPlaces: null == showFrequentPlaces ? _self.showFrequentPlaces : showFrequentPlaces // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -343,12 +365,17 @@ as double,previewPoint: freezed == previewPoint ? _self.previewPoint : previewPo
|
|||||||
as LatLng?,selectedGeofence: freezed == selectedGeofence ? _self.selectedGeofence : selectedGeofence // ignore: cast_nullable_to_non_nullable
|
as LatLng?,selectedGeofence: freezed == selectedGeofence ? _self.selectedGeofence : selectedGeofence // ignore: cast_nullable_to_non_nullable
|
||||||
as GeofenceEntity?,editingGeofence: freezed == editingGeofence ? _self.editingGeofence : editingGeofence // ignore: cast_nullable_to_non_nullable
|
as GeofenceEntity?,editingGeofence: freezed == editingGeofence ? _self.editingGeofence : editingGeofence // ignore: cast_nullable_to_non_nullable
|
||||||
as GeofenceEntity?,selectedFrequentPlace: freezed == selectedFrequentPlace ? _self.selectedFrequentPlace : selectedFrequentPlace // ignore: cast_nullable_to_non_nullable
|
as GeofenceEntity?,selectedFrequentPlace: freezed == selectedFrequentPlace ? _self.selectedFrequentPlace : selectedFrequentPlace // ignore: cast_nullable_to_non_nullable
|
||||||
|
as FrequentPlaceEntity?,editingFrequentPlace: freezed == editingFrequentPlace ? _self.editingFrequentPlace : editingFrequentPlace // ignore: cast_nullable_to_non_nullable
|
||||||
as FrequentPlaceEntity?,selectedHistoryPosition: freezed == selectedHistoryPosition ? _self.selectedHistoryPosition : selectedHistoryPosition // ignore: cast_nullable_to_non_nullable
|
as FrequentPlaceEntity?,selectedHistoryPosition: freezed == selectedHistoryPosition ? _self.selectedHistoryPosition : selectedHistoryPosition // ignore: cast_nullable_to_non_nullable
|
||||||
as PositionEntity?,isFollowing: null == isFollowing ? _self.isFollowing : isFollowing // ignore: cast_nullable_to_non_nullable
|
as PositionEntity?,isFollowing: null == isFollowing ? _self.isFollowing : isFollowing // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,actionsExpanded: null == actionsExpanded ? _self.actionsExpanded : actionsExpanded // ignore: cast_nullable_to_non_nullable
|
as bool,actionsExpanded: null == actionsExpanded ? _self.actionsExpanded : actionsExpanded // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,frequencyExpanded: null == frequencyExpanded ? _self.frequencyExpanded : frequencyExpanded // ignore: cast_nullable_to_non_nullable
|
as bool,frequencyExpanded: null == frequencyExpanded ? _self.frequencyExpanded : frequencyExpanded // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,mapZoom: null == mapZoom ? _self.mapZoom : mapZoom // ignore: cast_nullable_to_non_nullable
|
as bool,mapZoom: null == mapZoom ? _self.mapZoom : mapZoom // ignore: cast_nullable_to_non_nullable
|
||||||
as double,
|
as double,historyNavigationIndex: null == historyNavigationIndex ? _self.historyNavigationIndex : historyNavigationIndex // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,historyPlaying: null == historyPlaying ? _self.historyPlaying : historyPlaying // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isRevealAnimating: null == isRevealAnimating ? _self.isRevealAnimating : isRevealAnimating // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,historyRevealCount: freezed == historyRevealCount ? _self.historyRevealCount : historyRevealCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,6 +419,18 @@ $FrequentPlaceEntityCopyWith<$Res>? get selectedFrequentPlace {
|
|||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
|
$FrequentPlaceEntityCopyWith<$Res>? get editingFrequentPlace {
|
||||||
|
if (_self.editingFrequentPlace == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $FrequentPlaceEntityCopyWith<$Res>(_self.editingFrequentPlace!, (value) {
|
||||||
|
return _then(_self.copyWith(editingFrequentPlace: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of LocationMapState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
$PositionEntityCopyWith<$Res>? get selectedHistoryPosition {
|
$PositionEntityCopyWith<$Res>? get selectedHistoryPosition {
|
||||||
if (_self.selectedHistoryPosition == null) {
|
if (_self.selectedHistoryPosition == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -14,8 +14,15 @@ class LocationListSheet extends ConsumerStatefulWidget {
|
|||||||
final List<FrequentPlaceEntity> frequentPlaces;
|
final List<FrequentPlaceEntity> frequentPlaces;
|
||||||
final List<PositionEntity> positionHistory;
|
final List<PositionEntity> positionHistory;
|
||||||
final Color primaryColor;
|
final Color primaryColor;
|
||||||
|
final bool hasHiddenGeofences;
|
||||||
|
final bool hasHiddenFrequentPlaces;
|
||||||
|
final bool hasHiddenHistory;
|
||||||
final ValueChanged<GeofenceEntity> onGeofenceTap;
|
final ValueChanged<GeofenceEntity> onGeofenceTap;
|
||||||
|
final ValueChanged<GeofenceEntity>? onGeofenceEdit;
|
||||||
|
final ValueChanged<GeofenceEntity>? onGeofenceDelete;
|
||||||
final ValueChanged<FrequentPlaceEntity> onFrequentPlaceTap;
|
final ValueChanged<FrequentPlaceEntity> onFrequentPlaceTap;
|
||||||
|
final ValueChanged<FrequentPlaceEntity>? onFrequentPlaceEdit;
|
||||||
|
final ValueChanged<FrequentPlaceEntity>? onFrequentPlaceDelete;
|
||||||
final ValueChanged<PositionEntity> onHistoryTap;
|
final ValueChanged<PositionEntity> onHistoryTap;
|
||||||
|
|
||||||
const LocationListSheet({
|
const LocationListSheet({
|
||||||
@@ -24,8 +31,15 @@ class LocationListSheet extends ConsumerStatefulWidget {
|
|||||||
required this.frequentPlaces,
|
required this.frequentPlaces,
|
||||||
required this.positionHistory,
|
required this.positionHistory,
|
||||||
required this.primaryColor,
|
required this.primaryColor,
|
||||||
|
this.hasHiddenGeofences = false,
|
||||||
|
this.hasHiddenFrequentPlaces = false,
|
||||||
|
this.hasHiddenHistory = false,
|
||||||
required this.onGeofenceTap,
|
required this.onGeofenceTap,
|
||||||
|
this.onGeofenceEdit,
|
||||||
|
this.onGeofenceDelete,
|
||||||
required this.onFrequentPlaceTap,
|
required this.onFrequentPlaceTap,
|
||||||
|
this.onFrequentPlaceEdit,
|
||||||
|
this.onFrequentPlaceDelete,
|
||||||
required this.onHistoryTap,
|
required this.onHistoryTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,6 +48,11 @@ class LocationListSheet extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LocationListSheetState extends ConsumerState<LocationListSheet> {
|
class _LocationListSheetState extends ConsumerState<LocationListSheet> {
|
||||||
|
bool get _hasHiddenItems =>
|
||||||
|
widget.hasHiddenGeofences ||
|
||||||
|
widget.hasHiddenFrequentPlaces ||
|
||||||
|
widget.hasHiddenHistory;
|
||||||
|
|
||||||
List<PositionEntity> _filterHistory(String? selectedType) {
|
List<PositionEntity> _filterHistory(String? selectedType) {
|
||||||
if (selectedType == null) return widget.positionHistory;
|
if (selectedType == null) return widget.positionHistory;
|
||||||
return widget.positionHistory
|
return widget.positionHistory
|
||||||
@@ -90,6 +109,12 @@ class _LocationListSheetState extends ConsumerState<LocationListSheet> {
|
|||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
icon: Icons.shield,
|
icon: Icons.shield,
|
||||||
onTap: () => widget.onGeofenceTap(g),
|
onTap: () => widget.onGeofenceTap(g),
|
||||||
|
onEdit: widget.onGeofenceEdit != null
|
||||||
|
? () => widget.onGeofenceEdit!(g)
|
||||||
|
: null,
|
||||||
|
onDelete: widget.onGeofenceDelete != null
|
||||||
|
? () => widget.onGeofenceDelete!(g)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -111,6 +136,12 @@ class _LocationListSheetState extends ConsumerState<LocationListSheet> {
|
|||||||
color: Colors.orange,
|
color: Colors.orange,
|
||||||
icon: Icons.home_rounded,
|
icon: Icons.home_rounded,
|
||||||
onTap: () => widget.onFrequentPlaceTap(fp),
|
onTap: () => widget.onFrequentPlaceTap(fp),
|
||||||
|
onEdit: widget.onFrequentPlaceEdit != null
|
||||||
|
? () => widget.onFrequentPlaceEdit!(fp)
|
||||||
|
: null,
|
||||||
|
onDelete: widget.onFrequentPlaceDelete != null
|
||||||
|
? () => widget.onFrequentPlaceDelete!(fp)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -136,11 +167,15 @@ class _LocationListSheetState extends ConsumerState<LocationListSheet> {
|
|||||||
padding: const EdgeInsets.only(top: 40),
|
padding: const EdgeInsets.only(top: 40),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
context.translate(I18n.locationListNoItems),
|
_hasHiddenItems
|
||||||
|
? context.translate(
|
||||||
|
I18n.locationListHiddenItems)
|
||||||
|
: context.translate(I18n.locationListNoItems),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -273,6 +308,8 @@ class _LocationListSheetState extends ConsumerState<LocationListSheet> {
|
|||||||
required Color color,
|
required Color color,
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
required VoidCallback onTap,
|
required VoidCallback onTap,
|
||||||
|
VoidCallback? onEdit,
|
||||||
|
VoidCallback? onDelete,
|
||||||
}) {
|
}) {
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.only(bottom: 6),
|
margin: const EdgeInsets.only(bottom: 6),
|
||||||
@@ -302,11 +339,41 @@ class _LocationListSheetState extends ConsumerState<LocationListSheet> {
|
|||||||
subtitle,
|
subtitle,
|
||||||
style: TextStyle(fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
style: TextStyle(fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
trailing: Icon(
|
trailing: onEdit != null || onDelete != null
|
||||||
Icons.chevron_right,
|
? Row(
|
||||||
color: Theme.of(context).colorScheme.outline,
|
mainAxisSize: MainAxisSize.min,
|
||||||
size: 20,
|
children: [
|
||||||
),
|
if (onEdit != null)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onEdit,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: Icon(
|
||||||
|
Icons.edit_outlined,
|
||||||
|
size: 18,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (onDelete != null)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onDelete,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: Icon(
|
||||||
|
Icons.delete_outline,
|
||||||
|
size: 18,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,11 +20,15 @@ import 'package:utils/utils.dart';
|
|||||||
import 'device_banner.dart';
|
import 'device_banner.dart';
|
||||||
import 'name_input_sheet.dart';
|
import 'name_input_sheet.dart';
|
||||||
import 'location_list_sheet.dart';
|
import 'location_list_sheet.dart';
|
||||||
import 'map_controls/layer_toggles.dart';
|
import 'map_controls/gps_control_panel.dart';
|
||||||
|
import 'map_controls/history_player.dart';
|
||||||
|
import 'map_controls/map_action_button.dart';
|
||||||
|
import 'map_controls/gps_functions_dialog.dart';
|
||||||
import 'map_controls/map_actions_panel.dart';
|
import 'map_controls/map_actions_panel.dart';
|
||||||
import 'map_controls/map_style_selector.dart';
|
import 'map_controls/map_style_selector.dart';
|
||||||
import 'map_controls/placement_banner.dart';
|
import 'map_controls/placement_banner.dart';
|
||||||
import 'map_controls/radius_slider_bar.dart';
|
import 'map_controls/radius_slider_bar.dart';
|
||||||
|
import 'map_controls/reveal_progress_bar.dart';
|
||||||
import 'map_info_cards/frequent_place_info_card.dart';
|
import 'map_info_cards/frequent_place_info_card.dart';
|
||||||
import 'map_info_cards/geofence_info_card.dart';
|
import 'map_info_cards/geofence_info_card.dart';
|
||||||
import 'map_info_cards/history_position_info_card.dart';
|
import 'map_info_cards/history_position_info_card.dart';
|
||||||
@@ -77,14 +81,17 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
LocationMapController get _vm =>
|
LocationMapController get _vm =>
|
||||||
ref.read(locationMapControllerProvider.notifier);
|
ref.read(locationMapControllerProvider.notifier);
|
||||||
|
|
||||||
Color get _primaryColor =>
|
Color get _primaryColor => context.sfColors.legacyPrimary;
|
||||||
context.sfColors.legacyPrimary;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_mapController = MapController();
|
_mapController = MapController();
|
||||||
_startMonitoring();
|
_startMonitoring();
|
||||||
|
_vm.onPlaybackTick = () {
|
||||||
|
final state = ref.read(locationMapControllerProvider);
|
||||||
|
_navigateToHistoryPosition(state.historyNavigationIndex);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static const _minFrequency = 60;
|
static const _minFrequency = 60;
|
||||||
@@ -94,7 +101,9 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
final raw = widget.selectedDevice?.settings.frequency ?? 60;
|
final raw = widget.selectedDevice?.settings.frequency ?? 60;
|
||||||
final frequency = raw < _minFrequency ? _minFrequency : raw;
|
final frequency = raw < _minFrequency ? _minFrequency : raw;
|
||||||
_followTimer = Timer.periodic(Duration(seconds: frequency), (_) {
|
_followTimer = Timer.periodic(Duration(seconds: frequency), (_) {
|
||||||
if (ref.read(selectedDeviceProvider).value?.isDisconnected ?? true) return;
|
if (ref.read(selectedDeviceProvider).value?.isDisconnected ?? true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
widget.onRefreshPosition();
|
widget.onRefreshPosition();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -102,6 +111,9 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_followTimer?.cancel();
|
_followTimer?.cancel();
|
||||||
|
_vm.onPlaybackTick = null;
|
||||||
|
_vm.stopPlaybackTimer();
|
||||||
|
_vm.cancelRecenter();
|
||||||
_moveAnimation?.dispose();
|
_moveAnimation?.dispose();
|
||||||
_mapController.dispose();
|
_mapController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -131,12 +143,80 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
(oldWidget.positionHistory.length != widget.positionHistory.length ||
|
(oldWidget.positionHistory.length != widget.positionHistory.length ||
|
||||||
!oldWidget.showRouteTrail)) {
|
!oldWidget.showRouteTrail)) {
|
||||||
_fitHistoryBounds();
|
_fitHistoryBounds();
|
||||||
|
if (oldWidget.positionHistory.length != widget.positionHistory.length) {
|
||||||
|
Future(() {
|
||||||
|
_vm.startHistoryNavigation();
|
||||||
|
_startRevealAnimation();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _animatedMove(LatLng dest, double zoom) {
|
void _animatedMove(
|
||||||
|
LatLng dest,
|
||||||
|
double? zoom, {
|
||||||
|
Duration duration = const Duration(milliseconds: 400),
|
||||||
|
Curve curve = Curves.easeInOut,
|
||||||
|
}) {
|
||||||
|
_animatedMoveAsync(dest, zoom, duration: duration, curve: curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startRevealAnimation() async {
|
||||||
|
final positions = widget.positionHistory
|
||||||
|
.where((p) => p.latitude != 0 || p.longitude != 0)
|
||||||
|
.toList();
|
||||||
|
if (positions.length <= 1) return;
|
||||||
|
|
||||||
|
_vm.startReveal();
|
||||||
|
|
||||||
|
await _animatedMoveAsync(
|
||||||
|
LatLng(positions.first.latitude, positions.first.longitude),
|
||||||
|
18.0,
|
||||||
|
duration: const Duration(milliseconds: 1000),
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var i = 1; i < positions.length; i++) {
|
||||||
|
if (!mounted) return;
|
||||||
|
final state = ref.read(locationMapControllerProvider);
|
||||||
|
if (!state.isRevealAnimating) return;
|
||||||
|
_vm.updateRevealCount(i + 1);
|
||||||
|
await _animatedMoveAsync(
|
||||||
|
LatLng(positions[i].latitude, positions[i].longitude),
|
||||||
|
null,
|
||||||
|
duration: const Duration(milliseconds: 800),
|
||||||
|
curve: Curves.easeInOutCubic,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (!ref.read(locationMapControllerProvider).isRevealAnimating) return;
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
_vm.finishReveal();
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
if (!mounted) return;
|
||||||
|
_fitHistoryBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _skipRevealAnimation() {
|
||||||
|
_moveAnimation?.dispose();
|
||||||
|
_moveAnimation = null;
|
||||||
|
_vm.finishReveal();
|
||||||
|
Future(_vm.startHistoryNavigation);
|
||||||
|
_fitHistoryBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _animatedMoveAsync(
|
||||||
|
LatLng dest,
|
||||||
|
double? zoom, {
|
||||||
|
Duration duration = const Duration(milliseconds: 400),
|
||||||
|
Curve curve = Curves.easeInOut,
|
||||||
|
}) {
|
||||||
|
final completer = Completer<void>();
|
||||||
_moveAnimation?.dispose();
|
_moveAnimation?.dispose();
|
||||||
final camera = _mapController.camera;
|
final camera = _mapController.camera;
|
||||||
|
final targetZoom = zoom ?? camera.zoom;
|
||||||
final latTween = Tween<double>(
|
final latTween = Tween<double>(
|
||||||
begin: camera.center.latitude,
|
begin: camera.center.latitude,
|
||||||
end: dest.latitude,
|
end: dest.latitude,
|
||||||
@@ -145,18 +225,12 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
begin: camera.center.longitude,
|
begin: camera.center.longitude,
|
||||||
end: dest.longitude,
|
end: dest.longitude,
|
||||||
);
|
);
|
||||||
final zoomTween = Tween<double>(begin: camera.zoom, end: zoom);
|
final zoomTween = Tween<double>(begin: camera.zoom, end: targetZoom);
|
||||||
|
|
||||||
final controller = AnimationController(
|
final controller = AnimationController(duration: duration, vsync: this);
|
||||||
duration: const Duration(milliseconds: 400),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
_moveAnimation = controller;
|
_moveAnimation = controller;
|
||||||
|
|
||||||
final animation = CurvedAnimation(
|
final animation = CurvedAnimation(parent: controller, curve: curve);
|
||||||
parent: controller,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
|
|
||||||
controller.addListener(() {
|
controller.addListener(() {
|
||||||
_mapController.move(
|
_mapController.move(
|
||||||
@@ -170,10 +244,53 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
status == AnimationStatus.dismissed) {
|
status == AnimationStatus.dismissed) {
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
if (_moveAnimation == controller) _moveAnimation = null;
|
if (_moveAnimation == controller) _moveAnimation = null;
|
||||||
|
if (!completer.isCompleted) completer.complete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.forward();
|
controller.forward();
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isHistoryNavigating(LocationMapState mapState) =>
|
||||||
|
widget.showRouteTrail &&
|
||||||
|
widget.positionHistory.isNotEmpty &&
|
||||||
|
mapState.historyNavigationIndex >= 0 &&
|
||||||
|
mapState.historyNavigationIndex < widget.positionHistory.length;
|
||||||
|
|
||||||
|
void _navigateToHistoryPosition(int index) {
|
||||||
|
if (index < 0 || index >= widget.positionHistory.length) return;
|
||||||
|
final position = widget.positionHistory[index];
|
||||||
|
if (position.latitude == 0 && position.longitude == 0) return;
|
||||||
|
_animatedMove(LatLng(position.latitude, position.longitude), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleToggleFollow(LocationMapState mapState) async {
|
||||||
|
final willActivate = !mapState.isFollowing;
|
||||||
|
if (willActivate && !await guardDeviceCommand(context, ref)) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
_vm.toggleFollowing();
|
||||||
|
unawaited(
|
||||||
|
ref
|
||||||
|
.read(sfTrackingProvider)
|
||||||
|
.legacyLocationMapFollowToggled(willActivate),
|
||||||
|
);
|
||||||
|
if (willActivate && widget.selectedPosition != null) {
|
||||||
|
_centerOnDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _togglePlayback() {
|
||||||
|
_vm.toggleHistoryPlayback(widget.positionHistory.length);
|
||||||
|
final state = ref.read(locationMapControllerProvider);
|
||||||
|
if (state.historyPlaying) {
|
||||||
|
_navigateToHistoryPosition(state.historyNavigationIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scheduleRecenter() {
|
||||||
|
if (widget.selectedPosition == null) return;
|
||||||
|
_vm.scheduleRecenter(_centerOnDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _centerOnDevice() {
|
void _centerOnDevice() {
|
||||||
@@ -245,6 +362,46 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _showGpsFunctionsDialog(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
LocationMapState mapState,
|
||||||
|
) async {
|
||||||
|
final result = await showGpsFunctionsDialog(
|
||||||
|
context,
|
||||||
|
isFollowing: mapState.isFollowing,
|
||||||
|
showGeofences: mapState.showGeofences,
|
||||||
|
showFrequentPlaces: mapState.showFrequentPlaces,
|
||||||
|
showRouteTrail: widget.showRouteTrail,
|
||||||
|
hasPositionHistory: widget.positionHistory.isNotEmpty,
|
||||||
|
hasPosition: widget.selectedPosition != null,
|
||||||
|
);
|
||||||
|
if (result == null || !mounted) return;
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case GpsFunction.list:
|
||||||
|
_showListSheet();
|
||||||
|
case GpsFunction.addGeofence:
|
||||||
|
_vm.startPlacing(PlacingMode.geofence);
|
||||||
|
case GpsFunction.addFrequentPlace:
|
||||||
|
_vm.startPlacing(PlacingMode.frequentPlace);
|
||||||
|
case GpsFunction.share:
|
||||||
|
_shareLocation();
|
||||||
|
case GpsFunction.centerOnDevice:
|
||||||
|
_centerOnDevice();
|
||||||
|
case GpsFunction.toggleFollow:
|
||||||
|
_handleToggleFollow(mapState);
|
||||||
|
case GpsFunction.toggleGeofences:
|
||||||
|
_vm.toggleGeofences();
|
||||||
|
case GpsFunction.toggleFrequentPlaces:
|
||||||
|
_vm.toggleFrequentPlaces();
|
||||||
|
case GpsFunction.positionHistory:
|
||||||
|
_handleHistoryTap();
|
||||||
|
case GpsFunction.toggleRouteTrail:
|
||||||
|
ref.read(locationControllerProvider.notifier).toggleRouteTrail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _updateFrequency(int frequency) async {
|
Future<void> _updateFrequency(int frequency) async {
|
||||||
if (!await guardDeviceCommand(context, ref)) return;
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
final success = await ref
|
final success = await ref
|
||||||
@@ -253,7 +410,9 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (success) {
|
if (success) {
|
||||||
_followTimer?.cancel();
|
_followTimer?.cancel();
|
||||||
final safeFrequency = frequency < _minFrequency ? _minFrequency : frequency;
|
final safeFrequency = frequency < _minFrequency
|
||||||
|
? _minFrequency
|
||||||
|
: frequency;
|
||||||
_followTimer = Timer.periodic(Duration(seconds: safeFrequency), (_) {
|
_followTimer = Timer.periodic(Duration(seconds: safeFrequency), (_) {
|
||||||
widget.onRefreshPosition();
|
widget.onRefreshPosition();
|
||||||
});
|
});
|
||||||
@@ -274,19 +433,41 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
if (mapState.placingMode == PlacingMode.geofence) {
|
if (mapState.placingMode == PlacingMode.geofence) {
|
||||||
_vm.confirmGeofencePlacement(center);
|
_vm.confirmGeofencePlacement(center);
|
||||||
} else {
|
} else {
|
||||||
|
final editing = mapState.editingFrequentPlace;
|
||||||
_vm.confirmFrequentPlacePlacement();
|
_vm.confirmFrequentPlacePlacement();
|
||||||
showNameInputSheet(
|
showNameInputSheet(
|
||||||
context,
|
context,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
title: context.translate(I18n.locationNewFrequentPlace),
|
title: context.translate(
|
||||||
|
editing != null
|
||||||
|
? I18n.locationEditFrequentPlace
|
||||||
|
: I18n.locationNewFrequentPlace,
|
||||||
|
),
|
||||||
hintText: context.translate(I18n.locationHintFrequentPlace),
|
hintText: context.translate(I18n.locationHintFrequentPlace),
|
||||||
onSubmit: (name, _) => ref
|
initialName: editing?.name,
|
||||||
.read(locationControllerProvider.notifier)
|
submitLabel: editing != null
|
||||||
.createFrequentPlace(
|
? context.translate(I18n.locationSave)
|
||||||
name: name,
|
: null,
|
||||||
lat: center.latitude,
|
onSubmit: (name, _) {
|
||||||
lng: center.longitude,
|
if (editing != null) {
|
||||||
),
|
return ref
|
||||||
|
.read(locationControllerProvider.notifier)
|
||||||
|
.updateFrequentPlace(
|
||||||
|
id: editing.id,
|
||||||
|
name: name,
|
||||||
|
lat: center.latitude,
|
||||||
|
lng: center.longitude,
|
||||||
|
wifiList: editing.wifiList,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ref
|
||||||
|
.read(locationControllerProvider.notifier)
|
||||||
|
.createFrequentPlace(
|
||||||
|
name: name,
|
||||||
|
lat: center.latitude,
|
||||||
|
lng: center.longitude,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,15 +537,57 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
positionHistory: locationState.showRouteTrail
|
positionHistory: locationState.showRouteTrail
|
||||||
? locationState.positionHistory
|
? locationState.positionHistory
|
||||||
: [],
|
: [],
|
||||||
|
hasHiddenGeofences:
|
||||||
|
!mapState.showGeofences && locationState.geofences.isNotEmpty,
|
||||||
|
hasHiddenFrequentPlaces:
|
||||||
|
!mapState.showFrequentPlaces &&
|
||||||
|
locationState.frequentPlaces.isNotEmpty,
|
||||||
|
hasHiddenHistory:
|
||||||
|
!locationState.showRouteTrail &&
|
||||||
|
locationState.positionHistory.isNotEmpty,
|
||||||
primaryColor: _primaryColor,
|
primaryColor: _primaryColor,
|
||||||
onGeofenceTap: (g) {
|
onGeofenceTap: (g) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_animatedMove(LatLng(g.latitude, g.longitude), _defaultZoom);
|
_animatedMove(LatLng(g.latitude, g.longitude), _defaultZoom);
|
||||||
},
|
},
|
||||||
|
onGeofenceEdit: (g) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_vm.selectGeofence(g);
|
||||||
|
},
|
||||||
|
onGeofenceDelete: (g) async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
final confirmed = await _confirmDelete(
|
||||||
|
context,
|
||||||
|
I18n.locationDeleteGeofenceConfirm,
|
||||||
|
);
|
||||||
|
if (!confirmed || !mounted) return;
|
||||||
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
ref
|
||||||
|
.read(locationControllerProvider.notifier)
|
||||||
|
.deleteGeofence(id: g.id);
|
||||||
|
},
|
||||||
onFrequentPlaceTap: (fp) {
|
onFrequentPlaceTap: (fp) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_animatedMove(LatLng(fp.lat, fp.lng), _defaultZoom);
|
_animatedMove(LatLng(fp.lat, fp.lng), _defaultZoom);
|
||||||
},
|
},
|
||||||
|
onFrequentPlaceEdit: (fp) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_onEditFrequentPlace(fp);
|
||||||
|
},
|
||||||
|
onFrequentPlaceDelete: (fp) async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
final confirmed = await _confirmDelete(
|
||||||
|
context,
|
||||||
|
I18n.locationDeleteFrequentPlaceConfirm,
|
||||||
|
);
|
||||||
|
if (!confirmed || !mounted) return;
|
||||||
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
ref
|
||||||
|
.read(locationControllerProvider.notifier)
|
||||||
|
.deleteFrequentPlace(id: fp.id);
|
||||||
|
},
|
||||||
onHistoryTap: (p) {
|
onHistoryTap: (p) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
if (p.latitude != 0 || p.longitude != 0) {
|
if (p.latitude != 0 || p.longitude != 0) {
|
||||||
@@ -375,12 +598,34 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> _confirmDelete(BuildContext context, String messageKey) async {
|
||||||
|
final result = await showLegacyDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) => AlertDialog(
|
||||||
|
title: Text(context.translate(I18n.delete)),
|
||||||
|
content: Text(context.translate(messageKey)),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||||
|
child: Text(context.translate(I18n.cancel)),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
child: Text(context.translate(I18n.delete)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return result == true;
|
||||||
|
}
|
||||||
|
|
||||||
void _handleHistoryTap() {
|
void _handleHistoryTap() {
|
||||||
if (widget.positionHistory.isEmpty) {
|
_vm.stopPlaybackTimer();
|
||||||
_openDateRangePicker();
|
_vm.stopHistoryNavigation();
|
||||||
} else {
|
_openDateRangePicker();
|
||||||
ref.read(locationControllerProvider.notifier).toggleRouteTrail();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openDateRangePicker() async {
|
Future<void> _openDateRangePicker() async {
|
||||||
@@ -394,12 +639,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
start: now.subtract(const Duration(days: 1)),
|
start: now.subtract(const Duration(days: 1)),
|
||||||
end: now,
|
end: now,
|
||||||
),
|
),
|
||||||
builder: (context, child) => Theme(
|
builder: legacyPickerThemeBuilder,
|
||||||
data: Theme.of(
|
|
||||||
context,
|
|
||||||
).copyWith(colorScheme: ColorScheme.light(primary: _primaryColor)),
|
|
||||||
child: child!,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
@@ -411,33 +651,31 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
59,
|
59,
|
||||||
59,
|
59,
|
||||||
);
|
);
|
||||||
ref
|
if (!mounted) return;
|
||||||
|
showInfoDialog(context, I18n.locationHistoryLoading);
|
||||||
|
await ref
|
||||||
.read(locationControllerProvider.notifier)
|
.read(locationControllerProvider.notifier)
|
||||||
.loadPositionHistory(from: picked.start, to: to);
|
.loadPositionHistory(from: picked.start, to: to);
|
||||||
|
if (!mounted) return;
|
||||||
|
final history =
|
||||||
|
ref.read(locationControllerProvider).value?.positionHistory ?? [];
|
||||||
|
if (history.isEmpty) {
|
||||||
|
await showErrorDialog(context, I18n.locationHistoryEmpty);
|
||||||
|
} else {
|
||||||
|
await showSuccessDialog(
|
||||||
|
context,
|
||||||
|
I18n.locationHistoryLoaded,
|
||||||
|
args: {'count': history.length},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onEditFrequentPlace(FrequentPlaceEntity fp) async {
|
Future<void> _onEditFrequentPlace(FrequentPlaceEntity fp) async {
|
||||||
if (!await guardDeviceCommand(context, ref)) return;
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_vm.clearSelectedFrequentPlace();
|
_vm.startEditingFrequentPlace(fp);
|
||||||
showNameInputSheet(
|
_animatedMove(LatLng(fp.lat, fp.lng), null);
|
||||||
context,
|
|
||||||
ref: ref,
|
|
||||||
title: context.translate(I18n.locationEditFrequentPlace),
|
|
||||||
hintText: context.translate(I18n.locationHintFrequentPlace),
|
|
||||||
initialName: fp.name,
|
|
||||||
submitLabel: context.translate(I18n.locationSave),
|
|
||||||
onSubmit: (name, _) => ref
|
|
||||||
.read(locationControllerProvider.notifier)
|
|
||||||
.updateFrequentPlace(
|
|
||||||
id: fp.id,
|
|
||||||
name: name,
|
|
||||||
lat: fp.lat,
|
|
||||||
lng: fp.lng,
|
|
||||||
wifiList: fp.wifiList,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildMapLayers(LocationMapState mapState) {
|
List<Widget> _buildMapLayers(LocationMapState mapState) {
|
||||||
@@ -448,6 +686,8 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
zoom: mapState.mapZoom,
|
zoom: mapState.mapZoom,
|
||||||
onPositionTap: _vm.selectHistoryPosition,
|
onPositionTap: _vm.selectHistoryPosition,
|
||||||
onClusterTap: _animatedMove,
|
onClusterTap: _animatedMove,
|
||||||
|
activeIndex: mapState.historyNavigationIndex,
|
||||||
|
visibleCount: mapState.historyRevealCount,
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -488,17 +728,68 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
MarkerLayer(markers: _buildMarkers(mapState, historyLayer)),
|
MarkerLayer(markers: _buildMarkers(mapState, historyLayer)),
|
||||||
if (widget.selectedDevice != null)
|
if (mapState.isRevealAnimating) ...[
|
||||||
Align(
|
Positioned(
|
||||||
alignment: Alignment.bottomCenter,
|
bottom: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
child: DeviceBanner(
|
left: 0,
|
||||||
device: widget.selectedDevice!,
|
right: 0,
|
||||||
devices: widget.devices,
|
child: RevealProgressBar(
|
||||||
positions: widget.positions,
|
current: mapState.historyRevealCount ?? 0,
|
||||||
onDeviceChanged: widget.onDeviceChanged,
|
total: widget.positionHistory.length,
|
||||||
onTap: _centerOnDevice,
|
primaryColor: _primaryColor,
|
||||||
|
onSkip: _skipRevealAnimation,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
] else if (_isHistoryNavigating(mapState)) ...[
|
||||||
|
Positioned(
|
||||||
|
bottom: SizeUtils.getByScreen(small: 16, big: 14),
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: HistoryPlayer(
|
||||||
|
currentIndex: mapState.historyNavigationIndex,
|
||||||
|
total: widget.positionHistory.length,
|
||||||
|
isPlaying: mapState.historyPlaying,
|
||||||
|
currentPosition:
|
||||||
|
widget.positionHistory[mapState.historyNavigationIndex],
|
||||||
|
onPrevious: () {
|
||||||
|
_vm.previousHistoryPosition();
|
||||||
|
final state = ref.read(locationMapControllerProvider);
|
||||||
|
_navigateToHistoryPosition(state.historyNavigationIndex);
|
||||||
|
},
|
||||||
|
onNext: () {
|
||||||
|
_vm.nextHistoryPosition(widget.positionHistory.length);
|
||||||
|
final state = ref.read(locationMapControllerProvider);
|
||||||
|
_navigateToHistoryPosition(state.historyNavigationIndex);
|
||||||
|
},
|
||||||
|
onPlayPause: _togglePlayback,
|
||||||
|
onClose: () {
|
||||||
|
_vm.stopPlaybackTimer();
|
||||||
|
_vm.stopHistoryNavigation();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] else ...[
|
||||||
|
if (widget.selectedPosition != null)
|
||||||
|
Positioned(
|
||||||
|
bottom: SizeUtils.getByScreen(small: 190, big: 186),
|
||||||
|
right: 16,
|
||||||
|
child: MapActionButton(
|
||||||
|
icon: Icons.my_location,
|
||||||
|
onTap: _centerOnDevice,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.selectedDevice != null)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: DeviceBanner(
|
||||||
|
device: widget.selectedDevice!,
|
||||||
|
devices: widget.devices,
|
||||||
|
positions: widget.positions,
|
||||||
|
onDeviceChanged: widget.onDeviceChanged,
|
||||||
|
onTap: _centerOnDevice,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,7 +829,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onLongPress: () => _vm.selectFrequentPlace(fp),
|
onTap: () => _vm.selectFrequentPlace(fp),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withValues(alpha: 0.3),
|
color: Colors.orange.withValues(alpha: 0.3),
|
||||||
@@ -567,7 +858,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
width: 36,
|
width: 36,
|
||||||
height: 36,
|
height: 36,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onLongPress: () => _vm.selectGeofence(g),
|
onTap: () => _vm.selectGeofence(g),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue.withValues(alpha: 0.3),
|
color: Colors.blue.withValues(alpha: 0.3),
|
||||||
@@ -643,7 +934,12 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
FrequencySelector(
|
FrequencySelector(
|
||||||
currentFrequency:
|
currentFrequency:
|
||||||
ref.watch(selectedDeviceProvider).value?.settings.frequency ?? 60,
|
ref
|
||||||
|
.watch(selectedDeviceProvider)
|
||||||
|
.value
|
||||||
|
?.settings
|
||||||
|
.frequency ??
|
||||||
|
60,
|
||||||
options: widget.selectedDevice!.capabilities!.location!.options
|
options: widget.selectedDevice!.capabilities!.location!.options
|
||||||
.where((o) => o > 0 || o == -1)
|
.where((o) => o > 0 || o == -1)
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -656,31 +952,8 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
Positioned(
|
Positioned(
|
||||||
top: 12,
|
top: 12,
|
||||||
right: 12,
|
right: 12,
|
||||||
child: LayerToggles(
|
child: GpsControlPanel(
|
||||||
showGeofences: mapState.showGeofences,
|
onRefresh: () async {
|
||||||
showFrequentPlaces: mapState.showFrequentPlaces,
|
|
||||||
showRouteTrail: widget.showRouteTrail,
|
|
||||||
hasPositionHistory: widget.positionHistory.isNotEmpty,
|
|
||||||
isLoadingHistory: widget.isLoadingHistory,
|
|
||||||
onGeofencesToggled: _vm.toggleGeofences,
|
|
||||||
onFrequentPlacesToggled: _vm.toggleFrequentPlaces,
|
|
||||||
onHistoryTapped: _handleHistoryTap,
|
|
||||||
onHistoryLongPressed: _openDateRangePicker,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: SizeUtils.getByScreen(small: 120, big: 110),
|
|
||||||
right: 12,
|
|
||||||
child: MapActionsPanel(
|
|
||||||
actionsExpanded: mapState.actionsExpanded,
|
|
||||||
hasPosition: widget.selectedPosition != null,
|
|
||||||
isFollowing: mapState.isFollowing,
|
|
||||||
onToggleExpanded: _vm.toggleActionsExpanded,
|
|
||||||
onListTap: _showListSheet,
|
|
||||||
onAddGeofence: () => _vm.startPlacing(PlacingMode.geofence),
|
|
||||||
onAddFrequentPlace: () => _vm.startPlacing(PlacingMode.frequentPlace),
|
|
||||||
onShareTap: _shareLocation,
|
|
||||||
onRefreshTap: () async {
|
|
||||||
if (!await guardDeviceCommand(context, ref)) return;
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
unawaited(
|
unawaited(
|
||||||
ref.read(sfTrackingProvider).legacyLocationMapRefreshTapped(),
|
ref.read(sfTrackingProvider).legacyLocationMapRefreshTapped(),
|
||||||
@@ -694,27 +967,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
await showErrorDialog(context, I18n.errorPositions);
|
await showErrorDialog(context, I18n.errorPositions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCenterTap: _centerOnDevice,
|
onSettings: () => _showGpsFunctionsDialog(context, ref, mapState),
|
||||||
onToggleFollow: () async {
|
|
||||||
final willActivate = !mapState.isFollowing;
|
|
||||||
if (willActivate && !await guardDeviceCommand(context, ref)) return;
|
|
||||||
_vm.toggleFollowing();
|
|
||||||
unawaited(
|
|
||||||
ref
|
|
||||||
.read(sfTrackingProvider)
|
|
||||||
.legacyLocationMapFollowToggled(willActivate),
|
|
||||||
);
|
|
||||||
if (willActivate && widget.selectedPosition != null) {
|
|
||||||
_centerOnDevice();
|
|
||||||
}
|
|
||||||
if (!mounted) return;
|
|
||||||
await showSuccessDialog(
|
|
||||||
context,
|
|
||||||
willActivate
|
|
||||||
? I18n.locationMapFollowEnabled
|
|
||||||
: I18n.locationMapFollowDisabled,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -791,10 +1044,13 @@ class _LocationMapState extends ConsumerState<LocationMap>
|
|||||||
initialZoom: initialZoom,
|
initialZoom: initialZoom,
|
||||||
minZoom: 5,
|
minZoom: 5,
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
onPositionChanged: (camera, _) {
|
onPositionChanged: (camera, hasGesture) {
|
||||||
if (widget.showRouteTrail && widget.positionHistory.length > 2) {
|
if (widget.showRouteTrail && widget.positionHistory.length > 2) {
|
||||||
_vm.updateMapZoom(camera.zoom);
|
_vm.updateMapZoom(camera.zoom);
|
||||||
}
|
}
|
||||||
|
if (hasGesture) {
|
||||||
|
_scheduleRecenter();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
children: _buildMapLayers(mapState),
|
children: _buildMapLayers(mapState),
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class GpsControlPanel extends StatelessWidget {
|
||||||
|
final VoidCallback onRefresh;
|
||||||
|
final VoidCallback onSettings;
|
||||||
|
|
||||||
|
const GpsControlPanel({
|
||||||
|
super.key,
|
||||||
|
required this.onRefresh,
|
||||||
|
required this.onSettings,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: primaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.15),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_PanelButton(
|
||||||
|
icon: Icons.refresh,
|
||||||
|
onTap: onRefresh,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
_PanelButton(
|
||||||
|
icon: Icons.settings,
|
||||||
|
onTap: onSettings,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PanelButton extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const _PanelButton({
|
||||||
|
required this.icon,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.2),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
|
import 'package:legacy_ui/legacy_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
|
enum GpsFunction {
|
||||||
|
list,
|
||||||
|
addGeofence,
|
||||||
|
addFrequentPlace,
|
||||||
|
share,
|
||||||
|
centerOnDevice,
|
||||||
|
toggleFollow,
|
||||||
|
toggleGeofences,
|
||||||
|
toggleFrequentPlaces,
|
||||||
|
positionHistory,
|
||||||
|
toggleRouteTrail,
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<GpsFunction?> showGpsFunctionsDialog(
|
||||||
|
BuildContext context, {
|
||||||
|
required bool isFollowing,
|
||||||
|
required bool showGeofences,
|
||||||
|
required bool showFrequentPlaces,
|
||||||
|
required bool showRouteTrail,
|
||||||
|
required bool hasPositionHistory,
|
||||||
|
required bool hasPosition,
|
||||||
|
}) {
|
||||||
|
return showLegacyDialog<GpsFunction>(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) => _GpsFunctionsDialog(
|
||||||
|
isFollowing: isFollowing,
|
||||||
|
showGeofences: showGeofences,
|
||||||
|
showFrequentPlaces: showFrequentPlaces,
|
||||||
|
showRouteTrail: showRouteTrail,
|
||||||
|
hasPositionHistory: hasPositionHistory,
|
||||||
|
hasPosition: hasPosition,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GpsFunctionsDialog extends StatelessWidget {
|
||||||
|
final bool isFollowing;
|
||||||
|
final bool showGeofences;
|
||||||
|
final bool showFrequentPlaces;
|
||||||
|
final bool showRouteTrail;
|
||||||
|
final bool hasPositionHistory;
|
||||||
|
final bool hasPosition;
|
||||||
|
|
||||||
|
const _GpsFunctionsDialog({
|
||||||
|
required this.isFollowing,
|
||||||
|
required this.showGeofences,
|
||||||
|
required this.showFrequentPlaces,
|
||||||
|
required this.showRouteTrail,
|
||||||
|
required this.hasPositionHistory,
|
||||||
|
required this.hasPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: Center(
|
||||||
|
child: Text(
|
||||||
|
context.translate(I18n.gpsFunctionsTitle),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.only(top: 12, bottom: 8),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_FunctionTile(
|
||||||
|
icon: Icons.list_alt,
|
||||||
|
label: context.translate(I18n.gpsFunctionList),
|
||||||
|
onTap: () => Navigator.pop(context, GpsFunction.list),
|
||||||
|
),
|
||||||
|
_FunctionTile(
|
||||||
|
icon: Icons.add_location_alt,
|
||||||
|
label: context.translate(I18n.gpsFunctionAddGeofence),
|
||||||
|
onTap: () => Navigator.pop(context, GpsFunction.addGeofence),
|
||||||
|
visibilityIcon: showGeofences,
|
||||||
|
onVisibilityTap: () =>
|
||||||
|
Navigator.pop(context, GpsFunction.toggleGeofences),
|
||||||
|
),
|
||||||
|
_FunctionTile(
|
||||||
|
icon: Icons.add_home,
|
||||||
|
label: context.translate(I18n.gpsFunctionAddFrequentPlace),
|
||||||
|
onTap: () =>
|
||||||
|
Navigator.pop(context, GpsFunction.addFrequentPlace),
|
||||||
|
visibilityIcon: showFrequentPlaces,
|
||||||
|
onVisibilityTap: () =>
|
||||||
|
Navigator.pop(context, GpsFunction.toggleFrequentPlaces),
|
||||||
|
),
|
||||||
|
if (hasPosition)
|
||||||
|
_FunctionTile(
|
||||||
|
icon: Icons.share,
|
||||||
|
label: context.translate(I18n.gpsFunctionShare),
|
||||||
|
onTap: () => Navigator.pop(context, GpsFunction.share),
|
||||||
|
),
|
||||||
|
if (hasPosition)
|
||||||
|
_FunctionTile(
|
||||||
|
icon: isFollowing ? Icons.gps_fixed : Icons.gps_not_fixed,
|
||||||
|
label: context.translate(
|
||||||
|
isFollowing
|
||||||
|
? I18n.gpsFunctionStopFollow
|
||||||
|
: I18n.gpsFunctionFollow,
|
||||||
|
),
|
||||||
|
onTap: () => Navigator.pop(context, GpsFunction.toggleFollow),
|
||||||
|
),
|
||||||
|
_FunctionTile(
|
||||||
|
icon: Icons.route,
|
||||||
|
label: context.translate(I18n.locationLayerHistory),
|
||||||
|
onTap: () =>
|
||||||
|
Navigator.pop(context, GpsFunction.positionHistory),
|
||||||
|
visibilityIcon:
|
||||||
|
hasPositionHistory ? showRouteTrail : null,
|
||||||
|
onVisibilityTap: hasPositionHistory
|
||||||
|
? () =>
|
||||||
|
Navigator.pop(context, GpsFunction.toggleRouteTrail)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FunctionTile extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final bool? visibilityIcon;
|
||||||
|
final VoidCallback? onVisibilityTap;
|
||||||
|
|
||||||
|
const _FunctionTile({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.onTap,
|
||||||
|
this.visibilityIcon,
|
||||||
|
this.onVisibilityTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
dense: true,
|
||||||
|
leading: Icon(icon, color: primaryColor, size: 22),
|
||||||
|
title: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
trailing: visibilityIcon != null && onVisibilityTap != null
|
||||||
|
? GestureDetector(
|
||||||
|
onTap: onVisibilityTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Icon(
|
||||||
|
visibilityIcon! ? Icons.visibility : Icons.visibility_off,
|
||||||
|
size: 22,
|
||||||
|
color: visibilityIcon!
|
||||||
|
? primaryColor
|
||||||
|
: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.chevron_right, size: 20),
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
|
|
||||||
|
const _playbackIntervalSeconds = 3;
|
||||||
|
|
||||||
|
class HistoryPlayer extends StatelessWidget {
|
||||||
|
final int currentIndex;
|
||||||
|
final int total;
|
||||||
|
final bool isPlaying;
|
||||||
|
final PositionEntity currentPosition;
|
||||||
|
final VoidCallback onPrevious;
|
||||||
|
final VoidCallback onNext;
|
||||||
|
final VoidCallback onPlayPause;
|
||||||
|
final VoidCallback onClose;
|
||||||
|
|
||||||
|
const HistoryPlayer({
|
||||||
|
super.key,
|
||||||
|
required this.currentIndex,
|
||||||
|
required this.total,
|
||||||
|
required this.isPlaying,
|
||||||
|
required this.currentPosition,
|
||||||
|
required this.onPrevious,
|
||||||
|
required this.onNext,
|
||||||
|
required this.onPlayPause,
|
||||||
|
required this.onClose,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
|
final date = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
currentPosition.positionDate,
|
||||||
|
);
|
||||||
|
final dateText = DateFormat('dd/MM/yyyy HH:mm').format(date);
|
||||||
|
final addressParts = [
|
||||||
|
currentPosition.address?.street,
|
||||||
|
currentPosition.address?.city,
|
||||||
|
].where((s) => s != null && s.isNotEmpty && s != 'Unknown').join(', ');
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.15),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.location_on, color: primaryColor, size: 18),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
addressParts.isNotEmpty
|
||||||
|
? addressParts
|
||||||
|
: currentPosition.type,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onClose,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 20,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
Text(
|
||||||
|
'$dateText · ${currentPosition.type}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: currentIndex > 0 ? onPrevious : null,
|
||||||
|
icon: const Icon(Icons.skip_previous),
|
||||||
|
color: primaryColor,
|
||||||
|
iconSize: 28,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onPlayPause,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
if (isPlaying)
|
||||||
|
_CountdownRing(
|
||||||
|
key: ValueKey(currentIndex),
|
||||||
|
primaryColor: primaryColor,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: primaryColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
isPlaying ? Icons.pause : Icons.play_arrow,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 26,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
IconButton(
|
||||||
|
onPressed: currentIndex < total - 1 ? onNext : null,
|
||||||
|
icon: const Icon(Icons.skip_next),
|
||||||
|
color: primaryColor,
|
||||||
|
iconSize: 28,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
LinearProgressIndicator(
|
||||||
|
value: total > 1 ? currentIndex / (total - 1) : 0,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.outline.withValues(alpha: 0.2),
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(primaryColor),
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
minHeight: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${currentIndex + 1} / $total',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CountdownRing extends StatelessWidget {
|
||||||
|
final Color primaryColor;
|
||||||
|
|
||||||
|
const _CountdownRing({super.key, required this.primaryColor});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween(begin: 1.0, end: 0.0),
|
||||||
|
duration: const Duration(seconds: _playbackIntervalSeconds),
|
||||||
|
builder: (context, value, _) => SizedBox(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: value,
|
||||||
|
strokeWidth: 2.5,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(primaryColor),
|
||||||
|
backgroundColor: primaryColor.withValues(alpha: 0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
import 'package:location/src/features/location/presentation/providers/location_map_controller.dart';
|
import 'package:location/src/features/location/presentation/providers/location_map_controller.dart';
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
import 'package:utils/utils.dart';
|
import 'package:utils/utils.dart';
|
||||||
@@ -120,6 +121,7 @@ class FrequencySelector extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSegmented(BuildContext context, LocationMapController vm) {
|
Widget _buildSegmented(BuildContext context, LocationMapController vm) {
|
||||||
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
return Material(
|
return Material(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
@@ -142,9 +144,7 @@ class FrequencySelector extends ConsumerWidget {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 2),
|
margin: const EdgeInsets.symmetric(vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: selected
|
color: selected ? primaryColor : Colors.transparent,
|
||||||
? Theme.of(context).primaryColor
|
|
||||||
: Colors.transparent,
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -156,7 +156,7 @@ class FrequencySelector extends ConsumerWidget {
|
|||||||
fontSize: opt == -1 ? 10 : 12,
|
fontSize: opt == -1 ? 10 : 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: selected
|
color: selected
|
||||||
? Theme.of(context).colorScheme.surface
|
? Colors.white
|
||||||
: Theme.of(context).colorScheme.onSurface,
|
: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -167,10 +167,10 @@ class FrequencySelector extends ConsumerWidget {
|
|||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: vm.collapseFrequency,
|
onTap: vm.collapseFrequency,
|
||||||
child: const SizedBox(
|
child: SizedBox(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 24,
|
height: 24,
|
||||||
child: Icon(Icons.close, size: 16, color: Colors.black54),
|
child: Icon(Icons.close, size: 16, color: primaryColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ class MapStyleSelector extends ConsumerWidget {
|
|||||||
onTap: () =>
|
onTap: () =>
|
||||||
ref.read(mapStyleSelectorExpandedProvider.notifier).set(true),
|
ref.read(mapStyleSelectorExpandedProvider.notifier).set(true),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: const SizedBox(
|
child: SizedBox(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
child: Icon(Icons.layers, size: 22, color: Colors.grey),
|
child: Icon(Icons.layers, size: 22, color: primaryColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
|
class RevealProgressBar extends StatelessWidget {
|
||||||
|
final int current;
|
||||||
|
final int total;
|
||||||
|
final Color primaryColor;
|
||||||
|
final VoidCallback onSkip;
|
||||||
|
|
||||||
|
const RevealProgressBar({
|
||||||
|
super.key,
|
||||||
|
required this.current,
|
||||||
|
required this.total,
|
||||||
|
required this.primaryColor,
|
||||||
|
required this.onSkip,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.12),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.route, color: primaryColor, size: 20),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translate(I18n.locationRevealProgress),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
LinearProgressIndicator(
|
||||||
|
value: total > 1 ? current / total : 0,
|
||||||
|
backgroundColor: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline
|
||||||
|
.withValues(alpha: 0.2),
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(primaryColor),
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
minHeight: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'$current / $total',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onSkip,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: primaryColor.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
context.translate(I18n.locationRevealSkip),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,8 @@ class RouteHistoryLayer extends StatelessWidget {
|
|||||||
final double zoom;
|
final double zoom;
|
||||||
final ValueChanged<PositionEntity> onPositionTap;
|
final ValueChanged<PositionEntity> onPositionTap;
|
||||||
final void Function(LatLng point, double zoom) onClusterTap;
|
final void Function(LatLng point, double zoom) onClusterTap;
|
||||||
|
final int activeIndex;
|
||||||
|
final int? visibleCount;
|
||||||
|
|
||||||
const RouteHistoryLayer({
|
const RouteHistoryLayer({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -28,19 +30,56 @@ class RouteHistoryLayer extends StatelessWidget {
|
|||||||
required this.zoom,
|
required this.zoom,
|
||||||
required this.onPositionTap,
|
required this.onPositionTap,
|
||||||
required this.onClusterTap,
|
required this.onClusterTap,
|
||||||
|
this.activeIndex = -1,
|
||||||
|
this.visibleCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
List<PositionEntity> get _visiblePositions {
|
||||||
|
if (visibleCount == null) return positionHistory;
|
||||||
|
return positionHistory.take(visibleCount!).toList();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MarkerLayer(
|
return MarkerLayer(
|
||||||
markers: [..._buildDirectionArrows(), ..._buildHistoryMarkers()],
|
markers: [
|
||||||
|
..._buildDirectionArrows(),
|
||||||
|
..._buildHistoryMarkers(),
|
||||||
|
if (activeIndex >= 0 && activeIndex < positionHistory.length)
|
||||||
|
_buildActiveMarker(positionHistory[activeIndex]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marker _buildActiveMarker(PositionEntity position) {
|
||||||
|
return Marker(
|
||||||
|
point: LatLng(position.latitude, position.longitude),
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: routeArrowColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 3),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: routeArrowColor.withValues(alpha: 0.5),
|
||||||
|
blurRadius: 8,
|
||||||
|
spreadRadius: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.navigation, color: Colors.white, size: 20),
|
||||||
|
),
|
||||||
|
rotate: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Polyline> buildRouteSegments() {
|
List<Polyline> buildRouteSegments() {
|
||||||
if (positionHistory.length < 2) return [];
|
final visible = _visiblePositions;
|
||||||
|
if (visible.length < 2) return [];
|
||||||
|
|
||||||
final points = positionHistory
|
final points = visible
|
||||||
.where(_hasValidCoords)
|
.where(_hasValidCoords)
|
||||||
.map((p) => LatLng(p.latitude, p.longitude))
|
.map((p) => LatLng(p.latitude, p.longitude))
|
||||||
.toList();
|
.toList();
|
||||||
@@ -48,17 +87,18 @@ class RouteHistoryLayer extends StatelessWidget {
|
|||||||
if (points.length < 2) return [];
|
if (points.length < 2) return [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Polyline(points: points, color: routeColor, strokeWidth: 4.0),
|
Polyline(points: points, color: routeColor, strokeWidth: 5.0),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Marker> _buildDirectionArrows() {
|
List<Marker> _buildDirectionArrows() {
|
||||||
if (positionHistory.length < 2) return [];
|
final visible = _visiblePositions;
|
||||||
|
if (visible.length < 2) return [];
|
||||||
|
|
||||||
final arrows = <Marker>[];
|
final arrows = <Marker>[];
|
||||||
for (int i = 0; i < positionHistory.length - 1; i++) {
|
for (int i = 0; i < visible.length - 1; i++) {
|
||||||
final from = positionHistory[i];
|
final from = visible[i];
|
||||||
final to = positionHistory[i + 1];
|
final to = visible[i + 1];
|
||||||
if (!_hasValidCoords(from) || !_hasValidCoords(to)) continue;
|
if (!_hasValidCoords(from) || !_hasValidCoords(to)) continue;
|
||||||
|
|
||||||
final midLat = (from.latitude + to.latitude) / 2;
|
final midLat = (from.latitude + to.latitude) / 2;
|
||||||
@@ -89,14 +129,15 @@ class RouteHistoryLayer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Marker> _buildHistoryMarkers() {
|
List<Marker> _buildHistoryMarkers() {
|
||||||
if (positionHistory.isEmpty) return [];
|
final visible = _visiblePositions;
|
||||||
|
if (visible.isEmpty) return [];
|
||||||
|
|
||||||
final markers = <Marker>[];
|
final markers = <Marker>[];
|
||||||
final threshold =
|
final threshold =
|
||||||
_clusterBaseDegrees / math.pow(2, zoom - _clusterBaseZoom);
|
_clusterBaseDegrees / math.pow(2, zoom - _clusterBaseZoom);
|
||||||
|
|
||||||
final first = positionHistory.first;
|
final first = visible.first;
|
||||||
final last = positionHistory.last;
|
final last = visible.last;
|
||||||
|
|
||||||
markers.add(
|
markers.add(
|
||||||
_buildEndpointMarker(
|
_buildEndpointMarker(
|
||||||
@@ -105,7 +146,7 @@ class RouteHistoryLayer extends StatelessWidget {
|
|||||||
icon: Icons.play_arrow,
|
icon: Icons.play_arrow,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (positionHistory.length > 1) {
|
if (visible.length > 1) {
|
||||||
markers.add(
|
markers.add(
|
||||||
_buildEndpointMarker(
|
_buildEndpointMarker(
|
||||||
position: last,
|
position: last,
|
||||||
@@ -115,11 +156,11 @@ class RouteHistoryLayer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (positionHistory.length <= 2) return markers;
|
if (visible.length <= 2) return markers;
|
||||||
|
|
||||||
final intermediates = positionHistory.sublist(
|
final intermediates = visible.sublist(
|
||||||
1,
|
1,
|
||||||
positionHistory.length - 1,
|
visible.length - 1,
|
||||||
);
|
);
|
||||||
final clustered = <List<int>>[];
|
final clustered = <List<int>>[];
|
||||||
final visited = List.filled(intermediates.length, false);
|
final visited = List.filled(intermediates.length, false);
|
||||||
@@ -214,11 +255,11 @@ class RouteHistoryLayer extends StatelessWidget {
|
|||||||
}) {
|
}) {
|
||||||
return Marker(
|
return Marker(
|
||||||
point: LatLng(position.latitude, position.longitude),
|
point: LatLng(position.latitude, position.longitude),
|
||||||
width: 18,
|
width: 28,
|
||||||
height: 18,
|
height: 28,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => onPositionTap(position),
|
onTap: () => onPositionTap(position),
|
||||||
child: _CircleMarkerIcon(color: color, size: 18),
|
child: _CircleMarkerIcon(color: color, size: 28),
|
||||||
),
|
),
|
||||||
rotate: true,
|
rotate: true,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:location/src/core/domain/entities/geofence_entity.dart';
|
||||||
|
import 'package:location/src/core/domain/entities/frequent_place_entity.dart';
|
||||||
|
import 'package:location/src/features/location/presentation/providers/location_map_controller.dart';
|
||||||
|
import 'package:location/src/features/location/presentation/providers/location_map_state.dart';
|
||||||
|
import 'package:sf_shared/testing.dart';
|
||||||
|
import 'package:sf_tracking/sf_tracking.dart';
|
||||||
|
|
||||||
|
const _geofence = GeofenceEntity(
|
||||||
|
id: 'g1',
|
||||||
|
name: 'Home',
|
||||||
|
latitude: 40.0,
|
||||||
|
longitude: -3.0,
|
||||||
|
radius: 200,
|
||||||
|
isActive: true,
|
||||||
|
createdAt: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const _frequentPlace = FrequentPlaceEntity(
|
||||||
|
id: 'f1',
|
||||||
|
name: 'School',
|
||||||
|
lat: 41.0,
|
||||||
|
lng: -3.5,
|
||||||
|
createdAt: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
ProviderContainer buildContainer() {
|
||||||
|
return makeContainer(
|
||||||
|
overrides: [
|
||||||
|
sfTrackingProvider.overrideWithValue(
|
||||||
|
SfTrackingRepository(clients: const []),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationMapState readState(ProviderContainer container) =>
|
||||||
|
container.read(locationMapControllerProvider);
|
||||||
|
|
||||||
|
LocationMapController readNotifier(ProviderContainer container) =>
|
||||||
|
container.read(locationMapControllerProvider.notifier);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('history navigation', () {
|
||||||
|
test('startHistoryNavigation sets index to 0', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
|
||||||
|
expect(readState(container).historyNavigationIndex, 0);
|
||||||
|
expect(readState(container).historyPlaying, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stopHistoryNavigation resets index to -1', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).stopHistoryNavigation();
|
||||||
|
|
||||||
|
expect(readState(container).historyNavigationIndex, -1);
|
||||||
|
expect(readState(container).historyPlaying, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nextHistoryPosition increments index', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).nextHistoryPosition(10);
|
||||||
|
|
||||||
|
expect(readState(container).historyNavigationIndex, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nextHistoryPosition at end sets historyPlaying false', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).nextHistoryPosition(2);
|
||||||
|
readNotifier(container).nextHistoryPosition(2);
|
||||||
|
|
||||||
|
expect(readState(container).historyNavigationIndex, 1);
|
||||||
|
expect(readState(container).historyPlaying, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('previousHistoryPosition decrements index', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).nextHistoryPosition(10);
|
||||||
|
readNotifier(container).nextHistoryPosition(10);
|
||||||
|
readNotifier(container).previousHistoryPosition();
|
||||||
|
|
||||||
|
expect(readState(container).historyNavigationIndex, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('previousHistoryPosition at 0 stays at 0', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).previousHistoryPosition();
|
||||||
|
|
||||||
|
expect(readState(container).historyNavigationIndex, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('history playback', () {
|
||||||
|
test('toggleHistoryPlayback activates playback', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).toggleHistoryPlayback(10);
|
||||||
|
|
||||||
|
expect(readState(container).historyPlaying, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toggleHistoryPlayback deactivates playback', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).toggleHistoryPlayback(10);
|
||||||
|
readNotifier(container).toggleHistoryPlayback(10);
|
||||||
|
|
||||||
|
expect(readState(container).historyPlaying, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toggleHistoryPlayback at end restarts from 0', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).nextHistoryPosition(3);
|
||||||
|
readNotifier(container).nextHistoryPosition(3);
|
||||||
|
readNotifier(container).toggleHistoryPlayback(3);
|
||||||
|
|
||||||
|
expect(readState(container).historyNavigationIndex, 0);
|
||||||
|
expect(readState(container).historyPlaying, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stopPlaybackTimer cancels timer', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).toggleHistoryPlayback(10);
|
||||||
|
readNotifier(container).stopPlaybackTimer();
|
||||||
|
|
||||||
|
expect(readState(container).historyPlaying, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('startPlaybackTimer sets up timer and stopPlaybackTimer clears it', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startHistoryNavigation();
|
||||||
|
readNotifier(container).toggleHistoryPlayback(10);
|
||||||
|
|
||||||
|
expect(readState(container).historyPlaying, true);
|
||||||
|
|
||||||
|
readNotifier(container).stopPlaybackTimer();
|
||||||
|
readNotifier(container).stopPlaybackTimer();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('reveal animation', () {
|
||||||
|
test('startReveal sets animating true and count 1', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startReveal();
|
||||||
|
|
||||||
|
expect(readState(container).isRevealAnimating, true);
|
||||||
|
expect(readState(container).historyRevealCount, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateRevealCount updates count', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startReveal();
|
||||||
|
readNotifier(container).updateRevealCount(5);
|
||||||
|
|
||||||
|
expect(readState(container).historyRevealCount, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('finishReveal clears animation state', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startReveal();
|
||||||
|
readNotifier(container).finishReveal();
|
||||||
|
|
||||||
|
expect(readState(container).isRevealAnimating, false);
|
||||||
|
expect(readState(container).historyRevealCount, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('geofence editing', () {
|
||||||
|
test('startEditingGeofence enters placement mode', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startEditingGeofence(_geofence);
|
||||||
|
|
||||||
|
final state = readState(container);
|
||||||
|
expect(state.placingMode, PlacingMode.geofence);
|
||||||
|
expect(state.editingGeofence, _geofence);
|
||||||
|
expect(state.previewPoint, LatLng(40.0, -3.0));
|
||||||
|
expect(state.previewRadius, 200);
|
||||||
|
expect(state.selectedGeofence, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirmGeofencePlacement preserves editing radius', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startEditingGeofence(_geofence);
|
||||||
|
readNotifier(container).confirmGeofencePlacement(LatLng(41.0, -2.0));
|
||||||
|
|
||||||
|
final state = readState(container);
|
||||||
|
expect(state.placingMode, PlacingMode.none);
|
||||||
|
expect(state.adjustingRadius, true);
|
||||||
|
expect(state.previewPoint, LatLng(41.0, -2.0));
|
||||||
|
expect(state.previewRadius, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirmGeofencePlacement defaults radius 200 for new geofence', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startPlacing(PlacingMode.geofence);
|
||||||
|
readNotifier(container).confirmGeofencePlacement(LatLng(41.0, -2.0));
|
||||||
|
|
||||||
|
expect(readState(container).previewRadius, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancelPlacing clears editing state', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startEditingGeofence(_geofence);
|
||||||
|
readNotifier(container).cancelPlacing();
|
||||||
|
|
||||||
|
final state = readState(container);
|
||||||
|
expect(state.placingMode, PlacingMode.none);
|
||||||
|
expect(state.editingGeofence, isNull);
|
||||||
|
expect(state.editingFrequentPlace, isNull);
|
||||||
|
expect(state.previewPoint, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('frequent place editing', () {
|
||||||
|
test('startEditingFrequentPlace enters placement mode', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startEditingFrequentPlace(_frequentPlace);
|
||||||
|
|
||||||
|
final state = readState(container);
|
||||||
|
expect(state.placingMode, PlacingMode.frequentPlace);
|
||||||
|
expect(state.editingFrequentPlace, _frequentPlace);
|
||||||
|
expect(state.previewPoint, LatLng(41.0, -3.5));
|
||||||
|
expect(state.selectedFrequentPlace, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirmFrequentPlacePlacement clears editing state', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).startEditingFrequentPlace(_frequentPlace);
|
||||||
|
readNotifier(container).confirmFrequentPlacePlacement();
|
||||||
|
|
||||||
|
final state = readState(container);
|
||||||
|
expect(state.placingMode, PlacingMode.none);
|
||||||
|
expect(state.editingFrequentPlace, isNull);
|
||||||
|
expect(state.previewPoint, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('recenter', () {
|
||||||
|
test('scheduleRecenter does nothing when not following', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
expect(readState(container).isFollowing, false);
|
||||||
|
|
||||||
|
var called = false;
|
||||||
|
readNotifier(container).scheduleRecenter(() => called = true);
|
||||||
|
|
||||||
|
expect(called, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancelRecenter does not throw', () {
|
||||||
|
final container = buildContainer();
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
readNotifier(container).toggleFollowing();
|
||||||
|
readNotifier(container).scheduleRecenter(() {});
|
||||||
|
readNotifier(container).cancelRecenter();
|
||||||
|
readNotifier(container).cancelRecenter();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:legacy_theme/legacy_theme.dart';
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
@@ -98,7 +97,7 @@ class _AlarmFormSheet extends ConsumerWidget {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: formState.canSave
|
color: formState.canSave
|
||||||
? primaryColor
|
? primaryColor
|
||||||
: Colors.grey,
|
: Theme.of(context).colorScheme.outline,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize:
|
fontSize:
|
||||||
SizeUtils.getByScreen(small: 16, big: 17),
|
SizeUtils.getByScreen(small: 16, big: 17),
|
||||||
@@ -108,20 +107,44 @@ class _AlarmFormSheet extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||||
Container(
|
GestureDetector(
|
||||||
height: SizeUtils.getByScreen(small: 160, big: 180),
|
onTap: () async {
|
||||||
decoration: BoxDecoration(
|
final picked = await showTimePicker(
|
||||||
borderRadius:
|
context: context,
|
||||||
const BorderRadius.all(Radius.circular(16)),
|
initialTime: TimeOfDay(
|
||||||
color: Colors.grey.shade50,
|
hour: formState.durationMinutes ~/ 60,
|
||||||
),
|
minute: formState.durationMinutes % 60,
|
||||||
child: CupertinoTimerPicker(
|
),
|
||||||
mode: CupertinoTimerPickerMode.hm,
|
builder: legacyPickerThemeBuilder,
|
||||||
initialTimerDuration: Duration(
|
);
|
||||||
minutes: formState.durationMinutes,
|
if (picked != null) {
|
||||||
|
notifier.setDurationMinutes(
|
||||||
|
picked.hour * 60 + picked.minute,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: SizeUtils.getByScreen(small: 24, big: 28),
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.all(Radius.circular(16)),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainerHighest,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'${(formState.durationMinutes ~/ 60).toString().padLeft(2, '0')}:${(formState.durationMinutes % 60).toString().padLeft(2, '0')}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 48,
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTimerDurationChanged: (duration) =>
|
|
||||||
notifier.setDurationMinutes(duration.inMinutes),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 22)),
|
SizedBox(height: SizeUtils.getByScreen(small: 20, big: 22)),
|
||||||
@@ -197,7 +220,9 @@ class _RadioOption extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
isSelected ? Icons.radio_button_checked : Icons.radio_button_off,
|
isSelected ? Icons.radio_button_checked : Icons.radio_button_off,
|
||||||
color: isSelected ? primaryColor : Colors.grey,
|
color: isSelected
|
||||||
|
? primaryColor
|
||||||
|
: Theme.of(context).colorScheme.outline,
|
||||||
size: SizeUtils.getByScreen(small: 22, big: 24),
|
size: SizeUtils.getByScreen(small: 22, big: 24),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import 'package:sf_localizations/sf_localizations.dart';
|
|||||||
import 'package:sf_shared/sf_shared.dart';
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
const _languageOptions = <String, String>{
|
const _languageOptions = <String, String>{
|
||||||
'es': 'español',
|
'es': 'Español',
|
||||||
'en': 'English',
|
'en': 'English',
|
||||||
'pt': 'português',
|
'pt': 'Português',
|
||||||
'it': 'italiano',
|
'it': 'Italiano',
|
||||||
'fr': 'français',
|
'fr': 'Français',
|
||||||
'de': 'Deutsch',
|
'de': 'Deutsch',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ export 'src/widgets/refreshable_error_state.dart';
|
|||||||
export 'src/widgets/week_day_chips.dart';
|
export 'src/widgets/week_day_chips.dart';
|
||||||
export 'src/providers/map_style_provider.dart';
|
export 'src/providers/map_style_provider.dart';
|
||||||
export 'src/transitions/legacy_transitions.dart';
|
export 'src/transitions/legacy_transitions.dart';
|
||||||
|
export 'src/utils/legacy_picker_theme.dart';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
|
|
||||||
const _duration = Duration(milliseconds: 300);
|
const _duration = Duration(milliseconds: 300);
|
||||||
const _dialogDuration = Duration(milliseconds: 500);
|
const _dialogDuration = Duration(milliseconds: 500);
|
||||||
@@ -8,13 +9,24 @@ Future<T?> showLegacyDialog<T>({
|
|||||||
required WidgetBuilder builder,
|
required WidgetBuilder builder,
|
||||||
bool barrierDismissible = true,
|
bool barrierDismissible = true,
|
||||||
}) {
|
}) {
|
||||||
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
|
final brightness = Theme.of(context).brightness;
|
||||||
|
|
||||||
return showGeneralDialog<T>(
|
return showGeneralDialog<T>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: barrierDismissible,
|
barrierDismissible: barrierDismissible,
|
||||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
barrierColor: Colors.black54,
|
barrierColor: Colors.black54,
|
||||||
transitionDuration: _dialogDuration,
|
transitionDuration: _dialogDuration,
|
||||||
pageBuilder: (dialogContext, __, ___) => builder(dialogContext),
|
pageBuilder: (dialogContext, __, ___) {
|
||||||
|
final colorScheme = brightness == Brightness.dark
|
||||||
|
? ColorScheme.dark(primary: primaryColor)
|
||||||
|
: ColorScheme.light(primary: primaryColor);
|
||||||
|
return Theme(
|
||||||
|
data: Theme.of(dialogContext).copyWith(colorScheme: colorScheme),
|
||||||
|
child: builder(dialogContext),
|
||||||
|
);
|
||||||
|
},
|
||||||
transitionBuilder: (_, animation, __, child) {
|
transitionBuilder: (_, animation, __, child) {
|
||||||
final fadeCurved = CurvedAnimation(
|
final fadeCurved = CurvedAnimation(
|
||||||
parent: animation,
|
parent: animation,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:legacy_theme/legacy_theme.dart';
|
||||||
|
|
||||||
|
Widget legacyPickerThemeBuilder(BuildContext context, Widget? child) {
|
||||||
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
|
final brightness = Theme.of(context).brightness;
|
||||||
|
final colorScheme = brightness == Brightness.dark
|
||||||
|
? ColorScheme.dark(primary: primaryColor)
|
||||||
|
: ColorScheme.light(primary: primaryColor);
|
||||||
|
return Theme(
|
||||||
|
data: Theme.of(context).copyWith(colorScheme: colorScheme),
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -667,6 +667,22 @@
|
|||||||
"locationBannerAddress": "ADRESSE",
|
"locationBannerAddress": "ADRESSE",
|
||||||
"locationBannerDateTime": "DATUM/UHRZEIT",
|
"locationBannerDateTime": "DATUM/UHRZEIT",
|
||||||
"locationBannerBattery": "BATTERIE",
|
"locationBannerBattery": "BATTERIE",
|
||||||
|
"gpsFunctionsTitle": "Einstellungen",
|
||||||
|
"gpsFunctionList": "Liste anzeigen",
|
||||||
|
"gpsFunctionAddGeofence": "Sichere Zone hinzufügen",
|
||||||
|
"gpsFunctionAddFrequentPlace": "Häufigen Ort hinzufügen",
|
||||||
|
"gpsFunctionShare": "Standort teilen",
|
||||||
|
"gpsFunctionCenter": "Auf Gerät zentrieren",
|
||||||
|
"gpsFunctionFollow": "Auf der Karte zentriert halten",
|
||||||
|
"gpsFunctionStopFollow": "Zentrierung auf der Karte beenden",
|
||||||
|
"gpsFunctionFrequency": "Ortungsfrequenz",
|
||||||
|
"locationHistoryLoading": "Positionsverlauf wird geladen...",
|
||||||
|
"locationHistoryEmpty": "Keine Positionen im ausgewählten Zeitraum gefunden",
|
||||||
|
"locationHistoryLoaded": "{count} Positionen gefunden",
|
||||||
|
"locationRevealProgress": "Route wird gezeichnet...",
|
||||||
|
"locationRevealSkip": "Überspringen",
|
||||||
|
"locationDeleteGeofenceConfirm": "Möchten Sie diese sichere Zone wirklich löschen?",
|
||||||
|
"locationDeleteFrequentPlaceConfirm": "Möchten Sie diesen häufigen Ort wirklich löschen?",
|
||||||
"positionUpdated": "Letzte verfügbare Position aktualisiert",
|
"positionUpdated": "Letzte verfügbare Position aktualisiert",
|
||||||
"locationMapStyleLight": "Hell",
|
"locationMapStyleLight": "Hell",
|
||||||
"locationMapStyleDark": "Dunkel",
|
"locationMapStyleDark": "Dunkel",
|
||||||
@@ -698,6 +714,7 @@
|
|||||||
"locationListFrequentPlaces": "Häufige Orte",
|
"locationListFrequentPlaces": "Häufige Orte",
|
||||||
"locationListPositionHistory": "Positionsverlauf",
|
"locationListPositionHistory": "Positionsverlauf",
|
||||||
"locationListNoItems": "Keine Elemente vorhanden",
|
"locationListNoItems": "Keine Elemente vorhanden",
|
||||||
|
"locationListHiddenItems": "Es gibt ausgeblendete Elemente.\nAktivieren Sie deren Sichtbarkeit in den Einstellungen, um sie hier zu sehen.",
|
||||||
"locationListAll": "Alle",
|
"locationListAll": "Alle",
|
||||||
"locationHistoryPosition": "Verlaufsposition",
|
"locationHistoryPosition": "Verlaufsposition",
|
||||||
"locationDate": "Datum",
|
"locationDate": "Datum",
|
||||||
|
|||||||
@@ -847,6 +847,22 @@
|
|||||||
"locationBannerAddress": "ADDRESS",
|
"locationBannerAddress": "ADDRESS",
|
||||||
"locationBannerDateTime": "DATE/TIME",
|
"locationBannerDateTime": "DATE/TIME",
|
||||||
"locationBannerBattery": "BATTERY",
|
"locationBannerBattery": "BATTERY",
|
||||||
|
"gpsFunctionsTitle": "Settings",
|
||||||
|
"gpsFunctionList": "View list",
|
||||||
|
"gpsFunctionAddGeofence": "Add safe zone",
|
||||||
|
"gpsFunctionAddFrequentPlace": "Add frequent place",
|
||||||
|
"gpsFunctionShare": "Share location",
|
||||||
|
"gpsFunctionCenter": "Center on device",
|
||||||
|
"gpsFunctionFollow": "Keep centered on map",
|
||||||
|
"gpsFunctionStopFollow": "Stop centering on map",
|
||||||
|
"gpsFunctionFrequency": "Location frequency",
|
||||||
|
"locationHistoryLoading": "Loading position history...",
|
||||||
|
"locationHistoryEmpty": "No positions found in the selected range",
|
||||||
|
"locationHistoryLoaded": "{count} positions found",
|
||||||
|
"locationRevealProgress": "Drawing route...",
|
||||||
|
"locationRevealSkip": "Skip",
|
||||||
|
"locationDeleteGeofenceConfirm": "Are you sure you want to delete this safe zone?",
|
||||||
|
"locationDeleteFrequentPlaceConfirm": "Are you sure you want to delete this frequent place?",
|
||||||
"positionUpdated": "Updated to latest available position",
|
"positionUpdated": "Updated to latest available position",
|
||||||
"locationMapStyleLight": "Light",
|
"locationMapStyleLight": "Light",
|
||||||
"locationMapStyleDark": "Dark",
|
"locationMapStyleDark": "Dark",
|
||||||
@@ -878,6 +894,7 @@
|
|||||||
"locationListFrequentPlaces": "Frequent places",
|
"locationListFrequentPlaces": "Frequent places",
|
||||||
"locationListPositionHistory": "Position history",
|
"locationListPositionHistory": "Position history",
|
||||||
"locationListNoItems": "No items to show",
|
"locationListNoItems": "No items to show",
|
||||||
|
"locationListHiddenItems": "There are hidden items.\nEnable their visibility from Settings to see them here.",
|
||||||
"locationListAll": "All",
|
"locationListAll": "All",
|
||||||
"locationHistoryPosition": "History position",
|
"locationHistoryPosition": "History position",
|
||||||
"locationDate": "Date",
|
"locationDate": "Date",
|
||||||
|
|||||||
@@ -848,6 +848,22 @@
|
|||||||
"locationBannerAddress": "DIRECCIÓN",
|
"locationBannerAddress": "DIRECCIÓN",
|
||||||
"locationBannerDateTime": "FECHA/HORA",
|
"locationBannerDateTime": "FECHA/HORA",
|
||||||
"locationBannerBattery": "BATERÍA",
|
"locationBannerBattery": "BATERÍA",
|
||||||
|
"gpsFunctionsTitle": "Configuraciones",
|
||||||
|
"gpsFunctionList": "Ver listado",
|
||||||
|
"gpsFunctionAddGeofence": "Agregar zona segura",
|
||||||
|
"gpsFunctionAddFrequentPlace": "Agregar lugar frecuente",
|
||||||
|
"gpsFunctionShare": "Compartir ubicación",
|
||||||
|
"gpsFunctionCenter": "Centrar en dispositivo",
|
||||||
|
"gpsFunctionFollow": "Mantener centrado en el mapa",
|
||||||
|
"gpsFunctionStopFollow": "Dejar de centrar en el mapa",
|
||||||
|
"gpsFunctionFrequency": "Frecuencia de localización",
|
||||||
|
"locationHistoryLoading": "Cargando historial de posiciones...",
|
||||||
|
"locationHistoryEmpty": "No se encontraron posiciones en el rango seleccionado",
|
||||||
|
"locationHistoryLoaded": "Se encontraron {count} posiciones",
|
||||||
|
"locationRevealProgress": "Trazando ruta...",
|
||||||
|
"locationRevealSkip": "Saltar",
|
||||||
|
"locationDeleteGeofenceConfirm": "¿Seguro que quieres eliminar esta zona segura?",
|
||||||
|
"locationDeleteFrequentPlaceConfirm": "¿Seguro que quieres eliminar este lugar frecuente?",
|
||||||
"positionUpdated": "Última posición disponible actualizada",
|
"positionUpdated": "Última posición disponible actualizada",
|
||||||
"locationMapStyleLight": "Claro",
|
"locationMapStyleLight": "Claro",
|
||||||
"locationMapStyleDark": "Oscuro",
|
"locationMapStyleDark": "Oscuro",
|
||||||
@@ -879,6 +895,7 @@
|
|||||||
"locationListFrequentPlaces": "Lugares frecuentes",
|
"locationListFrequentPlaces": "Lugares frecuentes",
|
||||||
"locationListPositionHistory": "Historial de posiciones",
|
"locationListPositionHistory": "Historial de posiciones",
|
||||||
"locationListNoItems": "No hay elementos para mostrar",
|
"locationListNoItems": "No hay elementos para mostrar",
|
||||||
|
"locationListHiddenItems": "Hay elementos ocultos.\nActiva su visibilidad desde Configuraciones para verlos aquí.",
|
||||||
"locationListAll": "Todos",
|
"locationListAll": "Todos",
|
||||||
"locationHistoryPosition": "Posición del historial",
|
"locationHistoryPosition": "Posición del historial",
|
||||||
"locationDate": "Fecha",
|
"locationDate": "Fecha",
|
||||||
|
|||||||
@@ -667,6 +667,22 @@
|
|||||||
"locationBannerAddress": "ADRESSE",
|
"locationBannerAddress": "ADRESSE",
|
||||||
"locationBannerDateTime": "DATE/HEURE",
|
"locationBannerDateTime": "DATE/HEURE",
|
||||||
"locationBannerBattery": "BATTERIE",
|
"locationBannerBattery": "BATTERIE",
|
||||||
|
"gpsFunctionsTitle": "Paramètres",
|
||||||
|
"gpsFunctionList": "Voir la liste",
|
||||||
|
"gpsFunctionAddGeofence": "Ajouter une zone sûre",
|
||||||
|
"gpsFunctionAddFrequentPlace": "Ajouter un lieu fréquent",
|
||||||
|
"gpsFunctionShare": "Partager la position",
|
||||||
|
"gpsFunctionCenter": "Centrer sur l'appareil",
|
||||||
|
"gpsFunctionFollow": "Maintenir centré sur la carte",
|
||||||
|
"gpsFunctionStopFollow": "Arrêter de centrer sur la carte",
|
||||||
|
"gpsFunctionFrequency": "Fréquence de localisation",
|
||||||
|
"locationHistoryLoading": "Chargement de l'historique des positions...",
|
||||||
|
"locationHistoryEmpty": "Aucune position trouvée dans la plage sélectionnée",
|
||||||
|
"locationHistoryLoaded": "{count} positions trouvées",
|
||||||
|
"locationRevealProgress": "Tracé de l'itinéraire...",
|
||||||
|
"locationRevealSkip": "Passer",
|
||||||
|
"locationDeleteGeofenceConfirm": "Voulez-vous supprimer cette zone sûre ?",
|
||||||
|
"locationDeleteFrequentPlaceConfirm": "Voulez-vous supprimer ce lieu fréquent ?",
|
||||||
"positionUpdated": "Dernière position disponible mise à jour",
|
"positionUpdated": "Dernière position disponible mise à jour",
|
||||||
"locationMapStyleLight": "Clair",
|
"locationMapStyleLight": "Clair",
|
||||||
"locationMapStyleDark": "Sombre",
|
"locationMapStyleDark": "Sombre",
|
||||||
@@ -698,6 +714,7 @@
|
|||||||
"locationListFrequentPlaces": "Lieux fréquents",
|
"locationListFrequentPlaces": "Lieux fréquents",
|
||||||
"locationListPositionHistory": "Historique des positions",
|
"locationListPositionHistory": "Historique des positions",
|
||||||
"locationListNoItems": "Aucun élément à afficher",
|
"locationListNoItems": "Aucun élément à afficher",
|
||||||
|
"locationListHiddenItems": "Des éléments sont masqués.\nActivez leur visibilité depuis les Paramètres pour les voir ici.",
|
||||||
"locationListAll": "Tous",
|
"locationListAll": "Tous",
|
||||||
"locationHistoryPosition": "Position de l'historique",
|
"locationHistoryPosition": "Position de l'historique",
|
||||||
"locationDate": "Date",
|
"locationDate": "Date",
|
||||||
|
|||||||
@@ -667,6 +667,22 @@
|
|||||||
"locationBannerAddress": "INDIRIZZO",
|
"locationBannerAddress": "INDIRIZZO",
|
||||||
"locationBannerDateTime": "DATA/ORA",
|
"locationBannerDateTime": "DATA/ORA",
|
||||||
"locationBannerBattery": "BATTERIA",
|
"locationBannerBattery": "BATTERIA",
|
||||||
|
"gpsFunctionsTitle": "Impostazioni",
|
||||||
|
"gpsFunctionList": "Visualizza elenco",
|
||||||
|
"gpsFunctionAddGeofence": "Aggiungi zona sicura",
|
||||||
|
"gpsFunctionAddFrequentPlace": "Aggiungi luogo frequente",
|
||||||
|
"gpsFunctionShare": "Condividi posizione",
|
||||||
|
"gpsFunctionCenter": "Centra sul dispositivo",
|
||||||
|
"gpsFunctionFollow": "Mantieni centrato sulla mappa",
|
||||||
|
"gpsFunctionStopFollow": "Smetti di centrare sulla mappa",
|
||||||
|
"gpsFunctionFrequency": "Frequenza di localizzazione",
|
||||||
|
"locationHistoryLoading": "Caricamento cronologia posizioni...",
|
||||||
|
"locationHistoryEmpty": "Nessuna posizione trovata nell'intervallo selezionato",
|
||||||
|
"locationHistoryLoaded": "{count} posizioni trovate",
|
||||||
|
"locationRevealProgress": "Tracciamento percorso...",
|
||||||
|
"locationRevealSkip": "Salta",
|
||||||
|
"locationDeleteGeofenceConfirm": "Sei sicuro di voler eliminare questa zona sicura?",
|
||||||
|
"locationDeleteFrequentPlaceConfirm": "Sei sicuro di voler eliminare questo luogo frequente?",
|
||||||
"positionUpdated": "Ultima posizione disponibile aggiornata",
|
"positionUpdated": "Ultima posizione disponibile aggiornata",
|
||||||
"locationMapStyleLight": "Chiaro",
|
"locationMapStyleLight": "Chiaro",
|
||||||
"locationMapStyleDark": "Scuro",
|
"locationMapStyleDark": "Scuro",
|
||||||
@@ -698,6 +714,7 @@
|
|||||||
"locationListFrequentPlaces": "Luoghi frequenti",
|
"locationListFrequentPlaces": "Luoghi frequenti",
|
||||||
"locationListPositionHistory": "Cronologia posizioni",
|
"locationListPositionHistory": "Cronologia posizioni",
|
||||||
"locationListNoItems": "Nessun elemento da mostrare",
|
"locationListNoItems": "Nessun elemento da mostrare",
|
||||||
|
"locationListHiddenItems": "Ci sono elementi nascosti.\nAttiva la loro visibilità dalle Impostazioni per vederli qui.",
|
||||||
"locationListAll": "Tutti",
|
"locationListAll": "Tutti",
|
||||||
"locationHistoryPosition": "Posizione della cronologia",
|
"locationHistoryPosition": "Posizione della cronologia",
|
||||||
"locationDate": "Data",
|
"locationDate": "Data",
|
||||||
|
|||||||
@@ -667,6 +667,22 @@
|
|||||||
"locationBannerAddress": "ENDEREÇO",
|
"locationBannerAddress": "ENDEREÇO",
|
||||||
"locationBannerDateTime": "DATA/HORA",
|
"locationBannerDateTime": "DATA/HORA",
|
||||||
"locationBannerBattery": "BATERIA",
|
"locationBannerBattery": "BATERIA",
|
||||||
|
"gpsFunctionsTitle": "Configurações",
|
||||||
|
"gpsFunctionList": "Ver lista",
|
||||||
|
"gpsFunctionAddGeofence": "Adicionar zona segura",
|
||||||
|
"gpsFunctionAddFrequentPlace": "Adicionar lugar frequente",
|
||||||
|
"gpsFunctionShare": "Partilhar localização",
|
||||||
|
"gpsFunctionCenter": "Centrar no dispositivo",
|
||||||
|
"gpsFunctionFollow": "Manter centrado no mapa",
|
||||||
|
"gpsFunctionStopFollow": "Parar de centrar no mapa",
|
||||||
|
"gpsFunctionFrequency": "Frequência de localização",
|
||||||
|
"locationHistoryLoading": "A carregar histórico de posições...",
|
||||||
|
"locationHistoryEmpty": "Nenhuma posição encontrada no intervalo selecionado",
|
||||||
|
"locationHistoryLoaded": "{count} posições encontradas",
|
||||||
|
"locationRevealProgress": "Traçando rota...",
|
||||||
|
"locationRevealSkip": "Saltar",
|
||||||
|
"locationDeleteGeofenceConfirm": "Tens a certeza que queres eliminar esta zona segura?",
|
||||||
|
"locationDeleteFrequentPlaceConfirm": "Tens a certeza que queres eliminar este lugar frequente?",
|
||||||
"positionUpdated": "Última posição disponível atualizada",
|
"positionUpdated": "Última posição disponível atualizada",
|
||||||
"locationMapStyleLight": "Claro",
|
"locationMapStyleLight": "Claro",
|
||||||
"locationMapStyleDark": "Escuro",
|
"locationMapStyleDark": "Escuro",
|
||||||
@@ -698,6 +714,7 @@
|
|||||||
"locationListFrequentPlaces": "Locais frequentes",
|
"locationListFrequentPlaces": "Locais frequentes",
|
||||||
"locationListPositionHistory": "Histórico de posições",
|
"locationListPositionHistory": "Histórico de posições",
|
||||||
"locationListNoItems": "Nenhum item para mostrar",
|
"locationListNoItems": "Nenhum item para mostrar",
|
||||||
|
"locationListHiddenItems": "Existem itens ocultos.\nAtive a sua visibilidade nas Configurações para os ver aqui.",
|
||||||
"locationListAll": "Todos",
|
"locationListAll": "Todos",
|
||||||
"locationHistoryPosition": "Posição do histórico",
|
"locationHistoryPosition": "Posição do histórico",
|
||||||
"locationDate": "Data",
|
"locationDate": "Data",
|
||||||
|
|||||||
@@ -623,6 +623,23 @@ class I18n {
|
|||||||
static const String locationBannerAddress = 'locationBannerAddress';
|
static const String locationBannerAddress = 'locationBannerAddress';
|
||||||
static const String locationBannerDateTime = 'locationBannerDateTime';
|
static const String locationBannerDateTime = 'locationBannerDateTime';
|
||||||
static const String locationBannerBattery = 'locationBannerBattery';
|
static const String locationBannerBattery = 'locationBannerBattery';
|
||||||
|
static const String gpsFunctionsTitle = 'gpsFunctionsTitle';
|
||||||
|
static const String gpsFunctionList = 'gpsFunctionList';
|
||||||
|
static const String gpsFunctionAddGeofence = 'gpsFunctionAddGeofence';
|
||||||
|
static const String gpsFunctionAddFrequentPlace = 'gpsFunctionAddFrequentPlace';
|
||||||
|
static const String gpsFunctionShare = 'gpsFunctionShare';
|
||||||
|
static const String gpsFunctionCenter = 'gpsFunctionCenter';
|
||||||
|
static const String gpsFunctionFollow = 'gpsFunctionFollow';
|
||||||
|
static const String gpsFunctionStopFollow = 'gpsFunctionStopFollow';
|
||||||
|
static const String gpsFunctionFrequency = 'gpsFunctionFrequency';
|
||||||
|
static const String locationHistoryLoading = 'locationHistoryLoading';
|
||||||
|
static const String locationHistoryEmpty = 'locationHistoryEmpty';
|
||||||
|
static const String locationHistoryLoaded = 'locationHistoryLoaded';
|
||||||
|
static const String locationRevealProgress = 'locationRevealProgress';
|
||||||
|
static const String locationRevealSkip = 'locationRevealSkip';
|
||||||
|
static const String locationDeleteGeofenceConfirm = 'locationDeleteGeofenceConfirm';
|
||||||
|
static const String locationDeleteFrequentPlaceConfirm = 'locationDeleteFrequentPlaceConfirm';
|
||||||
|
static const String locationListHiddenItems = 'locationListHiddenItems';
|
||||||
static const String positionUpdated = 'positionUpdated';
|
static const String positionUpdated = 'positionUpdated';
|
||||||
static const String locationNewFrequentPlace = 'locationNewFrequentPlace';
|
static const String locationNewFrequentPlace = 'locationNewFrequentPlace';
|
||||||
static const String locationNewGeofence = 'locationNewGeofence';
|
static const String locationNewGeofence = 'locationNewGeofence';
|
||||||
|
|||||||
@@ -1,17 +1,39 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
enum _FeedbackKind { error, success, info }
|
enum _FeedbackKind { error, success, info }
|
||||||
|
|
||||||
|
final _queue = Queue<_FeedbackRequest>();
|
||||||
|
bool _isShowing = false;
|
||||||
|
|
||||||
|
class _FeedbackRequest {
|
||||||
|
final BuildContext context;
|
||||||
|
final String messageKey;
|
||||||
|
final Map<String, dynamic>? args;
|
||||||
|
final _FeedbackKind kind;
|
||||||
|
final Duration autoDismiss;
|
||||||
|
final Completer<void> completer;
|
||||||
|
|
||||||
|
_FeedbackRequest({
|
||||||
|
required this.context,
|
||||||
|
required this.messageKey,
|
||||||
|
required this.args,
|
||||||
|
required this.kind,
|
||||||
|
required this.autoDismiss,
|
||||||
|
required this.completer,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> showErrorDialog(
|
Future<void> showErrorDialog(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String messageKey, {
|
String messageKey, {
|
||||||
Map<String, dynamic>? args,
|
Map<String, dynamic>? args,
|
||||||
Duration autoDismiss = const Duration(seconds: 3),
|
Duration autoDismiss = const Duration(seconds: 3),
|
||||||
}) {
|
}) {
|
||||||
return _showPillFeedback(
|
return _enqueue(
|
||||||
context,
|
context,
|
||||||
messageKey: messageKey,
|
messageKey: messageKey,
|
||||||
args: args,
|
args: args,
|
||||||
@@ -26,7 +48,7 @@ Future<void> showSuccessDialog(
|
|||||||
Map<String, dynamic>? args,
|
Map<String, dynamic>? args,
|
||||||
Duration autoDismiss = const Duration(seconds: 3),
|
Duration autoDismiss = const Duration(seconds: 3),
|
||||||
}) {
|
}) {
|
||||||
return _showPillFeedback(
|
return _enqueue(
|
||||||
context,
|
context,
|
||||||
messageKey: messageKey,
|
messageKey: messageKey,
|
||||||
args: args,
|
args: args,
|
||||||
@@ -41,7 +63,7 @@ Future<void> showInfoDialog(
|
|||||||
Map<String, dynamic>? args,
|
Map<String, dynamic>? args,
|
||||||
Duration autoDismiss = const Duration(seconds: 3),
|
Duration autoDismiss = const Duration(seconds: 3),
|
||||||
}) {
|
}) {
|
||||||
return _showPillFeedback(
|
return _enqueue(
|
||||||
context,
|
context,
|
||||||
messageKey: messageKey,
|
messageKey: messageKey,
|
||||||
args: args,
|
args: args,
|
||||||
@@ -50,6 +72,50 @@ Future<void> showInfoDialog(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _enqueue(
|
||||||
|
BuildContext context, {
|
||||||
|
required String messageKey,
|
||||||
|
required Map<String, dynamic>? args,
|
||||||
|
required _FeedbackKind kind,
|
||||||
|
required Duration autoDismiss,
|
||||||
|
}) {
|
||||||
|
final completer = Completer<void>();
|
||||||
|
_queue.add(_FeedbackRequest(
|
||||||
|
context: context,
|
||||||
|
messageKey: messageKey,
|
||||||
|
args: args,
|
||||||
|
kind: kind,
|
||||||
|
autoDismiss: autoDismiss,
|
||||||
|
completer: completer,
|
||||||
|
));
|
||||||
|
_processQueue();
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _processQueue() async {
|
||||||
|
if (_isShowing || _queue.isEmpty) return;
|
||||||
|
|
||||||
|
while (_queue.isNotEmpty && !_queue.first.context.mounted) {
|
||||||
|
_queue.removeFirst().completer.complete();
|
||||||
|
}
|
||||||
|
if (_queue.isEmpty) return;
|
||||||
|
|
||||||
|
_isShowing = true;
|
||||||
|
final request = _queue.removeFirst();
|
||||||
|
|
||||||
|
await _showPillFeedback(
|
||||||
|
request.context,
|
||||||
|
messageKey: request.messageKey,
|
||||||
|
args: request.args,
|
||||||
|
kind: request.kind,
|
||||||
|
autoDismiss: request.autoDismiss,
|
||||||
|
);
|
||||||
|
|
||||||
|
request.completer.complete();
|
||||||
|
_isShowing = false;
|
||||||
|
_processQueue();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _showPillFeedback(
|
Future<void> _showPillFeedback(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String messageKey,
|
required String messageKey,
|
||||||
@@ -57,6 +123,7 @@ Future<void> _showPillFeedback(
|
|||||||
required _FeedbackKind kind,
|
required _FeedbackKind kind,
|
||||||
required Duration autoDismiss,
|
required Duration autoDismiss,
|
||||||
}) async {
|
}) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
final overlay = Overlay.of(context);
|
final overlay = Overlay.of(context);
|
||||||
final resolved = context.translate(messageKey, args: args);
|
final resolved = context.translate(messageKey, args: args);
|
||||||
|
|
||||||
@@ -78,6 +145,7 @@ Future<void> _showPillFeedback(
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final completer = Completer<void>();
|
||||||
late final OverlayEntry entry;
|
late final OverlayEntry entry;
|
||||||
late final AnimationController controller;
|
late final AnimationController controller;
|
||||||
|
|
||||||
@@ -93,9 +161,11 @@ Future<void> _showPillFeedback(
|
|||||||
);
|
);
|
||||||
|
|
||||||
void dismiss() {
|
void dismiss() {
|
||||||
|
if (completer.isCompleted) return;
|
||||||
controller.reverse().then((_) {
|
controller.reverse().then((_) {
|
||||||
entry.remove();
|
entry.remove();
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
|
completer.complete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,8 +262,7 @@ Future<void> _showPillFeedback(
|
|||||||
controller.forward();
|
controller.forward();
|
||||||
|
|
||||||
await Future.delayed(autoDismiss);
|
await Future.delayed(autoDismiss);
|
||||||
if (controller.isAnimating ||
|
dismiss();
|
||||||
controller.status == AnimationStatus.completed) {
|
|
||||||
dismiss();
|
return completer.future;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user