16 Commits

12 changed files with 242 additions and 346 deletions

View File

@@ -13,6 +13,7 @@ android {
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
} }
kotlinOptions { kotlinOptions {
@@ -39,6 +40,10 @@ android {
} }
} }
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}
flutter { flutter {
source = "../.." source = "../.."
} }

View File

@@ -1,8 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application
android:label="covas_mobile_new" android:label="covas_mobile_new"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-4855855675386260~3438207239"/>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@@ -11,12 +18,13 @@
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:enableOnBackInvokedCallback="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. --> to determine the Window background behind the Flutter UI. -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"
/> />

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -6,21 +6,28 @@ import 'pages/LoginDemo.dart';
import 'locale_provider.dart'; // <-- à adapter selon ton arborescence import 'locale_provider.dart'; // <-- à adapter selon ton arborescence
import 'package:covas_mobile/gen_l10n/app_localizations.dart'; import 'package:covas_mobile/gen_l10n/app_localizations.dart';
import 'classes/notification_service.dart'; import 'classes/notification_service.dart';
import 'classes/auth_service.dart';
import 'pages/ListItemMenu.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize(); await MobileAds.instance.initialize();
await NotificationService.initialize(); await NotificationService.initialize();
final AuthService _authService = AuthService();
final loggedIn = await _authService.isLoggedIn();
runApp( runApp(
ChangeNotifierProvider( ChangeNotifierProvider(
create: (_) => LocaleProvider(), create: (_) => LocaleProvider(),
child: MyApp(), child: MyApp(isLoggedIn: loggedIn),
), ),
); );
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
final bool isLoggedIn;
const MyApp({Key? key, required this.isLoggedIn}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final localeProvider = Provider.of<LocaleProvider>( final localeProvider = Provider.of<LocaleProvider>(
@@ -30,7 +37,7 @@ class MyApp extends StatelessWidget {
locale: localeProvider.locale, // <-- utilise la locale courante locale: localeProvider.locale, // <-- utilise la locale courante
supportedLocales: L10n.all, supportedLocales: L10n.all,
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,
home: LoginDemo(), home: isLoggedIn ? ListItemMenu() : LoginDemo(),
); );
} }
} }

View File

@@ -221,15 +221,13 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
final file = File(widget.imagePath); final file = File(widget.imagePath);
gemini gemini.prompt(parts: [
.textAndImage( Part.text(
text: "Peux-tu donner le nom, la date (si l'année n'est pas précisé, mettez l'année actuelle ou future) et le lieu de l'évènement sous format JSON (sans le caratère json au début de la chaine de caractère) avec les valeurs suivantes : name, place, description, tags (tableau sans espace), organizers (tableau), start_date et end_date (si le end_date est vide, alors donnez une valeur de six de plus par rapport à start_date) sous le format en YYYY-MM-DD HH:mm:ssZ"),
"Peux-tu donner le nom, la date (si l'année n'est pas précisé, mettez l'année actuelle ou future) et le lieu de l'évènement sous format JSON (sans le caratère json au début de la chaine de caractère) avec les valeurs suivantes : name, place, description, tags (tableau sans espace), organizers (tableau), start_date et end_date (si le end_date est vide, alors donnez une valeur de six de plus par rapport à start_date) sous le format en YYYY-MM-DD HH:mm:ssZ", Part.bytes(file.readAsBytesSync())
images: [file.readAsBytesSync()], ], model: "models/gemini-1.5-pro-latest").then((value) {
modelName: "models/gemini-1.5-pro-latest") searchEvents(value?.output ?? '', widget.imagePath);
.then((value) => searchEvents( }).catchError((e) => displayError);
value?.content?.parts?.last.text ?? '', widget.imagePath))
.catchError((e) => displayError);
} }
@override @override

View File

@@ -380,8 +380,8 @@ class _EditEventState extends State<EditEvent>
imgUrl = widget.events!.imgUrl ?? ""; imgUrl = widget.events!.imgUrl ?? "";
inputDesc.text = widget.events!.description ?? ""; inputDesc.text = widget.events!.description ?? "";
initialTags = List<String>.from(widget.events!.tags as List); initialTags = List<String>.from((widget.events?.tags as List?) ?? []);
initialOrga = List<String>.from(widget.events!.organizers as List); initialOrga = List<String>.from((widget.events?.organizers as List?) ?? []);
} }
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();

View File

@@ -48,17 +48,18 @@ class _EditProfileState extends State<EditSettings>
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
TextEditingController inputUserName = TextEditingController(); TextEditingController inputUserName = TextEditingController();
int? kilometer; int? kilometer = 50;
Future<void> getParameter() async { Future<void> getParameter() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() { setState(() {
var kilometer = prefs.getDouble("kilometer")?.toInt() ?? null; kilometer = prefs.getDouble("kilometer")?.toInt() ?? 50;
}); });
} }
Future<void> setParameter() async { Future<void> setParameter() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
print("kilometer : ${kilometer}");
if (kilometer != null) { if (kilometer != null) {
prefs.setDouble("kilometer", kilometer?.toDouble() ?? 50); prefs.setDouble("kilometer", kilometer?.toDouble() ?? 50);
showAlertDialog( showAlertDialog(
@@ -115,8 +116,7 @@ class _EditProfileState extends State<EditSettings>
AppLocalizations.of(context)?.define_kilometer ?? AppLocalizations.of(context)?.define_kilometer ??
'Define kilometer', 'Define kilometer',
), ),
value: value: kilometer,
kilometer, // Set the initial selected value here, or leave as `null` if unselected.
items: [ items: [
DropdownMenuItem( DropdownMenuItem(
value: 5, value: 5,
@@ -155,7 +155,7 @@ class _EditProfileState extends State<EditSettings>
color: Colors.blue, color: Colors.blue,
borderRadius: BorderRadius.circular(20)), borderRadius: BorderRadius.circular(20)),
child: TextButton( child: TextButton(
onPressed: () {}, onPressed: setParameter,
child: Text( child: Text(
AppLocalizations.of(context)?.update ?? "Update", AppLocalizations.of(context)?.update ?? "Update",
style: TextStyle(color: Colors.white, fontSize: 25), style: TextStyle(color: Colors.white, fontSize: 25),

View File

@@ -1,14 +1,15 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // For environment variables import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter/services.dart'; // For loading assets import 'package:flutter/services.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mapbox_gl/mapbox_gl.dart'; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' as mapbox;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:geolocator/geolocator.dart'; // For getting the user's location import 'package:geolocator/geolocator.dart' as geo;
import '../classes/alert.dart'; // Assuming this contains your error dialog code. import '../classes/alert.dart';
import '../variable/globals.dart' as globals; import '../variable/globals.dart' as globals;
import '../classes/MyDrawer.dart'; import '../classes/MyDrawer.dart';
import '../classes/auth_service.dart'; import '../classes/auth_service.dart';
@@ -16,10 +17,15 @@ import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:covas_mobile/gen_l10n/app_localizations.dart'; import 'package:covas_mobile/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../locale_provider.dart'; // import '../locale_provider.dart';
void main() async { void main() async {
await dotenv.load(fileName: ".env"); // Load .env file WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
// Set the access token globally
mapbox.MapboxOptions.setAccessToken(dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '');
runApp(const MyApp()); runApp(const MyApp());
} }
@@ -30,9 +36,7 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'Flutter Directions Example', 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"),
); );
} }
@@ -52,36 +56,26 @@ class MapboxPages extends StatefulWidget {
class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog { class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog {
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
late MapboxMapController mapController; mapbox.MapboxMap? mapboxMap;
late String mapboxAccessToken; mapbox.PointAnnotationManager? pointAnnotationManager;
List<LatLng> routeCoordinates = []; mapbox.PolylineAnnotationManager? polylineAnnotationManager;
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; mapbox.Point? userPosition;
bool isUserPositionInitialized = false; bool isUserPositionInitialized = false;
Line? currentRouteLine;
String selectedMode = 'driving';
List<List<double>> routeCoordinates = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_authService.checkTokenStatus(context); _authService.checkTokenStatus(context);
_getUserLocation(); _getUserLocation();
} }
void _initToken() {
mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
if (mapboxAccessToken.isEmpty) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.map_token ??
"Map Access Token is not available.");
}
}
Future<void> _getEventInfo() async { Future<void> _getEventInfo() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? ""; var accessToken = prefs.getString("access_token") ?? "";
@@ -96,276 +90,170 @@ class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog {
latitude = events["latitude"]; latitude = events["latitude"];
longitude = events["longitude"]; longitude = events["longitude"];
setState(() { setState(() => isLoading = false);
isLoading = false;
});
} else { } else {
_handleErrorResponse(responseGet.statusCode); _handleErrorResponse(responseGet.statusCode);
} }
} else { } else {
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", showAlertDialog(context, "Error", "Invalid cache.");
AppLocalizations.of(context)?.invalid_cache ?? "Invalid cache.");
} }
} }
void _handleErrorResponse(int statusCode) { void _handleErrorResponse(int statusCode) {
final messages = { final errorMessage = "Error $statusCode fetching event";
400: AppLocalizations.of(context)?.request_error ?? showAlertDialog(context, "Error", errorMessage);
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
final errorMessage = messages[statusCode] ??
AppLocalizations.of(context)?.unknown_error_auth ??
"Unknown error auth";
showAlertDialog(
context, AppLocalizations.of(context)?.error ?? "Error", errorMessage);
} }
Future<void> _getUserLocation() async { Future<void> _getUserLocation() async {
await dotenv.load(fileName: ".env");
// Set the access token globally
mapbox.MapboxOptions.setAccessToken(
dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '');
try { try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); bool serviceEnabled = await geo.Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) { if (!serviceEnabled) return;
showAlertDialog(
context, geo.LocationPermission permission =
AppLocalizations.of(context)?.error ?? "Error", await geo.Geolocator.checkPermission();
AppLocalizations.of(context)?.geo_disabled ?? if (permission == geo.LocationPermission.denied) {
"Location services are disabled."); permission = await geo.Geolocator.requestPermission();
return;
} }
LocationPermission permission = await Geolocator.checkPermission(); if (permission == geo.LocationPermission.deniedForever) return;
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission(); geo.Position position = await geo.Geolocator.getCurrentPosition();
if (permission == LocationPermission.denied) { setState(() {
showAlertDialog( userPosition = mapbox.Point(
context, coordinates:
AppLocalizations.of(context)?.error ?? "Error", mapbox.Position(position.longitude, position.latitude));
AppLocalizations.of(context)?.permission_denied ?? isUserPositionInitialized = true;
"Location permissions are denied."); });
return;
}
}
if (permission == LocationPermission.deniedForever) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.enable_permission ??
"Location permissions are permanently denied. Enable them in settings.");
return;
}
const LocationSettings locationSettings = LocationSettings(
accuracy: LocationAccuracy.medium, timeLimit: Duration(seconds: 5));
Position? position;
try {
position = await Geolocator.getCurrentPosition(
locationSettings: locationSettings);
} on LocationServiceDisabledException {
// Handle location services disabled
position = await Geolocator.getLastKnownPosition();
if (position == null) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.no_last_position ??
"No last known position available..");
}
} catch (e) {
// Handle other errors
position = await Geolocator.getLastKnownPosition();
if (position == null) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.no_last_position ??
"No last known position available");
}
}
if (position != null) {
setState(() {
userPosition = LatLng(position!.latitude, position!.longitude);
isUserPositionInitialized = true;
});
}
_initToken();
_getEventInfo(); _getEventInfo();
} catch (e) { } catch (e) {
showAlertDialog( showAlertDialog(context, "Error", "Failed to get location");
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.failed_location ??
"Failed to get user location");
} }
} }
Future<void> _fetchRoute( Future<void> _fetchRoute(
LatLng origin, LatLng destination, String mode) async { mapbox.Point origin, mapbox.Point destination, String mode) async {
final url = Uri.parse( 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', '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); final response = await http.get(url);
if (response.statusCode == 200) { if (response.statusCode == 200) {
final data = jsonDecode(response.body); 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 {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.failed_fetch ??
"Failed to fetch the route");
}
}
// Called when the map is created // Vérifie si 'routes' existe et contient au moins 1 élément
void _onStyleLoaded() async { if (data['routes'] != null && (data['routes'] as List).isNotEmpty) {
// Log the map controller and coordinates final geometry = data['routes'][0]['geometry']['coordinates'];
// Check if the mapController is really initialized setState(() {
if (mapController != null) { routeCoordinates = (geometry as List)
try { .map<List<double>>((coord) => [coord[0], coord[1]])
// Ensure the coordinates are valid .toList();
if (latitude != 0.0 && longitude != 0.0) { });
// Load marker image as Uint8List } else {
final userMarkerImage = await _loadMarkerImage('images/marker.png'); debugPrint("⚠️ Aucune route trouvée entre ${origin} et $destination.");
// Optionnel : afficher un snackbar/toast à lutilisateur
// Register the image with Mapbox
await mapController.addImage('event-marker', userMarkerImage);
final symbolOptions = SymbolOptions(
geometry: LatLng(latitude, longitude),
iconImage: "event-marker", // Use the registered custom marker
iconSize: 0.4, // Optional: Adjust size
);
// Debugging symbol options
// Add symbol to map
mapController!.addSymbol(symbolOptions);
} else {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.invalid_coordinates_symbol ??
"Error: Invalid coordinates, cannot add symbol.");
}
} catch (e) {
// Handle any exception that occurs when adding the symbol
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.error_symbol ??
"Error when adding symbol.");
} }
} else { } else {
showAlertDialog( debugPrint("❌ Erreur API Mapbox: ${response.statusCode}");
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.error_symbol ??
"Error when adding symbol.");
} }
} }
Future<void> _drawRouteAndMarkers() async { Future<void> _zoomToFitRoute(List<List<double>> coordinates) async {
// Remove previous route line if it exists if (mapboxMap == null || coordinates.isEmpty) return;
if (currentRouteLine != null) {
await mapController.removeLine(currentRouteLine!); double minLat = coordinates.first[1];
currentRouteLine = null; double maxLat = coordinates.first[1];
} double minLng = coordinates.first[0];
if (!isUserPositionInitialized) { double maxLng = coordinates.first[0];
showAlertDialog(
context, for (var coord in coordinates) {
AppLocalizations.of(context)?.error ?? "Error", if (coord[1] < minLat) minLat = coord[1];
AppLocalizations.of(context)?.position_not_init ?? if (coord[1] > maxLat) maxLat = coord[1];
"User position is not yet initialized. Try again."); if (coord[0] < minLng) minLng = coord[0];
return; if (coord[0] > maxLng) maxLng = coord[0];
} }
if (mapController != null && final bounds = mapbox.CoordinateBounds(
userPosition != null && southwest: mapbox.Point(coordinates: mapbox.Position(minLng, minLat)),
latitude != 0.0 && northeast: mapbox.Point(coordinates: mapbox.Position(maxLng, maxLat)),
longitude != 0.0) { infiniteBounds: true);
final destination = LatLng(latitude, longitude);
// Register the custom images // Calculer une CameraOptions automatiquement à partir des bounds
// Add event marker final cameraOptions = await mapboxMap!.cameraForCoordinateBounds(
bounds,
mapbox.MbxEdgeInsets(
top: 50, left: 50, right: 50, bottom: 50), // marges
0.0,
0.0,
null,
null);
final eventMarkerImage = await _loadMarkerImage('images/marker-red.png'); // Appliquer la caméra avec animation
await mapboxMap!.flyTo(
// Register the image with Mapbox cameraOptions,
await mapController.addImage('user-marker', eventMarkerImage); mapbox.MapAnimationOptions(duration: 1000),
await mapController.addSymbol(SymbolOptions(
geometry: userPosition,
iconImage: 'user-marker', // Custom icon for event
iconSize: 0.2,
));
// 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 {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.invalid_coordinates ??
"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<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 { Future<Uint8List> _loadMarkerImage(String assetPath) async {
final ByteData data = await rootBundle.load(assetPath); final ByteData data = await rootBundle.load(assetPath);
return data.buffer.asUint8List(); return data.buffer.asUint8List();
@@ -374,78 +262,68 @@ class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: Text(widget.place)),
title: Text(widget.place),
actions: [
DropdownButton<String>(
value: selectedMode,
items: [
DropdownMenuItem(
value: 'walking',
child: Row(
children: [
Icon(Icons.directions_walk, color: Colors.blue),
SizedBox(width: 8),
Text(AppLocalizations.of(context)?.walking ?? 'Walking'),
],
),
),
DropdownMenuItem(
value: 'cycling',
child: Row(
children: [
Icon(Icons.directions_bike, color: Colors.green),
SizedBox(width: 8),
Text(AppLocalizations.of(context)?.cycling ?? 'Cycling'),
],
),
),
DropdownMenuItem(
value: 'driving',
child: Row(
children: [
Icon(Icons.directions_car, color: Colors.red),
SizedBox(width: 8),
Text(AppLocalizations.of(context)?.driving ?? 'Driving'),
],
),
),
],
onChanged: (mode) {
setState(() {
selectedMode = mode!;
});
},
)
],
),
drawer: MyDrawer(), drawer: MyDrawer(),
body: Stack( body: isLoading
children: [ ? const Center(child: CircularProgressIndicator())
isLoading : Column(
? Center(child: CircularProgressIndicator()) children: [
: MapboxMap( Row(
accessToken: mapboxAccessToken, mainAxisAlignment: MainAxisAlignment.center,
onMapCreated: (controller) { children: [
mapController = controller; const Text("Mode : "),
}, DropdownButton<String>(
onStyleLoadedCallback: _onStyleLoaded, value: selectedMode,
initialCameraPosition: CameraPosition( items: const [
target: LatLng(latitude, longitude), DropdownMenuItem(
zoom: 14.0, 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,
),
), ),
), ),
Positioned( ],
bottom: 20,
right: 20,
child: FloatingActionButton(
onPressed: _drawRouteAndMarkers,
child: Icon(Icons.directions),
tooltip: AppLocalizations.of(context)?.get_direction ??
'Get Directions and Markers',
), ),
), floatingActionButton: FloatingActionButton(
], onPressed: _drawRouteAndMarkers,
child: const Icon(Icons.directions),
), ),
); );
} }

View File

@@ -37,7 +37,6 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
flutter_local_notifications: ^19.4.1
timezone: ^0.10.1 timezone: ^0.10.1
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
http: ^1.2.1 http: ^1.2.1
@@ -59,6 +58,7 @@ dependencies:
encrypt_shared_preferences: ^0.9.10 encrypt_shared_preferences: ^0.9.10
provider: ^6.1.2 # ou la dernière version provider: ^6.1.2 # ou la dernière version
mapbox_maps_flutter: ^2.10.0 mapbox_maps_flutter: ^2.10.0
flutter_local_notifications: ^19.4.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: