diff --git a/covas_mobile/lib/pages/ItemMenu.dart b/covas_mobile/lib/pages/ItemMenu.dart index 82e64cf..c83c204 100644 --- a/covas_mobile/lib/pages/ItemMenu.dart +++ b/covas_mobile/lib/pages/ItemMenu.dart @@ -244,7 +244,7 @@ class _ItemMenuState extends State with ShowErrorDialog { Navigator.push( context, MaterialPageRoute( - builder: (_) => Mapboxpages( + builder: (_) => MapboxPages( title: '${widget.title}', place: '${place}'))); }, diff --git a/covas_mobile/lib/pages/MapboxPages.dart b/covas_mobile/lib/pages/MapboxPages.dart index fc09080..6fab826 100644 --- a/covas_mobile/lib/pages/MapboxPages.dart +++ b/covas_mobile/lib/pages/MapboxPages.dart @@ -1,18 +1,16 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'dart:io'; -import 'package:flutter/services.dart'; // For loading assets import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; // For environment variables +import 'package:flutter/services.dart'; // For loading assets import 'package:http/http.dart' as http; import 'package:mapbox_gl/mapbox_gl.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:geolocator/geolocator.dart'; // For getting the user's location import '../classes/alert.dart'; // Assuming this contains your error dialog code. -import '../classes/events.dart'; // Your Event class, assuming you are using it. import '../variable/globals.dart' as globals; -import 'package:shared_preferences/shared_preferences.dart'; - void main() async { await dotenv.load(fileName: ".env"); // Load .env file runApp(const MyApp()); @@ -24,46 +22,48 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'Flutter Directions Example', theme: ThemeData( primarySwatch: Colors.blue, ), - home: const Mapboxpages(title: 'Event Location', place: "Flutter"), + home: const MapboxPages(title: 'Event Location', place: "Flutter"), ); } } -class Mapboxpages extends StatefulWidget { - const Mapboxpages({Key? key, required this.title, required this.place}) +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 createState() => _MapboxpagesState(); + State createState() => _MapboxPagesState(); } -class _MapboxpagesState extends State with ShowErrorDialog { +class _MapboxPagesState extends State with ShowErrorDialog { + late MapboxMapController mapController; late String mapboxAccessToken; - late MapboxMapController mapController; // Mark mapController as nullable + List routeCoordinates = []; + String selectedMode = 'driving'; double longitude = 0.0; double latitude = 0.0; bool isLoading = true; + late LatLng userPosition; + bool isUserPositionInitialized = false; + Line? currentRouteLine; @override void initState() { super.initState(); - _initToken(); - _getEventInfo(); + _getUserLocation(); } - // Load the Mapbox access token from the .env file void _initToken() { mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; if (mapboxAccessToken.isEmpty) { showErrorDialog(context, "Mapbox Access Token is not available."); - return; } } @@ -76,68 +76,110 @@ class _MapboxpagesState extends State with ShowErrorDialog { var responseGet = await http.get(urlGet, headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'}); - stderr.writeln('Response Get status: ${responseGet.statusCode}'); if (responseGet.statusCode == 200) { var events = jsonDecode(utf8.decode(responseGet.bodyBytes)); latitude = events["latitude"]; longitude = events["longitude"]; + setState(() { isLoading = false; }); } else { - var text = ""; - switch (responseGet.statusCode) { - case 400: - { - text = "RequĂȘte mal construite"; - } - break; - case 406: - { - text = "Mot de passe incorrect"; - } - break; - case 404: - { - text = "Utilisateur inconnu"; - } - break; - case 403: - { - text = "Vous n'avez pas l'autorisation de faire cette action"; - } - break; - case 410: - { - text = "Token invalide"; - } - break; - case 500: - { - text = "Probleme interne du serveur"; - } - break; - default: - { - text = "Probleme d'authentification inconnu"; - } - break; - } - showErrorDialog(context, text); + _handleErrorResponse(responseGet.statusCode); } } else { - showErrorDialog(context, "Cache invalide"); + showErrorDialog(context, "Invalid cache."); } } - // Load image from assets as Uint8List - Future _loadMarkerImage() async { - final ByteData data = await rootBundle.load('images/marker.png'); - return data.buffer.asUint8List(); + void _handleErrorResponse(int statusCode) { + String text; + switch (statusCode) { + case 400: + text = "Bad Request."; + break; + case 406: + text = "Incorrect Password."; + break; + case 404: + text = "User Not Found."; + break; + case 403: + text = "Action not permitted."; + break; + case 410: + text = "Invalid Token."; + break; + case 500: + text = "Internal Server Error."; + break; + default: + text = "Unknown error."; + } + showErrorDialog(context, text); } - void _onMapCreated(MapboxMapController controller) { - mapController = controller; + Future _getUserLocation() async { + try { + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + showErrorDialog(context, "Location services are disabled."); + return; + } + + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + showErrorDialog(context, "Location permissions are denied."); + return; + } + } + + if (permission == LocationPermission.deniedForever) { + showErrorDialog(context, + "Location permissions are permanently denied. Enable them in settings."); + return; + } + LocationSettings locationSettings = LocationSettings( + accuracy: LocationAccuracy.high, + distanceFilter: + 10, // Optional: Minimum distance (in meters) to trigger location update + ); + + Position position = await Geolocator.getCurrentPosition( + locationSettings: locationSettings, + ); + setState(() { + userPosition = LatLng(position.latitude, position.longitude); + isUserPositionInitialized = true; + }); + _initToken(); + _getEventInfo(); + } catch (e) { + showErrorDialog(context, "Failed to get user location: $e"); + } + } + + Future _fetchRoute( + LatLng origin, LatLng destination, String mode) async { + final url = Uri.parse( + 'https://api.mapbox.com/directions/v5/mapbox/$mode/${origin.longitude},${origin.latitude};${destination.longitude},${destination.latitude}?geometries=geojson&access_token=$mapboxAccessToken', + ); + + final response = await http.get(url); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + final geometry = data['routes'][0]['geometry']['coordinates']; + setState(() { + routeCoordinates = geometry.map((coord) { + return LatLng(coord[1], coord[0]); + }).toList(); + }); + } else { + showErrorDialog( + context, "Failed to fetch the route: ${response.statusCode}"); + } } // Called when the map is created @@ -152,15 +194,15 @@ class _MapboxpagesState extends State with ShowErrorDialog { // Ensure the coordinates are valid if (latitude != 0.0 && longitude != 0.0) { // Load marker image as Uint8List - final markerImage = await _loadMarkerImage(); + final userMarkerImage = await _loadMarkerImage('images/marker.png'); // Register the image with Mapbox - await mapController!.addImage("custom-marker", markerImage); + await mapController.addImage('event-marker', userMarkerImage); final symbolOptions = SymbolOptions( geometry: LatLng(latitude, longitude), - iconImage: "custom-marker", // Use the registered custom marker - iconSize: 0.5, // Optional: Adjust size + iconImage: "event-marker", // Use the registered custom marker + iconSize: 0.4, // Optional: Adjust size ); // Debugging symbol options @@ -181,21 +223,166 @@ class _MapboxpagesState extends State with ShowErrorDialog { } } + Future _drawRouteAndMarkers() async { + // Remove previous route line if it exists + if (currentRouteLine != null) { + await mapController.removeLine(currentRouteLine!); + currentRouteLine = null; + } + if (!isUserPositionInitialized) { + showErrorDialog( + context, "User position is not yet initialized. Try again."); + return; + } + + if (mapController != null && + userPosition != null && + latitude != 0.0 && + longitude != 0.0) { + final destination = LatLng(latitude, longitude); + + // Register the custom images + // Add event marker + + final userMarkerImage = await _loadMarkerImage('images/marker.png'); + + // Register the image with Mapbox + await mapController.addImage('user-marker', userMarkerImage); + await mapController.addSymbol(SymbolOptions( + geometry: userPosition, + iconImage: 'user-marker', // Custom icon for event + iconSize: 0.4, + )); + + // Fetch and draw route + await _fetchRoute(userPosition, destination, selectedMode); + + if (routeCoordinates.isNotEmpty) { + currentRouteLine = await mapController.addLine( + LineOptions( + geometry: routeCoordinates, + lineColor: '#3b9ddd', + lineWidth: 5.0, + lineOpacity: 0.8, + ), + ); + + _zoomToFitRoute(routeCoordinates); + } + } else { + showErrorDialog(context, "Invalid coordinates or user position."); + } + } + + void _zoomToFitRoute(List coordinates) { + // Calculate the bounding box + double minLat = coordinates.first.latitude; + double maxLat = coordinates.first.latitude; + double minLng = coordinates.first.longitude; + double maxLng = coordinates.first.longitude; + + for (LatLng coord in coordinates) { + if (coord.latitude < minLat) minLat = coord.latitude; + if (coord.latitude > maxLat) maxLat = coord.latitude; + if (coord.longitude < minLng) minLng = coord.longitude; + if (coord.longitude > maxLng) maxLng = coord.longitude; + } + + // Define the bounds + LatLng southwest = LatLng(minLat, minLng); + LatLng northeast = LatLng(maxLat, maxLng); + + mapController.moveCamera( + CameraUpdate.newLatLngBounds( + LatLngBounds(southwest: southwest, northeast: northeast), + left: 50, // Padding on the left + top: 50, // Padding on the top + right: 50, // Padding on the right + bottom: 50, // Padding on the bottom + ), + ); + } + + // Load image from assets + Future _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)), - body: isLoading - ? Center(child: CircularProgressIndicator()) - : MapboxMap( - accessToken: mapboxAccessToken, // Your Mapbox API key - onMapCreated: _onMapCreated, - onStyleLoadedCallback: _onStyleLoaded, - initialCameraPosition: CameraPosition( - target: LatLng(latitude, longitude), - zoom: 14.0, + appBar: AppBar( + title: Text(widget.place), + actions: [ + DropdownButton( + value: selectedMode, + items: [ + DropdownMenuItem( + value: 'walking', + child: Row( + children: [ + Icon(Icons.directions_walk, color: Colors.blue), + SizedBox(width: 8), + Text('Walking'), + ], + ), ), + DropdownMenuItem( + value: 'cycling', + child: Row( + children: [ + Icon(Icons.directions_bike, color: Colors.green), + SizedBox(width: 8), + Text('Cycling'), + ], + ), + ), + DropdownMenuItem( + value: 'driving', + child: Row( + children: [ + Icon(Icons.directions_car, color: Colors.red), + SizedBox(width: 8), + Text('Driving'), + ], + ), + ), + ], + onChanged: (mode) { + setState(() { + selectedMode = mode!; + }); + }, + ) + ], + ), + body: Stack( + children: [ + isLoading + ? Center(child: CircularProgressIndicator()) + : MapboxMap( + accessToken: mapboxAccessToken, + onMapCreated: (controller) { + mapController = controller; + }, + onStyleLoadedCallback: _onStyleLoaded, + initialCameraPosition: CameraPosition( + target: LatLng(latitude, longitude), + zoom: 14.0, + ), + ), + Positioned( + bottom: 20, + right: 20, + child: FloatingActionButton( + onPressed: _drawRouteAndMarkers, + child: Icon(Icons.directions), + tooltip: 'Get Directions and Markers', ), + ), + ], + ), ); } }