feat: Enhance map functionality with user location markers and place search integration
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
class MapContent extends StatefulWidget {
|
class MapContent extends StatefulWidget {
|
||||||
const MapContent({super.key});
|
const MapContent({super.key});
|
||||||
@@ -11,10 +14,18 @@ class MapContent extends StatefulWidget {
|
|||||||
|
|
||||||
class _MapContentState extends State<MapContent> {
|
class _MapContentState extends State<MapContent> {
|
||||||
GoogleMapController? _mapController;
|
GoogleMapController? _mapController;
|
||||||
LatLng _initialPosition = const LatLng(48.8566, 2.3522); // Paris par défaut
|
LatLng _initialPosition = const LatLng(48.8566, 2.3522);
|
||||||
final TextEditingController _searchController = TextEditingController();
|
final TextEditingController _searchController = TextEditingController();
|
||||||
bool _isLoadingLocation = false;
|
bool _isLoadingLocation = false;
|
||||||
|
bool _isSearching = false;
|
||||||
Position? _currentPosition;
|
Position? _currentPosition;
|
||||||
|
|
||||||
|
final Set<Marker> _markers = {};
|
||||||
|
final Set<Circle> _circles = {};
|
||||||
|
|
||||||
|
List<PlaceSuggestion> _suggestions = [];
|
||||||
|
|
||||||
|
static const String _apiKey = 'AIzaSyBPxanjGyrWVjI4-hZmi086VdQFSEYT_2U';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -29,14 +40,12 @@ class _MapContentState extends State<MapContent> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir la position actuelle
|
|
||||||
Future<void> _getCurrentLocation() async {
|
Future<void> _getCurrentLocation() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoadingLocation = true;
|
_isLoadingLocation = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Vérifier si la localisation est activée
|
|
||||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||||
if (!serviceEnabled) {
|
if (!serviceEnabled) {
|
||||||
_showError('Veuillez activer les services de localisation');
|
_showError('Veuillez activer les services de localisation');
|
||||||
@@ -46,7 +55,6 @@ class _MapContentState extends State<MapContent> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier les permissions
|
|
||||||
LocationPermission permission = await Geolocator.checkPermission();
|
LocationPermission permission = await Geolocator.checkPermission();
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
permission = await Geolocator.requestPermission();
|
permission = await Geolocator.requestPermission();
|
||||||
@@ -67,21 +75,26 @@ class _MapContentState extends State<MapContent> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir la position actuelle
|
|
||||||
Position position = await Geolocator.getCurrentPosition(
|
Position position = await Geolocator.getCurrentPosition(
|
||||||
desiredAccuracy: LocationAccuracy.high,
|
locationSettings: const LocationSettings(
|
||||||
|
accuracy: LocationAccuracy.high,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final newPosition = LatLng(position.latitude, position.longitude);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentPosition = position;
|
_currentPosition = position;
|
||||||
_initialPosition = LatLng(position.latitude, position.longitude);
|
_initialPosition = newPosition;
|
||||||
_isLoadingLocation = false;
|
_isLoadingLocation = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Animer la caméra vers la position actuelle
|
// Créer l'icône personnalisée et ajouter le marqueur
|
||||||
|
await _addUserLocationMarker(newPosition);
|
||||||
|
|
||||||
if (_mapController != null) {
|
if (_mapController != null) {
|
||||||
_mapController!.animateCamera(
|
_mapController!.animateCamera(
|
||||||
CameraUpdate.newLatLngZoom(_initialPosition, 14),
|
CameraUpdate.newLatLngZoom(_initialPosition, 15),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -92,18 +105,197 @@ class _MapContentState extends State<MapContent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showError(String message) {
|
// Créer une icône personnalisée à partir de l'icône Material
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
Future<BitmapDescriptor> _createCustomMarkerIcon() async {
|
||||||
SnackBar(content: Text(message), backgroundColor: Colors.red),
|
final pictureRecorder = ui.PictureRecorder();
|
||||||
|
final canvas = Canvas(pictureRecorder);
|
||||||
|
const size = 120.0;
|
||||||
|
|
||||||
|
// Dessiner l'icône person_pin_circle en bleu
|
||||||
|
final iconPainter = TextPainter(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
);
|
);
|
||||||
|
iconPainter.text = TextSpan(
|
||||||
|
text: String.fromCharCode(Icons.person_pin_circle.codePoint),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 100,
|
||||||
|
fontFamily: Icons.person_pin_circle.fontFamily,
|
||||||
|
color: Colors.blue[700],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
iconPainter.layout();
|
||||||
|
iconPainter.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(
|
||||||
|
(size - iconPainter.width) / 2,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final picture = pictureRecorder.endRecording();
|
||||||
|
final image = await picture.toImage(size.toInt(), size.toInt());
|
||||||
|
final bytes = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||||
|
|
||||||
|
return BitmapDescriptor.fromBytes(bytes!.buffer.asUint8List());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _searchLocation() {
|
// Ajouter le marqueur avec l'icône personnalisée
|
||||||
final searchQuery = _searchController.text.trim();
|
Future<void> _addUserLocationMarker(LatLng position) async {
|
||||||
if (searchQuery.isNotEmpty) {
|
_markers.clear();
|
||||||
ScaffoldMessenger.of(
|
_circles.clear();
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text('Recherche de: $searchQuery')));
|
// Ajouter un cercle de précision
|
||||||
|
_circles.add(
|
||||||
|
Circle(
|
||||||
|
circleId: const CircleId('user_location_accuracy'),
|
||||||
|
center: position,
|
||||||
|
radius: _currentPosition?.accuracy ?? 50,
|
||||||
|
fillColor: Colors.blue.withValues(alpha: 0.08),
|
||||||
|
strokeColor: Colors.blue.withValues(alpha: 0.3),
|
||||||
|
strokeWidth: 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Créer l'icône personnalisée
|
||||||
|
final icon = await _createCustomMarkerIcon();
|
||||||
|
|
||||||
|
// Ajouter le marqueur avec l'icône
|
||||||
|
setState(() {
|
||||||
|
_markers.add(
|
||||||
|
Marker(
|
||||||
|
markerId: const MarkerId('user_location'),
|
||||||
|
position: position,
|
||||||
|
icon: icon,
|
||||||
|
anchor: const Offset(0.5, 0.85), // Ancrer au bas de l'icône (le point du pin)
|
||||||
|
infoWindow: InfoWindow(
|
||||||
|
title: 'Ma position',
|
||||||
|
snippet: 'Lat: ${position.latitude.toStringAsFixed(4)}, Lng: ${position.longitude.toStringAsFixed(4)}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _searchPlaces(String query) async {
|
||||||
|
if (query.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_suggestions = [];
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isSearching = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final url = Uri.parse(
|
||||||
|
'https://maps.googleapis.com/maps/api/place/autocomplete/json'
|
||||||
|
'?input=${Uri.encodeComponent(query)}'
|
||||||
|
'&key=$_apiKey'
|
||||||
|
'&language=fr'
|
||||||
|
);
|
||||||
|
|
||||||
|
final response = await http.get(url);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = json.decode(response.body);
|
||||||
|
|
||||||
|
if (data['status'] == 'OK') {
|
||||||
|
final predictions = data['predictions'] as List;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_suggestions = predictions
|
||||||
|
.map((p) => PlaceSuggestion(
|
||||||
|
placeId: p['place_id'],
|
||||||
|
description: p['description'],
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
_isSearching = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_suggestions = [];
|
||||||
|
_isSearching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_showError('Erreur lors de la recherche de lieux: $e');
|
||||||
|
setState(() {
|
||||||
|
_isSearching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectPlace(PlaceSuggestion suggestion) async {
|
||||||
|
setState(() {
|
||||||
|
_isSearching = true;
|
||||||
|
_suggestions = [];
|
||||||
|
_searchController.text = suggestion.description;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final url = Uri.parse(
|
||||||
|
'https://maps.googleapis.com/maps/api/place/details/json'
|
||||||
|
'?place_id=${suggestion.placeId}'
|
||||||
|
'&key=$_apiKey'
|
||||||
|
'&language=fr',
|
||||||
|
);
|
||||||
|
|
||||||
|
final response = await http.get(url);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = json.decode(response.body);
|
||||||
|
|
||||||
|
if (data['status'] == 'OK') {
|
||||||
|
final location = data['result']['geometry']['location'];
|
||||||
|
final lat = location['lat'];
|
||||||
|
final lng = location['lng'];
|
||||||
|
final name = data['result']['name'];
|
||||||
|
|
||||||
|
final newPosition = LatLng(lat, lng);
|
||||||
|
|
||||||
|
// Ajouter un marqueur pour le lieu recherché (ne pas supprimer le marqueur de position)
|
||||||
|
setState(() {
|
||||||
|
// Garder le marqueur de position utilisateur
|
||||||
|
_markers.removeWhere((m) => m.markerId.value != 'user_location');
|
||||||
|
|
||||||
|
// Ajouter le nouveau marqueur de lieu
|
||||||
|
_markers.add(
|
||||||
|
Marker(
|
||||||
|
markerId: MarkerId(suggestion.placeId),
|
||||||
|
position: newPosition,
|
||||||
|
infoWindow: InfoWindow(title: name),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_isSearching = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
_mapController?.animateCamera(
|
||||||
|
CameraUpdate.newLatLngZoom(newPosition, 15),
|
||||||
|
);
|
||||||
|
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_showError('Erreur lors de la sélection du lieu: $e');
|
||||||
|
setState(() {
|
||||||
|
_isSearching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showError(String message) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,80 +303,247 @@ class _MapContentState extends State<MapContent> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Stack(
|
||||||
padding: const EdgeInsets.all(12.0),
|
children: [
|
||||||
child: Column(
|
// Google Maps en arrière-plan
|
||||||
children: [
|
Positioned.fill(
|
||||||
// Champ de recherche
|
child: Padding(
|
||||||
TextField(
|
padding: const EdgeInsets.all(12.0),
|
||||||
controller: _searchController,
|
child: ClipRRect(
|
||||||
decoration: InputDecoration(
|
borderRadius: BorderRadius.circular(16),
|
||||||
hintText: 'Rechercher un lieu...',
|
child: GoogleMap(
|
||||||
prefixIcon: const Icon(Icons.search),
|
initialCameraPosition: CameraPosition(
|
||||||
border: OutlineInputBorder(
|
target: _initialPosition,
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
zoom: 14,
|
||||||
|
),
|
||||||
|
onMapCreated: (GoogleMapController controller) {
|
||||||
|
_mapController = controller;
|
||||||
|
},
|
||||||
|
markers: _markers,
|
||||||
|
circles: _circles,
|
||||||
|
myLocationEnabled: false,
|
||||||
|
myLocationButtonEnabled: false,
|
||||||
|
zoomControlsEnabled: false,
|
||||||
|
mapType: MapType.normal,
|
||||||
|
compassEnabled: true,
|
||||||
|
rotateGesturesEnabled: true,
|
||||||
|
scrollGesturesEnabled: true,
|
||||||
|
tiltGesturesEnabled: true,
|
||||||
|
zoomGesturesEnabled: true,
|
||||||
|
onTap: (_) {
|
||||||
|
setState(() {
|
||||||
|
_suggestions = [];
|
||||||
|
});
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Indicateur de chargement
|
||||||
|
if (_isLoadingLocation)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.1),
|
||||||
|
blurRadius: 10,
|
||||||
|
spreadRadius: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
'Localisation en cours...',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.white,
|
|
||||||
),
|
),
|
||||||
onSubmitted: (_) => _searchLocation(),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
// Barre de recherche et suggestions
|
||||||
|
Positioned(
|
||||||
// Google Maps
|
top: 0,
|
||||||
Expanded(
|
left: 0,
|
||||||
child: Stack(
|
right: 0,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
// Champ de recherche
|
||||||
borderRadius: BorderRadius.circular(16),
|
Container(
|
||||||
child: GoogleMap(
|
decoration: BoxDecoration(
|
||||||
initialCameraPosition: CameraPosition(
|
borderRadius: BorderRadius.circular(12),
|
||||||
target: _initialPosition,
|
boxShadow: [
|
||||||
zoom: 14,
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.15),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: _searchController,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black87,
|
||||||
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
onMapCreated: (GoogleMapController controller) {
|
decoration: InputDecoration(
|
||||||
_mapController = controller;
|
hintText: 'Rechercher un lieu...',
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
prefixIcon: _isSearching
|
||||||
|
? const Padding(
|
||||||
|
padding: EdgeInsets.all(14.0),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Icon(Icons.search, color: Colors.grey[700]),
|
||||||
|
suffixIcon: _searchController.text.isNotEmpty
|
||||||
|
? IconButton(
|
||||||
|
icon: Icon(Icons.clear, color: Colors.grey[700]),
|
||||||
|
onPressed: () {
|
||||||
|
_searchController.clear();
|
||||||
|
setState(() {
|
||||||
|
_suggestions = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.white,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
_searchPlaces(value);
|
||||||
},
|
},
|
||||||
myLocationEnabled: true,
|
|
||||||
myLocationButtonEnabled: false,
|
|
||||||
zoomControlsEnabled: false,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Indicateur de chargement
|
// Liste des suggestions
|
||||||
if (_isLoadingLocation)
|
if (_suggestions.isNotEmpty)
|
||||||
Center(
|
Container(
|
||||||
child: Container(
|
margin: const EdgeInsets.only(top: 8),
|
||||||
padding: EdgeInsets.all(16),
|
constraints: BoxConstraints(
|
||||||
decoration: BoxDecoration(
|
maxHeight: MediaQuery.of(context).size.height * 0.4,
|
||||||
color: Colors.white,
|
),
|
||||||
borderRadius: BorderRadius.circular(12),
|
decoration: BoxDecoration(
|
||||||
),
|
color: Colors.white,
|
||||||
child: Column(
|
borderRadius: BorderRadius.circular(12),
|
||||||
mainAxisSize: MainAxisSize.min,
|
boxShadow: [
|
||||||
children: [
|
BoxShadow(
|
||||||
CircularProgressIndicator(),
|
color: Colors.black.withValues(alpha: 0.15),
|
||||||
SizedBox(height: 8),
|
blurRadius: 10,
|
||||||
Text('Localisation en cours...'),
|
offset: const Offset(0, 4),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: _suggestions.length,
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
height: 1,
|
||||||
|
color: Colors.grey[300],
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final suggestion = _suggestions[index];
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => _selectPlace(suggestion),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.place,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
size: 22,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
suggestion.description,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Bouton flottant pour recentrer sur la position actuelle
|
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: _getCurrentLocation,
|
onPressed: _getCurrentLocation,
|
||||||
tooltip: 'Ma position',
|
tooltip: 'Ma position',
|
||||||
child: Icon(Icons.my_location),
|
child: const Icon(Icons.my_location),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PlaceSuggestion {
|
||||||
|
final String placeId;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
PlaceSuggestion({
|
||||||
|
required this.placeId,
|
||||||
|
required this.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
42
pubspec.lock
42
pubspec.lock
@@ -129,6 +129,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.11"
|
||||||
|
dio:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.9.0"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -376,6 +392,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.14+2"
|
version: "0.5.14+2"
|
||||||
|
google_places_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: google_places_flutter
|
||||||
|
sha256: "37bd64221cf4a5aa97eb3a33dc2d40f6326aa5ae4e2f2a9a7116bdc1a14f5194"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
google_sign_in:
|
google_sign_in:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -433,7 +457,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.6"
|
version: "0.15.6"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||||
@@ -528,6 +552,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -640,6 +672,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.5+1"
|
version: "6.1.5+1"
|
||||||
|
rxdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rxdart
|
||||||
|
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.28.0"
|
||||||
sanitize_html:
|
sanitize_html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ dependencies:
|
|||||||
google_sign_in: ^7.2.0
|
google_sign_in: ^7.2.0
|
||||||
google_sign_in_platform_interface: ^3.1.0
|
google_sign_in_platform_interface: ^3.1.0
|
||||||
geolocator: ^14.0.2
|
geolocator: ^14.0.2
|
||||||
|
google_places_flutter: ^2.1.1
|
||||||
|
http: ^1.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user