From f880ac10027ed76fbaceeb74af0cee13d97b1d2e Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sat, 16 Nov 2024 23:24:00 +0100 Subject: [PATCH 1/6] add direction 75% --- covas_mobile/lib/pages/ItemMenu.dart | 2 +- covas_mobile/lib/pages/MapboxPages.dart | 266 +++++++++++++++++------- 2 files changed, 190 insertions(+), 78 deletions(-) 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..d0f643f 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; + LatLng? userPosition; @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,7 +76,6 @@ 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"]; @@ -85,59 +84,98 @@ class _MapboxpagesState extends State with ShowErrorDialog { 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); + }); + } 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 +190,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('user-marker', userMarkerImage); final symbolOptions = SymbolOptions( geometry: LatLng(latitude, longitude), - iconImage: "custom-marker", // Use the registered custom marker - iconSize: 0.5, // Optional: Adjust size + iconImage: "user-marker", // Use the registered custom marker + iconSize: 0.4, // Optional: Adjust size ); // Debugging symbol options @@ -181,21 +219,95 @@ class _MapboxpagesState extends State with ShowErrorDialog { } } + Future _drawRouteAndMarkers() async { + 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('event-marker', userMarkerImage); + await mapController.addSymbol(SymbolOptions( + geometry: destination, + iconImage: 'event-marker', // Custom icon for event + iconSize: 0.4, + )); + + // Fetch and draw route + await _fetchRoute(userPosition!, destination, selectedMode); + + if (routeCoordinates.isNotEmpty) { + await mapController.addLine( + LineOptions( + geometry: routeCoordinates, + lineColor: '#3b9ddd', + lineWidth: 5.0, + lineOpacity: 0.8, + ), + ); + } + } else { + showErrorDialog(context, "Invalid coordinates or user position."); + } + } + + // 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: ['walking', 'cycling', 'driving'].map((mode) { + return DropdownMenuItem(value: mode, child: Text(mode)); + }).toList(), + 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', ), + ), + ], + ), ); } } From 4a04520800543c9d8b4d819a4d844ead29891c39 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sun, 17 Nov 2024 10:46:17 +0100 Subject: [PATCH 2/6] add messae error --- covas_mobile/lib/pages/MapboxPages.dart | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/covas_mobile/lib/pages/MapboxPages.dart b/covas_mobile/lib/pages/MapboxPages.dart index d0f643f..22df91e 100644 --- a/covas_mobile/lib/pages/MapboxPages.dart +++ b/covas_mobile/lib/pages/MapboxPages.dart @@ -50,14 +50,14 @@ class _MapboxPagesState extends State with ShowErrorDialog { double longitude = 0.0; double latitude = 0.0; bool isLoading = true; - LatLng? userPosition; + late LatLng userPosition; + bool isUserPositionInitialized = false; @override void initState() { super.initState(); _initToken(); _getEventInfo(); - _getUserLocation(); } void _initToken() { @@ -80,6 +80,7 @@ class _MapboxPagesState extends State with ShowErrorDialog { var events = jsonDecode(utf8.decode(responseGet.bodyBytes)); latitude = events["latitude"]; longitude = events["longitude"]; + _getUserLocation(); setState(() { isLoading = false; }); @@ -151,6 +152,7 @@ class _MapboxPagesState extends State with ShowErrorDialog { ); setState(() { userPosition = LatLng(position.latitude, position.longitude); + isUserPositionInitialized = true; }); } catch (e) { showErrorDialog(context, "Failed to get user location: $e"); @@ -193,11 +195,11 @@ class _MapboxPagesState extends State with ShowErrorDialog { final userMarkerImage = await _loadMarkerImage('images/marker.png'); // Register the image with Mapbox - await mapController.addImage('user-marker', userMarkerImage); + await mapController.addImage('event-marker', userMarkerImage); final symbolOptions = SymbolOptions( geometry: LatLng(latitude, longitude), - iconImage: "user-marker", // Use the registered custom marker + iconImage: "event-marker", // Use the registered custom marker iconSize: 0.4, // Optional: Adjust size ); @@ -220,6 +222,12 @@ class _MapboxPagesState extends State with ShowErrorDialog { } Future _drawRouteAndMarkers() async { + if (!isUserPositionInitialized) { + showErrorDialog( + context, "User position is not yet initialized. Try again."); + return; + } + if (mapController != null && userPosition != null && latitude != 0.0 && @@ -232,10 +240,10 @@ class _MapboxPagesState extends State with ShowErrorDialog { final userMarkerImage = await _loadMarkerImage('images/marker.png'); // Register the image with Mapbox - await mapController.addImage('event-marker', userMarkerImage); + await mapController.addImage('user-marker', userMarkerImage); await mapController.addSymbol(SymbolOptions( - geometry: destination, - iconImage: 'event-marker', // Custom icon for event + geometry: userPosition, + iconImage: 'user-marker', // Custom icon for event iconSize: 0.4, )); From af65dc1cb0c2ef1c34955fb87a1f0221e25c9d83 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sun, 17 Nov 2024 10:58:29 +0100 Subject: [PATCH 3/6] geolocation first --- covas_mobile/lib/pages/MapboxPages.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/covas_mobile/lib/pages/MapboxPages.dart b/covas_mobile/lib/pages/MapboxPages.dart index 22df91e..701cda6 100644 --- a/covas_mobile/lib/pages/MapboxPages.dart +++ b/covas_mobile/lib/pages/MapboxPages.dart @@ -56,8 +56,7 @@ class _MapboxPagesState extends State with ShowErrorDialog { @override void initState() { super.initState(); - _initToken(); - _getEventInfo(); + _getUserLocation(); } void _initToken() { @@ -80,7 +79,7 @@ class _MapboxPagesState extends State with ShowErrorDialog { var events = jsonDecode(utf8.decode(responseGet.bodyBytes)); latitude = events["latitude"]; longitude = events["longitude"]; - _getUserLocation(); + setState(() { isLoading = false; }); @@ -154,6 +153,8 @@ class _MapboxPagesState extends State with ShowErrorDialog { userPosition = LatLng(position.latitude, position.longitude); isUserPositionInitialized = true; }); + _initToken(); + _getEventInfo(); } catch (e) { showErrorDialog(context, "Failed to get user location: $e"); } From 7f5d59857cdbcc28f9de296ad2363a8d738e5b6c Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sun, 17 Nov 2024 11:10:43 +0100 Subject: [PATCH 4/6] re-zoom ok --- covas_mobile/lib/pages/MapboxPages.dart | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/covas_mobile/lib/pages/MapboxPages.dart b/covas_mobile/lib/pages/MapboxPages.dart index 701cda6..1c5e2bc 100644 --- a/covas_mobile/lib/pages/MapboxPages.dart +++ b/covas_mobile/lib/pages/MapboxPages.dart @@ -260,12 +260,42 @@ class _MapboxPagesState extends State with ShowErrorDialog { 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); From c8223d9b7df57199434b33444a52aab854af42b6 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sun, 17 Nov 2024 11:31:07 +0100 Subject: [PATCH 5/6] remove line after change --- covas_mobile/lib/pages/MapboxPages.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/covas_mobile/lib/pages/MapboxPages.dart b/covas_mobile/lib/pages/MapboxPages.dart index 1c5e2bc..0cda564 100644 --- a/covas_mobile/lib/pages/MapboxPages.dart +++ b/covas_mobile/lib/pages/MapboxPages.dart @@ -52,6 +52,7 @@ class _MapboxPagesState extends State with ShowErrorDialog { bool isLoading = true; late LatLng userPosition; bool isUserPositionInitialized = false; + Line? currentRouteLine; @override void initState() { @@ -223,6 +224,11 @@ 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."); @@ -249,10 +255,10 @@ class _MapboxPagesState extends State with ShowErrorDialog { )); // Fetch and draw route - await _fetchRoute(userPosition!, destination, selectedMode); + await _fetchRoute(userPosition, destination, selectedMode); if (routeCoordinates.isNotEmpty) { - await mapController.addLine( + currentRouteLine = await mapController.addLine( LineOptions( geometry: routeCoordinates, lineColor: '#3b9ddd', @@ -260,6 +266,7 @@ class _MapboxPagesState extends State with ShowErrorDialog { lineOpacity: 0.8, ), ); + _zoomToFitRoute(routeCoordinates); } } else { From de6a7f239979313a8d8504d7f7adaad22d58b6bb Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sun, 17 Nov 2024 11:38:59 +0100 Subject: [PATCH 6/6] icons worked --- covas_mobile/lib/pages/MapboxPages.dart | 35 ++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/covas_mobile/lib/pages/MapboxPages.dart b/covas_mobile/lib/pages/MapboxPages.dart index 0cda564..6fab826 100644 --- a/covas_mobile/lib/pages/MapboxPages.dart +++ b/covas_mobile/lib/pages/MapboxPages.dart @@ -317,9 +317,38 @@ class _MapboxPagesState extends State with ShowErrorDialog { actions: [ DropdownButton( value: selectedMode, - items: ['walking', 'cycling', 'driving'].map((mode) { - return DropdownMenuItem(value: mode, child: Text(mode)); - }).toList(), + 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!;