feat: Mise à jour des interfaces mobiles v3.2.3
- Amélioration des interfaces utilisateur sur mobile - Optimisation de la responsivité des composants Flutter - Mise à jour des widgets de chat et communication - Amélioration des formulaires et tableaux - Ajout de nouveaux composants pour l'administration - Optimisation des thèmes et styles visuels 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/core/theme/app_theme.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
@@ -10,7 +11,6 @@ import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/services/current_amicale_service.dart';
|
||||
import 'package:geosector_app/presentation/widgets/passage_form_dialog.dart';
|
||||
@@ -26,36 +26,37 @@ class UserFieldModePage extends StatefulWidget {
|
||||
State<UserFieldModePage> createState() => _UserFieldModePageState();
|
||||
}
|
||||
|
||||
class _UserFieldModePageState extends State<UserFieldModePage> with TickerProviderStateMixin {
|
||||
class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
with TickerProviderStateMixin {
|
||||
// Controllers
|
||||
final MapController _mapController = MapController();
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
|
||||
// Animation controllers pour le clignotement
|
||||
late AnimationController _gpsBlinkController;
|
||||
late AnimationController _networkBlinkController;
|
||||
late Animation<double> _gpsBlinkAnimation;
|
||||
late Animation<double> _networkBlinkAnimation;
|
||||
|
||||
|
||||
// Position et tracking
|
||||
Position? _currentPosition;
|
||||
StreamSubscription<Position>? _positionStreamSubscription;
|
||||
Timer? _qualityUpdateTimer;
|
||||
|
||||
|
||||
// Qualité des signaux
|
||||
double _gpsAccuracy = 999;
|
||||
ConnectivityResult _connectivityResult = ConnectivityResult.none;
|
||||
bool _isGpsEnabled = false;
|
||||
|
||||
|
||||
// Mode boussole
|
||||
bool _compassMode = false;
|
||||
double _heading = 0;
|
||||
StreamSubscription<MagnetometerEvent>? _magnetometerSubscription;
|
||||
|
||||
|
||||
// Filtrage et recherche
|
||||
String _searchQuery = '';
|
||||
List<PassageModel> _nearbyPassages = [];
|
||||
|
||||
|
||||
// État de chargement
|
||||
bool _isLoading = true;
|
||||
bool _locationPermissionGranted = false;
|
||||
@@ -65,7 +66,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeAnimations();
|
||||
|
||||
|
||||
if (kIsWeb) {
|
||||
// Sur web, utiliser une position simulée pour éviter le blocage
|
||||
_initializeWebMode();
|
||||
@@ -75,19 +76,21 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
_startQualityMonitoring();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _initializeWebMode() async {
|
||||
// Essayer d'obtenir la position réelle depuis le navigateur
|
||||
try {
|
||||
setState(() {
|
||||
_statusMessage = "Demande d'autorisation de géolocalisation...";
|
||||
});
|
||||
|
||||
|
||||
// Demander la permission et obtenir la position
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
setState(() {
|
||||
_currentPosition = position;
|
||||
_gpsAccuracy = position.accuracy;
|
||||
@@ -97,38 +100,40 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
_locationPermissionGranted = true;
|
||||
_statusMessage = "";
|
||||
});
|
||||
|
||||
|
||||
// Charger les passages proches de la position réelle
|
||||
_updateNearbyPassages();
|
||||
|
||||
|
||||
// Démarrer le suivi de position même sur web
|
||||
_startLocationTracking();
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('Erreur géolocalisation web: $e');
|
||||
|
||||
|
||||
// Essayer d'utiliser les coordonnées GPS de l'amicale
|
||||
double fallbackLat = 46.603354; // Centre de la France par défaut
|
||||
double fallbackLat = 46.603354; // Centre de la France par défaut
|
||||
double fallbackLng = 1.888334;
|
||||
String statusMessage = "Position approximative";
|
||||
|
||||
|
||||
try {
|
||||
final amicale = CurrentAmicaleService.instance.currentAmicale;
|
||||
if (amicale != null && amicale.gpsLat.isNotEmpty && amicale.gpsLng.isNotEmpty) {
|
||||
if (amicale != null &&
|
||||
amicale.gpsLat.isNotEmpty &&
|
||||
amicale.gpsLng.isNotEmpty) {
|
||||
final amicaleLat = double.tryParse(amicale.gpsLat);
|
||||
final amicaleLng = double.tryParse(amicale.gpsLng);
|
||||
|
||||
|
||||
if (amicaleLat != null && amicaleLng != null) {
|
||||
fallbackLat = amicaleLat;
|
||||
fallbackLng = amicaleLng;
|
||||
statusMessage = "Position de l'amicale";
|
||||
debugPrint('Utilisation des coordonnées de l\'amicale: $fallbackLat, $fallbackLng');
|
||||
debugPrint(
|
||||
'Utilisation des coordonnées de l\'amicale: $fallbackLat, $fallbackLng');
|
||||
}
|
||||
}
|
||||
} catch (amicaleError) {
|
||||
debugPrint('Erreur récupération coordonnées amicale: $amicaleError');
|
||||
}
|
||||
|
||||
|
||||
// Utiliser la position de fallback (amicale ou centre France)
|
||||
setState(() {
|
||||
_currentPosition = Position(
|
||||
@@ -150,7 +155,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
_locationPermissionGranted = false;
|
||||
_statusMessage = statusMessage;
|
||||
});
|
||||
|
||||
|
||||
_updateNearbyPassages();
|
||||
}
|
||||
}
|
||||
@@ -207,12 +212,12 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
_isGpsEnabled = true;
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
|
||||
_updateNearbyPassages();
|
||||
_updateBlinkAnimations();
|
||||
|
||||
|
||||
// Centrer la carte sur la nouvelle position
|
||||
if (_mapController.mapEventStream != null && !_compassMode) {
|
||||
if (!_compassMode) {
|
||||
_mapController.move(LatLng(position.latitude, position.longitude), 17);
|
||||
}
|
||||
}, onError: (error) {
|
||||
@@ -224,22 +229,24 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
|
||||
void _startQualityMonitoring() {
|
||||
// Mise à jour toutes les 5 secondes
|
||||
_qualityUpdateTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||
_qualityUpdateTimer =
|
||||
Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||
// Vérifier la connexion réseau
|
||||
final connectivityResults = await Connectivity().checkConnectivity();
|
||||
setState(() {
|
||||
// Prendre le premier résultat de la liste
|
||||
_connectivityResult = connectivityResults.isNotEmpty
|
||||
? connectivityResults.first
|
||||
_connectivityResult = connectivityResults.isNotEmpty
|
||||
? connectivityResults.first
|
||||
: ConnectivityResult.none;
|
||||
});
|
||||
|
||||
|
||||
// Vérifier si le GPS est activé
|
||||
final isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
final isLocationServiceEnabled =
|
||||
await Geolocator.isLocationServiceEnabled();
|
||||
setState(() {
|
||||
_isGpsEnabled = isLocationServiceEnabled;
|
||||
});
|
||||
|
||||
|
||||
_updateBlinkAnimations();
|
||||
});
|
||||
}
|
||||
@@ -272,9 +279,9 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
// Calculer les distances et trier
|
||||
final passagesWithDistance = allPassages.map((passage) {
|
||||
// Convertir les coordonnées GPS string en double
|
||||
final double lat = double.tryParse(passage.gpsLat ?? '0') ?? 0;
|
||||
final double lng = double.tryParse(passage.gpsLng ?? '0') ?? 0;
|
||||
|
||||
final double lat = double.tryParse(passage.gpsLat) ?? 0;
|
||||
final double lng = double.tryParse(passage.gpsLng) ?? 0;
|
||||
|
||||
final distance = _calculateDistance(
|
||||
_currentPosition!.latitude,
|
||||
_currentPosition!.longitude,
|
||||
@@ -295,7 +302,8 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
});
|
||||
}
|
||||
|
||||
double _calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||
double _calculateDistance(
|
||||
double lat1, double lon1, double lat2, double lon2) {
|
||||
const distance = Distance();
|
||||
return distance.as(
|
||||
LengthUnit.Meter,
|
||||
@@ -315,7 +323,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setState(() {
|
||||
_compassMode = !_compassMode;
|
||||
});
|
||||
@@ -330,7 +338,8 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
}
|
||||
|
||||
void _startCompass() {
|
||||
_magnetometerSubscription = magnetometerEvents.listen((MagnetometerEvent event) {
|
||||
_magnetometerSubscription =
|
||||
magnetometerEventStream().listen((MagnetometerEvent event) {
|
||||
setState(() {
|
||||
// Calculer l'orientation à partir du magnétomètre
|
||||
_heading = math.atan2(event.y, event.x) * (180 / math.pi);
|
||||
@@ -346,7 +355,6 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void _recenterMap() {
|
||||
if (_currentPosition != null) {
|
||||
_mapController.move(
|
||||
@@ -374,7 +382,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Vérifier si l'amicale autorise la suppression des passages
|
||||
bool _canDeletePassages() {
|
||||
try {
|
||||
@@ -383,17 +391,19 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
return amicale.chkUserDeletePass == true;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification des permissions de suppression: $e');
|
||||
debugPrint(
|
||||
'Erreur lors de la vérification des permissions de suppression: $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Afficher le dialog de confirmation de suppression
|
||||
void _showDeleteConfirmationDialog(PassageModel passage) {
|
||||
final TextEditingController confirmController = TextEditingController();
|
||||
final String streetNumber = passage.numero ?? '';
|
||||
final String fullAddress = '${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim();
|
||||
|
||||
final String streetNumber = passage.numero;
|
||||
final String fullAddress =
|
||||
'${passage.numero} ${passage.rueBis} ${passage.rue}'.trim();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
@@ -411,12 +421,12 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
Text(
|
||||
'ATTENTION : Cette action est irréversible !',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
fontSize: 16,
|
||||
fontSize: AppTheme.r(context, 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -434,9 +444,9 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
),
|
||||
child: Text(
|
||||
fullAddress.isEmpty ? 'Adresse inconnue' : fullAddress,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
fontSize: AppTheme.r(context, 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -450,7 +460,9 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
controller: confirmController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Numéro de rue',
|
||||
hintText: streetNumber.isNotEmpty ? 'Ex: $streetNumber' : 'Saisir le numéro',
|
||||
hintText: streetNumber.isNotEmpty
|
||||
? 'Ex: $streetNumber'
|
||||
: 'Saisir le numéro',
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.home),
|
||||
),
|
||||
@@ -481,8 +493,9 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (streetNumber.isNotEmpty && enteredNumber.toUpperCase() != streetNumber.toUpperCase()) {
|
||||
|
||||
if (streetNumber.isNotEmpty &&
|
||||
enteredNumber.toUpperCase() != streetNumber.toUpperCase()) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Le numéro de rue ne correspond pas'),
|
||||
@@ -491,11 +504,11 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Fermer le dialog
|
||||
confirmController.dispose();
|
||||
Navigator.of(dialogContext).pop();
|
||||
|
||||
|
||||
// Effectuer la suppression
|
||||
await _deletePassage(passage);
|
||||
},
|
||||
@@ -510,20 +523,21 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Supprimer un passage
|
||||
Future<void> _deletePassage(PassageModel passage) async {
|
||||
try {
|
||||
// Appeler le repository pour supprimer via l'API
|
||||
final success = await passageRepository.deletePassageViaApi(passage.id);
|
||||
|
||||
|
||||
if (success && mounted) {
|
||||
ApiException.showSuccess(context, 'Passage supprimé avec succès');
|
||||
|
||||
|
||||
// Rafraîchir la liste des passages
|
||||
_updateNearbyPassages();
|
||||
} else if (mounted) {
|
||||
ApiException.showError(context, Exception('Erreur lors de la suppression'));
|
||||
ApiException.showError(
|
||||
context, Exception('Erreur lors de la suppression'));
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur suppression passage: $e');
|
||||
@@ -546,8 +560,6 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[100],
|
||||
appBar: AppBar(
|
||||
@@ -558,19 +570,22 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
kIsWeb
|
||||
? (_locationPermissionGranted
|
||||
? 'GPS: ${_currentPosition!.latitude.toStringAsFixed(4)}, ${_currentPosition!.longitude.toStringAsFixed(4)}'
|
||||
: _statusMessage.isNotEmpty ? _statusMessage : 'Position approximative')
|
||||
: '',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
kIsWeb
|
||||
? (_locationPermissionGranted
|
||||
? 'GPS: ${_currentPosition!.latitude.toStringAsFixed(4)}, ${_currentPosition!.longitude.toStringAsFixed(4)}'
|
||||
: _statusMessage.isNotEmpty
|
||||
? _statusMessage
|
||||
: 'Position approximative')
|
||||
: '',
|
||||
style: TextStyle(
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -636,7 +651,8 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey[100],
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 20),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@@ -648,10 +664,12 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
// En-tête de la liste
|
||||
Container(
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.location_on, color: Colors.green[600], size: 20),
|
||||
Icon(Icons.location_on,
|
||||
color: Colors.green[600], size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${_getFilteredPassages().length} passage${_getFilteredPassages().length > 1 ? 's' : ''} à proximité',
|
||||
@@ -709,7 +727,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.2),
|
||||
color: color.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
@@ -719,7 +737,10 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${_gpsAccuracy.toStringAsFixed(0)}m',
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 12),
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: AppTheme.r(context, 12)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -774,7 +795,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.2),
|
||||
color: color.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
@@ -784,7 +805,10 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 12),
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: AppTheme.r(context, 12)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -806,7 +830,8 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
}
|
||||
|
||||
final apiService = ApiService.instance;
|
||||
final mapboxApiKey = AppKeys.getMapboxApiKey(apiService.getCurrentEnvironment());
|
||||
final mapboxApiKey =
|
||||
AppKeys.getMapboxApiKey(apiService.getCurrentEnvironment());
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
@@ -815,7 +840,8 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
child: FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: LatLng(_currentPosition!.latitude, _currentPosition!.longitude),
|
||||
initialCenter: LatLng(
|
||||
_currentPosition!.latitude, _currentPosition!.longitude),
|
||||
initialZoom: 17,
|
||||
maxZoom: 19,
|
||||
minZoom: 10,
|
||||
@@ -827,9 +853,9 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
children: [
|
||||
TileLayer(
|
||||
// Utiliser l'API v4 de Mapbox sur mobile ou OpenStreetMap en fallback
|
||||
urlTemplate: kIsWeb
|
||||
? 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=$mapboxApiKey'
|
||||
: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', // OpenStreetMap temporairement sur mobile
|
||||
urlTemplate: kIsWeb
|
||||
? 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=$mapboxApiKey'
|
||||
: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', // OpenStreetMap temporairement sur mobile
|
||||
userAgentPackageName: 'app.geosector.fr',
|
||||
additionalOptions: const {
|
||||
'attribution': '© OpenStreetMap contributors',
|
||||
@@ -840,24 +866,27 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
CircleLayer(
|
||||
circles: [
|
||||
CircleMarker(
|
||||
point: LatLng(_currentPosition!.latitude, _currentPosition!.longitude),
|
||||
point: LatLng(_currentPosition!.latitude,
|
||||
_currentPosition!.longitude),
|
||||
radius: 50,
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
borderColor: Colors.blue.withOpacity(0.3),
|
||||
color: Colors.blue.withValues(alpha: 0.1),
|
||||
borderColor: Colors.blue.withValues(alpha: 0.3),
|
||||
borderStrokeWidth: 1,
|
||||
),
|
||||
CircleMarker(
|
||||
point: LatLng(_currentPosition!.latitude, _currentPosition!.longitude),
|
||||
point: LatLng(_currentPosition!.latitude,
|
||||
_currentPosition!.longitude),
|
||||
radius: 100,
|
||||
color: Colors.transparent,
|
||||
borderColor: Colors.blue.withOpacity(0.2),
|
||||
borderColor: Colors.blue.withValues(alpha: 0.2),
|
||||
borderStrokeWidth: 1,
|
||||
),
|
||||
CircleMarker(
|
||||
point: LatLng(_currentPosition!.latitude, _currentPosition!.longitude),
|
||||
point: LatLng(_currentPosition!.latitude,
|
||||
_currentPosition!.longitude),
|
||||
radius: 250,
|
||||
color: Colors.transparent,
|
||||
borderColor: Colors.blue.withOpacity(0.15),
|
||||
borderColor: Colors.blue.withValues(alpha: 0.15),
|
||||
borderStrokeWidth: 1,
|
||||
),
|
||||
],
|
||||
@@ -870,7 +899,8 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: LatLng(_currentPosition!.latitude, _currentPosition!.longitude),
|
||||
point: LatLng(_currentPosition!.latitude,
|
||||
_currentPosition!.longitude),
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: Container(
|
||||
@@ -880,7 +910,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
border: Border.all(color: Colors.white, width: 3),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withOpacity(0.3),
|
||||
color: Colors.blue.withValues(alpha: 0.3),
|
||||
blurRadius: 10,
|
||||
spreadRadius: 5,
|
||||
),
|
||||
@@ -941,9 +971,9 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Mode boussole',
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -973,9 +1003,9 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
const borderColor = Color(0xFFF7A278);
|
||||
|
||||
// Convertir les coordonnées GPS string en double
|
||||
final double lat = double.tryParse(passage.gpsLat ?? '0') ?? 0;
|
||||
final double lng = double.tryParse(passage.gpsLng ?? '0') ?? 0;
|
||||
|
||||
final double lat = double.tryParse(passage.gpsLat) ?? 0;
|
||||
final double lng = double.tryParse(passage.gpsLng) ?? 0;
|
||||
|
||||
return Marker(
|
||||
point: LatLng(lat, lng),
|
||||
width: 40,
|
||||
@@ -989,7 +1019,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
border: Border.all(color: borderColor, width: 3),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
@@ -997,11 +1027,12 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${passage.numero ?? ''}${(passage.rueBis != null && passage.rueBis!.isNotEmpty) ? passage.rueBis!.substring(0, 1).toLowerCase() : ''}',
|
||||
'${passage.numero}${(passage.rueBis.isNotEmpty) ? passage.rueBis.substring(0, 1).toLowerCase() : ''}',
|
||||
style: TextStyle(
|
||||
color: fillColor == Colors.white ? Colors.black : Colors.white,
|
||||
color:
|
||||
fillColor == Colors.white ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
@@ -1016,19 +1047,21 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
|
||||
List<Map<String, dynamic>> _getFilteredPassages() {
|
||||
// Filtrer d'abord par recherche si nécessaire
|
||||
List<PassageModel> filtered = _searchQuery.isEmpty
|
||||
? _nearbyPassages
|
||||
: _nearbyPassages.where((passage) {
|
||||
final address = '${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim().toLowerCase();
|
||||
return address.contains(_searchQuery);
|
||||
}).toList();
|
||||
|
||||
List<PassageModel> filtered = _searchQuery.isEmpty
|
||||
? _nearbyPassages
|
||||
: _nearbyPassages.where((passage) {
|
||||
final address = '${passage.numero} ${passage.rueBis} ${passage.rue}'
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return address.contains(_searchQuery);
|
||||
}).toList();
|
||||
|
||||
// Convertir au format attendu par PassagesListWidget avec distance
|
||||
return filtered.map((passage) {
|
||||
// Calculer la distance
|
||||
final double lat = double.tryParse(passage.gpsLat ?? '0') ?? 0;
|
||||
final double lng = double.tryParse(passage.gpsLng ?? '0') ?? 0;
|
||||
|
||||
final double lat = double.tryParse(passage.gpsLat) ?? 0;
|
||||
final double lng = double.tryParse(passage.gpsLng) ?? 0;
|
||||
|
||||
final distance = _currentPosition != null
|
||||
? _calculateDistance(
|
||||
_currentPosition!.latitude,
|
||||
@@ -1037,10 +1070,11 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
lng,
|
||||
)
|
||||
: 0.0;
|
||||
|
||||
|
||||
// Construire l'adresse complète
|
||||
final String address = '${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim();
|
||||
|
||||
final String address =
|
||||
'${passage.numero} ${passage.rueBis} ${passage.rue}'.trim();
|
||||
|
||||
// Convertir le montant
|
||||
double amount = 0.0;
|
||||
try {
|
||||
@@ -1051,7 +1085,7 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
} catch (e) {
|
||||
// Ignorer les erreurs de conversion
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
'id': passage.id,
|
||||
'address': address.isEmpty ? 'Adresse inconnue' : address,
|
||||
@@ -1066,7 +1100,10 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
'fkUser': passage.fkUser,
|
||||
'distance': distance, // Ajouter la distance pour le tri et l'affichage
|
||||
'nbPassages': passage.nbPassages, // Pour la couleur de l'indicateur
|
||||
'isOwnedByCurrentUser': passage.fkUser == userRepository.getCurrentUser()?.id, // Ajout du champ pour le widget
|
||||
'isOwnedByCurrentUser': passage.fkUser ==
|
||||
userRepository
|
||||
.getCurrentUser()
|
||||
?.id, // Ajout du champ pour le widget
|
||||
// Garder les données originales pour l'édition
|
||||
'numero': passage.numero,
|
||||
'rueBis': passage.rueBis,
|
||||
@@ -1109,18 +1146,18 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
|
||||
},
|
||||
);
|
||||
},
|
||||
onPassageDelete: _canDeletePassages()
|
||||
? (passage) {
|
||||
// Retrouver le PassageModel original pour la suppression
|
||||
final passageId = passage['id'] as int;
|
||||
final originalPassage = _nearbyPassages.firstWhere(
|
||||
(p) => p.id == passageId,
|
||||
orElse: () => _nearbyPassages.first,
|
||||
);
|
||||
_showDeleteConfirmationDialog(originalPassage);
|
||||
}
|
||||
: null,
|
||||
onPassageDelete: _canDeletePassages()
|
||||
? (passage) {
|
||||
// Retrouver le PassageModel original pour la suppression
|
||||
final passageId = passage['id'] as int;
|
||||
final originalPassage = _nearbyPassages.firstWhere(
|
||||
(p) => p.id == passageId,
|
||||
orElse: () => _nearbyPassages.first,
|
||||
);
|
||||
_showDeleteConfirmationDialog(originalPassage);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user