Files
mobile-flutter/covas_mobile_new/lib/pages/MapboxPages.dart
2025-09-04 18:11:12 +02:00

331 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' as mapbox;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:geolocator/geolocator.dart' as geo;
import '../classes/alert.dart';
import '../variable/globals.dart' as globals;
import '../classes/MyDrawer.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:covas_mobile/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
// Set the access token globally
mapbox.MapboxOptions.setAccessToken(dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Directions Example',
theme: ThemeData(primarySwatch: Colors.blue),
home: const MapboxPages(title: 'Event Location', place: "Flutter"),
);
}
}
class MapboxPages extends StatefulWidget {
const MapboxPages({Key? key, required this.title, required this.place})
: super(key: key);
final String title;
final String place;
@override
State<MapboxPages> createState() => _MapboxPagesState();
}
class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog {
final AuthService _authService = AuthService();
mapbox.MapboxMap? mapboxMap;
mapbox.PointAnnotationManager? pointAnnotationManager;
mapbox.PolylineAnnotationManager? polylineAnnotationManager;
double longitude = 0.0;
double latitude = 0.0;
bool isLoading = true;
mapbox.Point? userPosition;
bool isUserPositionInitialized = false;
String selectedMode = 'driving';
List<List<double>> routeCoordinates = [];
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
_getUserLocation();
}
Future<void> _getEventInfo() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var urlGet = Uri.parse("${globals.api}/events/${widget.title}");
var responseGet = await http.get(urlGet,
headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'});
if (responseGet.statusCode == 200) {
var events = jsonDecode(utf8.decode(responseGet.bodyBytes));
latitude = events["latitude"];
longitude = events["longitude"];
setState(() => isLoading = false);
} else {
_handleErrorResponse(responseGet.statusCode);
}
} else {
showAlertDialog(context, "Error", "Invalid cache.");
}
}
void _handleErrorResponse(int statusCode) {
final errorMessage = "Error $statusCode fetching event";
showAlertDialog(context, "Error", errorMessage);
}
Future<void> _getUserLocation() async {
await dotenv.load(fileName: ".env");
// Set the access token globally
mapbox.MapboxOptions.setAccessToken(
dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '');
try {
bool serviceEnabled = await geo.Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) return;
geo.LocationPermission permission =
await geo.Geolocator.checkPermission();
if (permission == geo.LocationPermission.denied) {
permission = await geo.Geolocator.requestPermission();
}
if (permission == geo.LocationPermission.deniedForever) return;
geo.Position position = await geo.Geolocator.getCurrentPosition();
setState(() {
userPosition = mapbox.Point(
coordinates:
mapbox.Position(position.longitude, position.latitude));
isUserPositionInitialized = true;
});
_getEventInfo();
} catch (e) {
showAlertDialog(context, "Error", "Failed to get location");
}
}
Future<void> _fetchRoute(
mapbox.Point origin, mapbox.Point destination, String mode) async {
final url = Uri.parse(
'https://api.mapbox.com/directions/v5/mapbox/$mode/'
'${origin.coordinates.lng},${origin.coordinates.lat};'
'${destination.coordinates.lng},${destination.coordinates.lat}'
'?geometries=geojson&access_token=${dotenv.env['MAPBOX_ACCESS_TOKEN']}',
);
final response = await http.get(url);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
// Vérifie si 'routes' existe et contient au moins 1 élément
if (data['routes'] != null && (data['routes'] as List).isNotEmpty) {
final geometry = data['routes'][0]['geometry']['coordinates'];
setState(() {
routeCoordinates = (geometry as List)
.map<List<double>>((coord) => [coord[0], coord[1]])
.toList();
});
} else {
debugPrint("⚠️ Aucune route trouvée entre ${origin} et $destination.");
// Optionnel : afficher un snackbar/toast à lutilisateur
}
} else {
debugPrint("❌ Erreur API Mapbox: ${response.statusCode}");
}
}
Future<void> _zoomToFitRoute(List<List<double>> coordinates) async {
if (mapboxMap == null || coordinates.isEmpty) return;
double minLat = coordinates.first[1];
double maxLat = coordinates.first[1];
double minLng = coordinates.first[0];
double maxLng = coordinates.first[0];
for (var coord in coordinates) {
if (coord[1] < minLat) minLat = coord[1];
if (coord[1] > maxLat) maxLat = coord[1];
if (coord[0] < minLng) minLng = coord[0];
if (coord[0] > maxLng) maxLng = coord[0];
}
final bounds = mapbox.CoordinateBounds(
southwest: mapbox.Point(coordinates: mapbox.Position(minLng, minLat)),
northeast: mapbox.Point(coordinates: mapbox.Position(maxLng, maxLat)),
infiniteBounds: true);
// Calculer une CameraOptions automatiquement à partir des bounds
final cameraOptions = await mapboxMap!.cameraForCoordinateBounds(
bounds,
mapbox.MbxEdgeInsets(
top: 50, left: 50, right: 50, bottom: 50), // marges
0.0,
0.0,
null,
null);
// Appliquer la caméra avec animation
await mapboxMap!.flyTo(
cameraOptions,
mapbox.MapAnimationOptions(duration: 1000),
);
}
Future<void> _drawRouteAndMarkers() async {
if (mapboxMap == null || !isUserPositionInitialized) return;
// Managers
pointAnnotationManager ??=
await mapboxMap!.annotations.createPointAnnotationManager();
polylineAnnotationManager ??=
await mapboxMap!.annotations.createPolylineAnnotationManager();
// Clear old annotations
await pointAnnotationManager!.deleteAll();
await polylineAnnotationManager!.deleteAll();
final destination =
mapbox.Point(coordinates: mapbox.Position(longitude, latitude));
// Add user marker
final userIcon = await _loadMarkerImage('images/marker.png');
await pointAnnotationManager!.create(mapbox.PointAnnotationOptions(
geometry: mapbox.Point(
coordinates: mapbox.Position(
userPosition!.coordinates.lng, userPosition!.coordinates.lat)),
image: userIcon,
iconSize: 0.4,
));
// Ajoute directement la flèche rouge à la position de lévénement
final eventIcon = await _loadMarkerImage('images/marker-red.png');
await pointAnnotationManager!.create(mapbox.PointAnnotationOptions(
geometry: mapbox.Point(coordinates: mapbox.Position(longitude, latitude)),
image: eventIcon,
iconSize: 0.2,
));
// Fetch and draw route
await _fetchRoute(userPosition!, destination, selectedMode);
if (routeCoordinates.isNotEmpty) {
await polylineAnnotationManager!.create(mapbox.PolylineAnnotationOptions(
geometry: mapbox.LineString(
coordinates:
routeCoordinates.map((c) => mapbox.Position(c[0], c[1])).toList(),
),
lineColor: Colors.blue.value,
lineWidth: 4.0,
));
await _zoomToFitRoute(routeCoordinates);
}
}
Future<Uint8List> _loadMarkerImage(String assetPath) async {
final ByteData data = await rootBundle.load(assetPath);
return data.buffer.asUint8List();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.place)),
drawer: MyDrawer(),
body: isLoading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Mode : "),
DropdownButton<String>(
value: selectedMode,
items: const [
DropdownMenuItem(
value: 'driving', child: Text("🚗 Voiture")),
DropdownMenuItem(
value: 'walking', child: Text("🚶 Marche")),
DropdownMenuItem(
value: 'cycling', child: Text("🚴 Vélo")),
],
onChanged: (value) {
if (value != null) {
setState(() {
selectedMode = value;
});
}
},
),
],
),
Expanded(
child: mapbox.MapWidget(
onMapCreated: (controller) async {
mapboxMap = controller;
// Crée un manager si nécessaire
pointAnnotationManager ??= await mapboxMap!.annotations
.createPointAnnotationManager();
// Ajoute directement la flèche rouge à la position de lévénement
final eventIcon =
await _loadMarkerImage('images/marker-red.png');
await pointAnnotationManager!
.create(mapbox.PointAnnotationOptions(
geometry: mapbox.Point(
coordinates: mapbox.Position(longitude, latitude)),
image: eventIcon,
iconSize: 0.2,
));
},
cameraOptions: mapbox.CameraOptions(
center: mapbox.Point(
coordinates: mapbox.Position(longitude, latitude)),
zoom: 14.0,
),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _drawRouteAndMarkers,
child: const Icon(Icons.directions),
),
);
}
}