331 lines
11 KiB
Dart
331 lines
11 KiB
Dart
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 à l’utilisateur
|
||
}
|
||
} 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),
|
||
),
|
||
);
|
||
}
|
||
}
|