Merge pull request 'feature/routing-map' (#15) from feature/routing-map into main
Reviewed-on: #15
This commit is contained in:
commit
67b56b5764
@ -244,7 +244,7 @@ class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => Mapboxpages(
|
builder: (_) => MapboxPages(
|
||||||
title: '${widget.title}',
|
title: '${widget.title}',
|
||||||
place: '${place}')));
|
place: '${place}')));
|
||||||
},
|
},
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/services.dart'; // For loading assets
|
|
||||||
import 'package:flutter/material.dart';
|
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:http/http.dart' as http;
|
||||||
import 'package:mapbox_gl/mapbox_gl.dart';
|
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/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 '../variable/globals.dart' as globals;
|
||||||
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
await dotenv.load(fileName: ".env"); // Load .env file
|
await dotenv.load(fileName: ".env"); // Load .env file
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
@ -24,46 +22,48 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'Flutter Directions Example',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
),
|
),
|
||||||
home: const Mapboxpages(title: 'Event Location', place: "Flutter"),
|
home: const MapboxPages(title: 'Event Location', place: "Flutter"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Mapboxpages extends StatefulWidget {
|
class MapboxPages extends StatefulWidget {
|
||||||
const Mapboxpages({Key? key, required this.title, required this.place})
|
const MapboxPages({Key? key, required this.title, required this.place})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String place;
|
final String place;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Mapboxpages> createState() => _MapboxpagesState();
|
State<MapboxPages> createState() => _MapboxPagesState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MapboxpagesState extends State<Mapboxpages> with ShowErrorDialog {
|
class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
|
||||||
|
late MapboxMapController mapController;
|
||||||
late String mapboxAccessToken;
|
late String mapboxAccessToken;
|
||||||
late MapboxMapController mapController; // Mark mapController as nullable
|
List<LatLng> routeCoordinates = [];
|
||||||
|
String selectedMode = 'driving';
|
||||||
double longitude = 0.0;
|
double longitude = 0.0;
|
||||||
double latitude = 0.0;
|
double latitude = 0.0;
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
|
late LatLng userPosition;
|
||||||
|
bool isUserPositionInitialized = false;
|
||||||
|
Line? currentRouteLine;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initToken();
|
_getUserLocation();
|
||||||
_getEventInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the Mapbox access token from the .env file
|
|
||||||
void _initToken() {
|
void _initToken() {
|
||||||
mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
|
mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
|
||||||
if (mapboxAccessToken.isEmpty) {
|
if (mapboxAccessToken.isEmpty) {
|
||||||
showErrorDialog(context, "Mapbox Access Token is not available.");
|
showErrorDialog(context, "Mapbox Access Token is not available.");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,68 +76,110 @@ class _MapboxpagesState extends State<Mapboxpages> with ShowErrorDialog {
|
|||||||
|
|
||||||
var responseGet = await http.get(urlGet,
|
var responseGet = await http.get(urlGet,
|
||||||
headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'});
|
headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'});
|
||||||
stderr.writeln('Response Get status: ${responseGet.statusCode}');
|
|
||||||
if (responseGet.statusCode == 200) {
|
if (responseGet.statusCode == 200) {
|
||||||
var events = jsonDecode(utf8.decode(responseGet.bodyBytes));
|
var events = jsonDecode(utf8.decode(responseGet.bodyBytes));
|
||||||
latitude = events["latitude"];
|
latitude = events["latitude"];
|
||||||
longitude = events["longitude"];
|
longitude = events["longitude"];
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var text = "";
|
_handleErrorResponse(responseGet.statusCode);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showErrorDialog(context, "Cache invalide");
|
showErrorDialog(context, "Invalid cache.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load image from assets as Uint8List
|
void _handleErrorResponse(int statusCode) {
|
||||||
Future<Uint8List> _loadMarkerImage() async {
|
String text;
|
||||||
final ByteData data = await rootBundle.load('images/marker.png');
|
switch (statusCode) {
|
||||||
return data.buffer.asUint8List();
|
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) {
|
Future<void> _getUserLocation() async {
|
||||||
mapController = controller;
|
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<void> _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<LatLng>((coord) {
|
||||||
|
return LatLng(coord[1], coord[0]);
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showErrorDialog(
|
||||||
|
context, "Failed to fetch the route: ${response.statusCode}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when the map is created
|
// Called when the map is created
|
||||||
@ -152,15 +194,15 @@ class _MapboxpagesState extends State<Mapboxpages> with ShowErrorDialog {
|
|||||||
// Ensure the coordinates are valid
|
// Ensure the coordinates are valid
|
||||||
if (latitude != 0.0 && longitude != 0.0) {
|
if (latitude != 0.0 && longitude != 0.0) {
|
||||||
// Load marker image as Uint8List
|
// Load marker image as Uint8List
|
||||||
final markerImage = await _loadMarkerImage();
|
final userMarkerImage = await _loadMarkerImage('images/marker.png');
|
||||||
|
|
||||||
// Register the image with Mapbox
|
// Register the image with Mapbox
|
||||||
await mapController!.addImage("custom-marker", markerImage);
|
await mapController.addImage('event-marker', userMarkerImage);
|
||||||
|
|
||||||
final symbolOptions = SymbolOptions(
|
final symbolOptions = SymbolOptions(
|
||||||
geometry: LatLng(latitude, longitude),
|
geometry: LatLng(latitude, longitude),
|
||||||
iconImage: "custom-marker", // Use the registered custom marker
|
iconImage: "event-marker", // Use the registered custom marker
|
||||||
iconSize: 0.5, // Optional: Adjust size
|
iconSize: 0.4, // Optional: Adjust size
|
||||||
);
|
);
|
||||||
|
|
||||||
// Debugging symbol options
|
// Debugging symbol options
|
||||||
@ -181,21 +223,166 @@ class _MapboxpagesState extends State<Mapboxpages> with ShowErrorDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _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<LatLng> 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<Uint8List> _loadMarkerImage(String assetPath) async {
|
||||||
|
final ByteData data = await rootBundle.load(assetPath);
|
||||||
|
return data.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(widget.place)),
|
appBar: AppBar(
|
||||||
body: isLoading
|
title: Text(widget.place),
|
||||||
? Center(child: CircularProgressIndicator())
|
actions: [
|
||||||
: MapboxMap(
|
DropdownButton<String>(
|
||||||
accessToken: mapboxAccessToken, // Your Mapbox API key
|
value: selectedMode,
|
||||||
onMapCreated: _onMapCreated,
|
items: [
|
||||||
onStyleLoadedCallback: _onStyleLoaded,
|
DropdownMenuItem(
|
||||||
initialCameraPosition: CameraPosition(
|
value: 'walking',
|
||||||
target: LatLng(latitude, longitude),
|
child: Row(
|
||||||
zoom: 14.0,
|
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',
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user