16 Commits

12 changed files with 242 additions and 346 deletions

View File

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

View File

@@ -1,8 +1,15 @@
<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:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-4855855675386260~3438207239"/>
<activity
android:name=".MainActivity"
android:exported="true"
@@ -11,12 +18,13 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:enableOnBackInvokedCallback="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
<meta-data
android:name="io.flutter.embedding.android.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 'package:covas_mobile/gen_l10n/app_localizations.dart';
import 'classes/notification_service.dart';
import 'classes/auth_service.dart';
import 'pages/ListItemMenu.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
await NotificationService.initialize();
final AuthService _authService = AuthService();
final loggedIn = await _authService.isLoggedIn();
runApp(
ChangeNotifierProvider(
create: (_) => LocaleProvider(),
child: MyApp(),
child: MyApp(isLoggedIn: loggedIn),
),
);
}
class MyApp extends StatelessWidget {
final bool isLoggedIn;
const MyApp({Key? key, required this.isLoggedIn}) : super(key: key);
@override
Widget build(BuildContext context) {
final localeProvider = Provider.of<LocaleProvider>(
@@ -30,7 +37,7 @@ class MyApp extends StatelessWidget {
locale: localeProvider.locale, // <-- utilise la locale courante
supportedLocales: L10n.all,
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);
gemini
.textAndImage(
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",
images: [file.readAsBytesSync()],
modelName: "models/gemini-1.5-pro-latest")
.then((value) => searchEvents(
value?.content?.parts?.last.text ?? '', widget.imagePath))
.catchError((e) => displayError);
gemini.prompt(parts: [
Part.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"),
Part.bytes(file.readAsBytesSync())
], model: "models/gemini-1.5-pro-latest").then((value) {
searchEvents(value?.output ?? '', widget.imagePath);
}).catchError((e) => displayError);
}
@override

View File

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

View File

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

View File

@@ -1,14 +1,15 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
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:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter/services.dart';
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: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 '../classes/MyDrawer.dart';
import '../classes/auth_service.dart';
@@ -16,10 +17,15 @@ 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'; //
import '../locale_provider.dart';
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());
}
@@ -30,9 +36,7 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Directions Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
theme: ThemeData(primarySwatch: Colors.blue),
home: const MapboxPages(title: 'Event Location', place: "Flutter"),
);
}
@@ -52,36 +56,26 @@ class MapboxPages extends StatefulWidget {
class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog {
final AuthService _authService = AuthService();
late MapboxMapController mapController;
late String mapboxAccessToken;
List<LatLng> routeCoordinates = [];
String selectedMode = 'driving';
mapbox.MapboxMap? mapboxMap;
mapbox.PointAnnotationManager? pointAnnotationManager;
mapbox.PolylineAnnotationManager? polylineAnnotationManager;
double longitude = 0.0;
double latitude = 0.0;
bool isLoading = true;
late LatLng userPosition;
mapbox.Point? userPosition;
bool isUserPositionInitialized = false;
Line? currentRouteLine;
String selectedMode = 'driving';
List<List<double>> routeCoordinates = [];
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
_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 {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
@@ -96,276 +90,170 @@ class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog {
latitude = events["latitude"];
longitude = events["longitude"];
setState(() {
isLoading = false;
});
setState(() => isLoading = false);
} else {
_handleErrorResponse(responseGet.statusCode);
}
} else {
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.invalid_cache ?? "Invalid cache.");
showAlertDialog(context, "Error", "Invalid cache.");
}
}
void _handleErrorResponse(int statusCode) {
final messages = {
400: AppLocalizations.of(context)?.request_error ??
"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);
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 Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.geo_disabled ??
"Location services are disabled.");
return;
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();
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.permission_denied ??
"Location permissions are denied.");
return;
}
}
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;
});
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();
} catch (e) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.failed_location ??
"Failed to get user location");
showAlertDialog(context, "Error", "Failed to get location");
}
}
Future<void> _fetchRoute(
LatLng origin, LatLng destination, String mode) async {
mapbox.Point origin, mapbox.Point 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',
'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);
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
void _onStyleLoaded() async {
// Log the map controller and coordinates
// 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'];
// Check if the mapController is really initialized
if (mapController != null) {
try {
// Ensure the coordinates are valid
if (latitude != 0.0 && longitude != 0.0) {
// Load marker image as Uint8List
final userMarkerImage = await _loadMarkerImage('images/marker.png');
// 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.");
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 à lutilisateur
}
} else {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.error_symbol ??
"Error when adding symbol.");
debugPrint("❌ Erreur API Mapbox: ${response.statusCode}");
}
}
Future<void> _drawRouteAndMarkers() async {
// Remove previous route line if it exists
if (currentRouteLine != null) {
await mapController.removeLine(currentRouteLine!);
currentRouteLine = null;
}
if (!isUserPositionInitialized) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.position_not_init ??
"User position is not yet initialized. Try again.");
return;
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];
}
if (mapController != null &&
userPosition != null &&
latitude != 0.0 &&
longitude != 0.0) {
final destination = LatLng(latitude, longitude);
final bounds = mapbox.CoordinateBounds(
southwest: mapbox.Point(coordinates: mapbox.Position(minLng, minLat)),
northeast: mapbox.Point(coordinates: mapbox.Position(maxLng, maxLat)),
infiniteBounds: true);
// Register the custom images
// Add event marker
// 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);
final eventMarkerImage = await _loadMarkerImage('images/marker-red.png');
// Register the image with Mapbox
await mapController.addImage('user-marker', eventMarkerImage);
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
),
// Appliquer la caméra avec animation
await mapboxMap!.flyTo(
cameraOptions,
mapbox.MapAnimationOptions(duration: 1000),
);
}
// 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 {
final ByteData data = await rootBundle.load(assetPath);
return data.buffer.asUint8List();
@@ -374,78 +262,68 @@ class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
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!;
});
},
)
],
),
appBar: AppBar(title: Text(widget.place)),
drawer: MyDrawer(),
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,
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,
),
),
),
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.
# Use with the CupertinoIcons class for iOS style icons.
flutter_local_notifications: ^19.4.1
timezone: ^0.10.1
cupertino_icons: ^1.0.2
http: ^1.2.1
@@ -59,6 +58,7 @@ dependencies:
encrypt_shared_preferences: ^0.9.10
provider: ^6.1.2 # ou la dernière version
mapbox_maps_flutter: ^2.10.0
flutter_local_notifications: ^19.4.1
dev_dependencies:
flutter_test: