feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD) * DEV: Clés TEST Pierre (mode test) * REC: Clés TEST Client (mode test) * PROD: Clés LIVE Client (mode live) - Ajout de la gestion des bases de données immeubles/bâtiments * Configuration buildings_database pour DEV/REC/PROD * Service BuildingService pour enrichissement des adresses - Optimisations pages et améliorations ergonomie - Mises à jour des dépendances Composer - Nettoyage des fichiers obsolètes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
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';
|
||||
@@ -15,8 +14,8 @@ 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';
|
||||
import 'package:geosector_app/presentation/widgets/passages/passages_list_widget.dart';
|
||||
import 'package:geosector_app/presentation/widgets/grouped_passages_dialog.dart';
|
||||
import 'package:geosector_app/app.dart';
|
||||
import 'package:sensors_plus/sensors_plus.dart';
|
||||
import 'package:geosector_app/core/utils/api_exception.dart';
|
||||
|
||||
class UserFieldModePage extends StatefulWidget {
|
||||
@@ -45,14 +44,9 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
|
||||
// Qualité des signaux
|
||||
double _gpsAccuracy = 999;
|
||||
ConnectivityResult _connectivityResult = ConnectivityResult.none;
|
||||
List<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 = [];
|
||||
@@ -62,11 +56,18 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
bool _locationPermissionGranted = false;
|
||||
String _statusMessage = '';
|
||||
|
||||
// Listener pour les changements de la box passages
|
||||
Box<PassageModel>? _passagesBox;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeAnimations();
|
||||
|
||||
// Écouter les changements de la Hive box passages pour rafraîchir la carte
|
||||
_passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
_passagesBox?.listenable().addListener(_onPassagesChanged);
|
||||
|
||||
if (kIsWeb) {
|
||||
// Sur web, utiliser une position simulée pour éviter le blocage
|
||||
_initializeWebMode();
|
||||
@@ -77,6 +78,13 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
}
|
||||
}
|
||||
|
||||
// Callback appelé quand la box passages change
|
||||
void _onPassagesChanged() {
|
||||
if (mounted) {
|
||||
_updateNearbyPassages();
|
||||
}
|
||||
}
|
||||
|
||||
void _initializeWebMode() async {
|
||||
// Essayer d'obtenir la position réelle depuis le navigateur
|
||||
try {
|
||||
@@ -86,14 +94,16 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
|
||||
// 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;
|
||||
_isGpsEnabled = true;
|
||||
_connectivityResult = ConnectivityResult.wifi;
|
||||
_connectivityResult = [ConnectivityResult.wifi];
|
||||
_isLoading = false;
|
||||
_locationPermissionGranted = true;
|
||||
_statusMessage = "";
|
||||
@@ -148,7 +158,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
);
|
||||
_gpsAccuracy = 100.0;
|
||||
_isGpsEnabled = false;
|
||||
_connectivityResult = ConnectivityResult.wifi;
|
||||
_connectivityResult = [ConnectivityResult.wifi];
|
||||
_isLoading = false;
|
||||
_locationPermissionGranted = false;
|
||||
_statusMessage = statusMessage;
|
||||
@@ -215,9 +225,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
_updateBlinkAnimations();
|
||||
|
||||
// Centrer la carte sur la nouvelle position
|
||||
if (!_compassMode) {
|
||||
_mapController.move(LatLng(position.latitude, position.longitude), 17);
|
||||
}
|
||||
_mapController.move(LatLng(position.latitude, position.longitude), 17);
|
||||
}, onError: (error) {
|
||||
setState(() {
|
||||
_isGpsEnabled = false;
|
||||
@@ -256,8 +264,8 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
}
|
||||
|
||||
// Réseau: clignoter si connexion faible ou absente
|
||||
if (_connectivityResult == ConnectivityResult.none ||
|
||||
_connectivityResult == ConnectivityResult.mobile) {
|
||||
if (_connectivityResult.contains(ConnectivityResult.none) ||
|
||||
_connectivityResult.contains(ConnectivityResult.mobile)) {
|
||||
_networkBlinkController.repeat(reverse: true);
|
||||
} else {
|
||||
_networkBlinkController.stop();
|
||||
@@ -288,12 +296,29 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
|
||||
passagesWithDistance.sort((a, b) => a.value.compareTo(b.value));
|
||||
|
||||
setState(() {
|
||||
_nearbyPassages = passagesWithDistance
|
||||
.where((entry) => entry.value <= 500) // Max 500m
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
});
|
||||
final newNearbyPassages = passagesWithDistance
|
||||
.where((entry) => entry.value <= 500) // Max 500m
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
|
||||
// Ne setState que si les passages ont vraiment changé
|
||||
if (!_arePassagesEqual(_nearbyPassages, newNearbyPassages)) {
|
||||
setState(() {
|
||||
_nearbyPassages = newNearbyPassages;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Comparer deux listes de passages pour éviter les setState inutiles
|
||||
bool _arePassagesEqual(List<PassageModel> oldPassages, List<PassageModel> newPassages) {
|
||||
if (oldPassages.length != newPassages.length) return false;
|
||||
|
||||
// Créer des clés uniques incluant ID + fkType pour détecter les changements de type
|
||||
// (important pour le gradient des immeubles qui dépend du fkType)
|
||||
final oldKeys = oldPassages.map((p) => '${p.id}_${p.fkType}').toSet();
|
||||
final newKeys = newPassages.map((p) => '${p.id}_${p.fkType}').toSet();
|
||||
|
||||
return oldKeys.length == newKeys.length && oldKeys.containsAll(newKeys);
|
||||
}
|
||||
|
||||
double _calculateDistance(
|
||||
@@ -306,49 +331,6 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleCompassMode() {
|
||||
// Mode boussole désactivé sur web
|
||||
if (kIsWeb) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Le mode boussole nécessite un appareil mobile'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_compassMode = !_compassMode;
|
||||
});
|
||||
|
||||
if (_compassMode) {
|
||||
_startCompass();
|
||||
// Vibration légère pour feedback
|
||||
HapticFeedback.lightImpact();
|
||||
} else {
|
||||
_stopCompass();
|
||||
}
|
||||
}
|
||||
|
||||
void _startCompass() {
|
||||
_magnetometerSubscription =
|
||||
magnetometerEvents.listen((MagnetometerEvent event) {
|
||||
setState(() {
|
||||
// Calculer l'orientation à partir du magnétomètre
|
||||
_heading = math.atan2(event.y, event.x) * (180 / math.pi);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _stopCompass() {
|
||||
_magnetometerSubscription?.cancel();
|
||||
_magnetometerSubscription = null;
|
||||
setState(() {
|
||||
_heading = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void _recenterMap() {
|
||||
if (_currentPosition != null) {
|
||||
_mapController.move(
|
||||
@@ -378,6 +360,17 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
);
|
||||
}
|
||||
|
||||
// Afficher la dialog groupée pour les immeubles
|
||||
void _showGroupedPassagesDialog(PassageModel referencePassage) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => GroupedPassagesDialog(
|
||||
referencePassage: referencePassage,
|
||||
isAdmin: false, // Mode terrain = utilisateur simple
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier si l'amicale autorise la suppression des passages
|
||||
bool _canDeletePassages() {
|
||||
try {
|
||||
@@ -546,10 +539,10 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
void dispose() {
|
||||
_positionStreamSubscription?.cancel();
|
||||
_qualityUpdateTimer?.cancel();
|
||||
_magnetometerSubscription?.cancel();
|
||||
_gpsBlinkController.dispose();
|
||||
_networkBlinkController.dispose();
|
||||
_searchController.dispose();
|
||||
_passagesBox?.listenable().removeListener(_onPassagesChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -568,7 +561,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
@@ -722,7 +715,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.2),
|
||||
color: color.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
@@ -752,7 +745,13 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
String label;
|
||||
String tooltip;
|
||||
|
||||
switch (_connectivityResult) {
|
||||
// Utiliser le premier élément de la liste pour déterminer le type de connexion
|
||||
final primaryResult = _connectivityResult.firstWhere(
|
||||
(result) => result != ConnectivityResult.none,
|
||||
orElse: () => ConnectivityResult.none
|
||||
);
|
||||
|
||||
switch (primaryResult) {
|
||||
case ConnectivityResult.wifi:
|
||||
icon = Icons.wifi;
|
||||
color = Colors.green;
|
||||
@@ -790,7 +789,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.2),
|
||||
color: color.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
@@ -830,9 +829,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: _compassMode ? _heading * (math.pi / 180) : 0,
|
||||
child: FlutterMap(
|
||||
FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: LatLng(
|
||||
@@ -851,41 +848,11 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
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',
|
||||
userAgentPackageName: 'app3.geosector.fr',
|
||||
additionalOptions: const {
|
||||
'attribution': '© OpenStreetMap contributors',
|
||||
},
|
||||
),
|
||||
// Cercles de distance en mode boussole
|
||||
if (_compassMode)
|
||||
CircleLayer(
|
||||
circles: [
|
||||
CircleMarker(
|
||||
point: LatLng(_currentPosition!.latitude,
|
||||
_currentPosition!.longitude),
|
||||
radius: 50,
|
||||
color: Colors.blue.withValues(alpha: 0.1),
|
||||
borderColor: Colors.blue.withValues(alpha: 0.3),
|
||||
borderStrokeWidth: 1,
|
||||
),
|
||||
CircleMarker(
|
||||
point: LatLng(_currentPosition!.latitude,
|
||||
_currentPosition!.longitude),
|
||||
radius: 100,
|
||||
color: Colors.transparent,
|
||||
borderColor: Colors.blue.withValues(alpha: 0.2),
|
||||
borderStrokeWidth: 1,
|
||||
),
|
||||
CircleMarker(
|
||||
point: LatLng(_currentPosition!.latitude,
|
||||
_currentPosition!.longitude),
|
||||
radius: 250,
|
||||
color: Colors.transparent,
|
||||
borderColor: Colors.blue.withValues(alpha: 0.15),
|
||||
borderStrokeWidth: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
// Markers des passages
|
||||
MarkerLayer(
|
||||
markers: _buildPassageMarkers(),
|
||||
@@ -905,7 +872,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
border: Border.all(color: Colors.white, width: 3),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withValues(alpha: 0.3),
|
||||
color: Colors.blue.withOpacity(0.3),
|
||||
blurRadius: 10,
|
||||
spreadRadius: 5,
|
||||
),
|
||||
@@ -921,7 +888,6 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Bouton recentrage (bas gauche)
|
||||
Positioned(
|
||||
@@ -934,48 +900,6 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
child: const Icon(Icons.my_location),
|
||||
),
|
||||
),
|
||||
// Bouton boussole (bas droite)
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
child: FloatingActionButton.small(
|
||||
backgroundColor: _compassMode ? Colors.green : Colors.white,
|
||||
foregroundColor: _compassMode ? Colors.white : Colors.grey[700],
|
||||
onPressed: _toggleCompassMode,
|
||||
child: Transform.rotate(
|
||||
angle: _compassMode ? _heading * (math.pi / 180) : 0,
|
||||
child: const Icon(Icons.explore),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Indicateur de mode boussole
|
||||
if (_compassMode)
|
||||
Positioned(
|
||||
top: 16,
|
||||
right: 16,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.explore, color: Colors.white, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Mode boussole',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -993,7 +917,34 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
List<Marker> _buildPassageMarkers() {
|
||||
if (_currentPosition == null) return [];
|
||||
|
||||
return _nearbyPassages.map((passage) {
|
||||
final List<Marker> markers = [];
|
||||
|
||||
// 1. Séparer les passages immeubles (fkHabitat=2) des autres
|
||||
final buildingPassages = <String, List<Map<String, dynamic>>>{};
|
||||
final individualPassages = <PassageModel>[];
|
||||
|
||||
for (final passage in _nearbyPassages) {
|
||||
if (passage.fkHabitat == 2) {
|
||||
// Créer une clé unique basée sur l'adresse complète
|
||||
final addressKey = '${passage.numero}|${passage.rueBis}|${passage.rue}|${passage.ville}';
|
||||
|
||||
// Convertir les coordonnées GPS string en double
|
||||
final double lat = double.tryParse(passage.gpsLat) ?? 0;
|
||||
final double lng = double.tryParse(passage.gpsLng) ?? 0;
|
||||
|
||||
buildingPassages.putIfAbsent(addressKey, () => []);
|
||||
buildingPassages[addressKey]!.add({
|
||||
'model': passage,
|
||||
'position': LatLng(lat, lng),
|
||||
'id': passage.id,
|
||||
});
|
||||
} else {
|
||||
individualPassages.add(passage);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Créer les markers individuels (fkHabitat != 2) - Cercles
|
||||
for (final passage in individualPassages) {
|
||||
// Déterminer la couleur selon le type de passage
|
||||
Color fillColor = Colors.grey; // Couleur par défaut
|
||||
|
||||
@@ -1022,45 +973,121 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
final double lat = double.tryParse(passage.gpsLat) ?? 0;
|
||||
final double lng = double.tryParse(passage.gpsLng) ?? 0;
|
||||
|
||||
return Marker(
|
||||
point: LatLng(lat, lng),
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: GestureDetector(
|
||||
onTap: () => _openPassageForm(passage),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: fillColor,
|
||||
border: Border.all(color: borderColor, width: 3),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
markers.add(
|
||||
Marker(
|
||||
point: LatLng(lat, lng),
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: GestureDetector(
|
||||
onTap: () => _openPassageForm(passage),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: fillColor,
|
||||
border: Border.all(color: borderColor, width: 3),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${passage.numero}${(passage.rueBis.isNotEmpty) ? passage.rueBis.substring(0, 1).toLowerCase() : ''}',
|
||||
style: TextStyle(
|
||||
// Texte noir sur fond clair, blanc sur fond foncé
|
||||
color: fillColor.computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${passage.numero}${(passage.rueBis.isNotEmpty) ? passage.rueBis.substring(0, 1).toLowerCase() : ''}',
|
||||
style: TextStyle(
|
||||
// Texte noir sur fond clair, blanc sur fond foncé
|
||||
color: fillColor.computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// 3. Créer les markers groupés (carrés avec dégradé blanc→vert selon avancement)
|
||||
for (final entry in buildingPassages.entries) {
|
||||
final passages = entry.value;
|
||||
if (passages.isEmpty) continue;
|
||||
|
||||
// Utiliser la position du premier passage du groupe
|
||||
final position = passages.first['position'] as LatLng;
|
||||
final count = passages.length;
|
||||
final displayCount = count >= 99 ? '99' : count.toString();
|
||||
|
||||
// Calculer le pourcentage de passages réalisés (fkType != 2)
|
||||
final models = passages.map((p) => p['model'] as PassageModel).toList();
|
||||
final realizedCount = models.where((p) => p.fkType != 2).length;
|
||||
final percentage = realizedCount / models.length;
|
||||
|
||||
// Déterminer la couleur de remplissage selon le palier (5 niveaux)
|
||||
Color fillColor;
|
||||
if (percentage == 0) {
|
||||
// 0% : Blanc pur
|
||||
fillColor = Colors.white;
|
||||
} else if (percentage <= 0.25) {
|
||||
// 1-25% : Blanc cassé → Vert très clair
|
||||
fillColor = Color.lerp(Colors.white, const Color(0xFFB3F5E0), (percentage / 0.25))!;
|
||||
} else if (percentage <= 0.50) {
|
||||
// 26-50% : Vert très clair → Vert clair
|
||||
fillColor = Color.lerp(const Color(0xFFB3F5E0), const Color(0xFF66EBBB), ((percentage - 0.25) / 0.25))!;
|
||||
} else if (percentage <= 0.75) {
|
||||
// 51-75% : Vert clair → Vert moyen
|
||||
fillColor = Color.lerp(const Color(0xFF66EBBB), const Color(0xFF33E1A0), ((percentage - 0.50) / 0.25))!;
|
||||
} else if (percentage < 1.0) {
|
||||
// 76-99% : Vert moyen → Vert foncé
|
||||
fillColor = Color.lerp(const Color(0xFF33E1A0), const Color(0xFF00E09D), ((percentage - 0.75) / 0.25))!;
|
||||
} else {
|
||||
// 100% : Vert foncé (couleur "Effectué")
|
||||
fillColor = const Color(0xFF00E09D);
|
||||
}
|
||||
|
||||
markers.add(
|
||||
Marker(
|
||||
point: position,
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_showGroupedPassagesDialog(passages.first['model'] as PassageModel);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: fillColor,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: Colors.blue, // Bordure bleue toujours
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
displayCount,
|
||||
style: TextStyle(
|
||||
color: percentage < 0.5 ? Colors.black87 : Colors.white, // Texte noir sur fond clair, blanc sur fond foncé
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _getFilteredPassages() {
|
||||
@@ -1121,7 +1148,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
|
||||
'isOwnedByCurrentUser': passage.fkUser ==
|
||||
userRepository
|
||||
.getCurrentUser()
|
||||
?.id, // Ajout du champ pour le widget
|
||||
?.opeUserId, // Comparer avec ope_users.id
|
||||
// Garder les données originales pour l'édition
|
||||
'numero': passage.numero,
|
||||
'rueBis': passage.rueBis,
|
||||
|
||||
Reference in New Issue
Block a user