location picker and ui fixes #17
| @@ -2,3 +2,4 @@ const String APP_NAME = 'AnyWay'; | ||||
|  | ||||
| String API_URL_BASE = 'https://anyway.kluster.moll.re'; | ||||
|  | ||||
| const String MAP_ID = '41c21ac9b81dbfd8'; | ||||
|   | ||||
| @@ -6,14 +6,17 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:anyway/constants.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:anyway/modules/trips_overview.dart'; | ||||
| import 'package:anyway/modules/trips_saved_list.dart'; | ||||
| import 'package:anyway/utils/load_trips.dart'; | ||||
|  | ||||
| import 'package:anyway/pages/new_trip.dart'; | ||||
| import 'package:anyway/pages/tutorial.dart'; | ||||
| import 'package:anyway/pages/overview.dart'; | ||||
| import 'package:anyway/pages/current_trip.dart'; | ||||
| import 'package:anyway/pages/profile.dart'; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // BasePage is the scaffold that holds all other pages | ||||
| // A side drawer is used to switch between pages | ||||
| class BasePage extends StatefulWidget { | ||||
| @@ -39,7 +42,25 @@ class _BasePageState extends State<BasePage> { | ||||
|      | ||||
|      | ||||
|     if (widget.mainScreen == "map") { | ||||
|       currentView = NavigationOverview(trip: widget.trip ?? getFirstTrip(trips)); | ||||
|       if (widget.trip != null) { | ||||
|         currentView = TripPage(trip: widget.trip!); | ||||
|       } else { | ||||
|         currentView = FutureBuilder( | ||||
|           future: trips, | ||||
|           builder: (context, snapshot) { | ||||
|             if (snapshot.hasData) { | ||||
|               List<Trip> availableTrips = snapshot.data!; | ||||
|               if (availableTrips.isNotEmpty) { | ||||
|                 return TripPage(trip: availableTrips[0]); | ||||
|               } else { | ||||
|                 return Text("Wow, so empty!"); | ||||
|               } | ||||
|             } else { | ||||
|               return const Text("loading..."); | ||||
|             } | ||||
|           }, | ||||
|         ); | ||||
|       } | ||||
|     } else if (widget.mainScreen == "tutorial") { | ||||
|       currentView = TutorialPage(); | ||||
|     } else if (widget.mainScreen == "profile") { | ||||
| @@ -131,72 +152,3 @@ class _BasePageState extends State<BasePage> { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // This function is used to get the first trip from a list of trips | ||||
| // TODO: Implement this function | ||||
| Trip getFirstTrip(Future<List<Trip>> trips) { | ||||
|   Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>()); | ||||
|   t1.landmarks.add( | ||||
|     Landmark( | ||||
|       uuid: '0', | ||||
|       name: "Start", | ||||
|       location: [48.85, 2.32], | ||||
|       type: start, | ||||
|     ), | ||||
|   ); | ||||
|   t1.landmarks.add( | ||||
|     Landmark( | ||||
|       uuid: '1', | ||||
|       name: "Eiffel Tower", | ||||
|       location: [48.859, 2.295], | ||||
|       type: sightseeing, | ||||
|       imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg" | ||||
|     ), | ||||
|   ); | ||||
|   t1.landmarks.add( | ||||
|     Landmark( | ||||
|       uuid: "2", | ||||
|       name: "Notre Dame Cathedral", | ||||
|       location: [48.8530, 2.3498], | ||||
|       type: sightseeing, | ||||
|       imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Notre-Dame_de_Paris%2C_4_October_2017.jpg/440px-Notre-Dame_de_Paris%2C_4_October_2017.jpg" | ||||
|     ), | ||||
|   ); | ||||
|   t1.landmarks.add( | ||||
|     Landmark( | ||||
|       uuid: "3", | ||||
|       name: "Louvre palace", | ||||
|       location: [48.8606, 2.3376], | ||||
|       type: sightseeing, | ||||
|       imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Louvre_Museum_Wikimedia_Commons.jpg/540px-Louvre_Museum_Wikimedia_Commons.jpg" | ||||
|     ), | ||||
|   ); | ||||
|   t1.landmarks.add( | ||||
|     Landmark( | ||||
|       uuid: "4", | ||||
|       name: "Pont-des-arts", | ||||
|       location: [48.8585, 2.3376], | ||||
|       type: sightseeing, | ||||
|       imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg/560px-Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg" | ||||
|     ), | ||||
|   ); | ||||
|   t1.landmarks.add( | ||||
|     Landmark( | ||||
|       uuid: "5", | ||||
|       name: "Panthéon", | ||||
|       location: [48.847, 2.347], | ||||
|       type: sightseeing, | ||||
|       imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Pantheon_of_Paris_007.JPG/1280px-Pantheon_of_Paris_007.JPG" | ||||
|     ), | ||||
|   ); | ||||
|   t1.landmarks.add( | ||||
|     Landmark( | ||||
|       uuid: "6", | ||||
|       name: "Galeries Lafayette", | ||||
|       location: [48.87, 2.32], | ||||
|       type: shopping, | ||||
|       imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/de/GaleriesLafayetteNuit.jpg/220px-GaleriesLafayetteNuit.jpg" | ||||
|     ), | ||||
|   ); | ||||
|   return t1; | ||||
| } | ||||
| @@ -4,6 +4,8 @@ import 'package:anyway/layout.dart'; | ||||
|  | ||||
| void main() => runApp(const App()); | ||||
|  | ||||
| final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); | ||||
|  | ||||
| class App extends StatelessWidget { | ||||
|   const App({super.key}); | ||||
|  | ||||
| @@ -14,6 +16,7 @@ class App extends StatelessWidget { | ||||
|       title: APP_NAME, | ||||
|       home: BasePage(mainScreen: "map"), | ||||
|       theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]), | ||||
|       scaffoldMessengerKey: rootScaffoldMessengerKey | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										64
									
								
								frontend/lib/modules/current_trip_landmarks_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/lib/modules/current_trip_landmarks_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import 'dart:developer'; | ||||
| import 'package:anyway/modules/step_between_landmarks.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:anyway/modules/landmark_card.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:anyway/main.dart'; | ||||
|  | ||||
|  | ||||
|  | ||||
| List<Widget> landmarksList(Trip trip) { | ||||
|   log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
|    | ||||
|   List<Widget> children = []; | ||||
|  | ||||
|   log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
|  | ||||
|   if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == start ) { | ||||
|     children.add( | ||||
|       const Text("No landmarks in this trip"), | ||||
|     ); | ||||
|     return children; | ||||
|   } | ||||
|  | ||||
|   for (Landmark landmark in trip.landmarks) { | ||||
|     children.add( | ||||
|       Dismissible( | ||||
|         key: ValueKey<int>(landmark.hashCode), | ||||
|         child: LandmarkCard(landmark), | ||||
|         dismissThresholds: {DismissDirection.endToStart: 0.95, DismissDirection.startToEnd: 0.95}, | ||||
|         onDismissed: (direction) { | ||||
|           log('Removing ${landmark.name}'); | ||||
|           trip.removeLandmark(landmark); | ||||
|           // Then show a snackbar | ||||
|  | ||||
|           rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|             SnackBar(content: Text("We won't show ${landmark.name} again")) | ||||
|           ); | ||||
|         }, | ||||
|  | ||||
|         background: Container(color: Colors.red), | ||||
|         secondaryBackground: Container( | ||||
|           color: Colors.red, | ||||
|           child: Icon( | ||||
|             Icons.delete, | ||||
|             color: Colors.white, | ||||
|           ), | ||||
|           padding: EdgeInsets.all(15), | ||||
|           alignment: Alignment.centerRight, | ||||
|         ), | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     if (landmark.next != null) { | ||||
|       children.add( | ||||
|         StepBetweenLandmarks(current: landmark, next: landmark.next!) | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return children; | ||||
| } | ||||
|  | ||||
							
								
								
									
										85
									
								
								frontend/lib/modules/current_trip_map.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								frontend/lib/modules/current_trip_map.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| import 'dart:collection'; | ||||
|  | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/modules/themed_marker.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:google_maps_flutter/google_maps_flutter.dart'; | ||||
| import 'package:widget_to_marker/widget_to_marker.dart'; | ||||
|  | ||||
|  | ||||
| class MapWidget extends StatefulWidget { | ||||
|  | ||||
|   final Trip? trip; | ||||
|  | ||||
|   MapWidget({ | ||||
|     this.trip | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   State<MapWidget> createState() => _MapWidgetState(); | ||||
| } | ||||
|  | ||||
| class _MapWidgetState extends State<MapWidget> { | ||||
|   late GoogleMapController mapController; | ||||
|  | ||||
|   CameraPosition _cameraPosition = CameraPosition( | ||||
|     target: LatLng(48.8566, 2.3522), | ||||
|     zoom: 11.0, | ||||
|   ); | ||||
|   Set<Marker> mapMarkers = <Marker>{}; | ||||
|    | ||||
|  | ||||
|   void _onMapCreated(GoogleMapController controller) async { | ||||
|     mapController = controller; | ||||
|     List<double>? newLocation = widget.trip?.landmarks.firstOrNull?.location; | ||||
|     if (newLocation != null) { | ||||
|       CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1])); | ||||
|       controller.moveCamera(update); | ||||
|     } | ||||
|     setMapMarkers(); | ||||
|   } | ||||
|  | ||||
|   void _onCameraIdle() { | ||||
|     // print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0))); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   void setMapMarkers() async { | ||||
|     List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? []; | ||||
|     Set<Marker> newMarkers = <Marker>{}; | ||||
|     for (int i = 0; i < landmarks.length; i++) { | ||||
|       Landmark landmark = landmarks[i]; | ||||
|       List<double> location = landmark.location; | ||||
|       Marker marker = Marker( | ||||
|         markerId: MarkerId(landmark.uuid), | ||||
|         position: LatLng(location[0], location[1]), | ||||
|         icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor( | ||||
|           logicalSize: const Size(150, 150), | ||||
|           imageSize: const Size(150, 150) | ||||
|         ), | ||||
|       ); | ||||
|       newMarkers.add(marker); | ||||
|     } | ||||
|     setState(() { | ||||
|       mapMarkers = newMarkers; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     widget.trip?.addListener(setMapMarkers); | ||||
|     return GoogleMap( | ||||
|       onMapCreated: _onMapCreated, | ||||
|       initialCameraPosition: _cameraPosition, | ||||
|       onCameraIdle: _onCameraIdle, | ||||
|       // onLongPress: , | ||||
|       markers: mapMarkers, | ||||
|       cloudMapId: MAP_ID, | ||||
|       mapToolbarEnabled: false, | ||||
|       zoomControlsEnabled: false, | ||||
|  | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										33
									
								
								frontend/lib/modules/current_trip_save_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								frontend/lib/modules/current_trip_save_button.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:auto_size_text/auto_size_text.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
|  | ||||
| Widget saveButton(Trip trip) => ElevatedButton( | ||||
|   onPressed: () async { | ||||
|     SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||
|     trip.toPrefs(prefs); | ||||
|   }, | ||||
|   child: SizedBox( | ||||
|     width: 100, | ||||
|     child: Row( | ||||
|       mainAxisAlignment: MainAxisAlignment.center, | ||||
|       children: [ | ||||
|         Icon( | ||||
|           Icons.save, | ||||
|         ), | ||||
|         Expanded( | ||||
|           child: Padding( | ||||
|             padding: EdgeInsets.only(left: 10, top: 5, bottom: 5, right: 5), | ||||
|             child: AutoSizeText( | ||||
|               'Save trip', | ||||
|               maxLines: 2, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ), | ||||
|   ) | ||||
| ); | ||||
|  | ||||
| @@ -1,17 +1,19 @@ | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
|  | ||||
|  | ||||
| class LandmarkCard extends StatefulWidget { | ||||
|   final Landmark landmark; | ||||
|   @override | ||||
|   _LandmarkCardState createState() => _LandmarkCardState(); | ||||
|    | ||||
|   LandmarkCard(this.landmark); | ||||
|    | ||||
|   @override | ||||
|   _LandmarkCardState createState() => _LandmarkCardState(); | ||||
| } | ||||
|  | ||||
|  | ||||
| class _LandmarkCardState extends State<LandmarkCard> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|   | ||||
| @@ -1,168 +0,0 @@ | ||||
| import 'dart:developer'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
|  | ||||
| import 'package:anyway/modules/landmark_card.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| class LandmarksOverview extends StatefulWidget { | ||||
|   final Trip? trip; | ||||
|   const LandmarksOverview({super.key, this.trip}); | ||||
|  | ||||
|   @override | ||||
|   State<LandmarksOverview> createState() => _LandmarksOverviewState(); | ||||
| } | ||||
|  | ||||
| class _LandmarksOverviewState extends State<LandmarksOverview> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return ListenableBuilder( | ||||
|       listenable: widget.trip!, | ||||
|       builder: (BuildContext context, Widget? child) { | ||||
|         Trip trip = widget.trip!; | ||||
|         log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
|          | ||||
|         List<Widget> children; | ||||
|          | ||||
|         if (trip.uuid != 'pending' && trip.uuid != 'error') { | ||||
|           log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
|           if (trip.landmarks.length <= 1) { | ||||
|             children = [ | ||||
|               const Text("No landmarks in this trip"), | ||||
|             ]; | ||||
|           } else { | ||||
|             children = [ | ||||
|               landmarksWithSteps(), | ||||
|               saveButton(), | ||||
|             ]; | ||||
|           } | ||||
|         } else if(trip.uuid == 'pending') { | ||||
|           // the trip is still being fetched from the api | ||||
|           children = [Center(child: CircularProgressIndicator())]; | ||||
|         } else { | ||||
|             // trip.uuid == 'error' | ||||
|             // show the error raised by the api | ||||
|             // String error =  | ||||
|             children = [ | ||||
|               const Icon( | ||||
|                 Icons.error_outline, | ||||
|                 color: Colors.red, | ||||
|                 size: 60, | ||||
|               ), | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(top: 16), | ||||
|                 child: Text('Error: ${trip.errorDescription}'), | ||||
|               ), | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         return Column( | ||||
|           children: children, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
|   Widget saveButton() => ElevatedButton( | ||||
|     onPressed: () async { | ||||
|       Trip? trip = await widget.trip; | ||||
|       SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||
|       trip?.toPrefs(prefs); | ||||
|     }, | ||||
|     child: const Text('Save'), | ||||
|   ); | ||||
|  | ||||
|   Widget landmarksWithSteps() { | ||||
|     return ListenableBuilder( | ||||
|       listenable: widget.trip!, | ||||
|       builder: (BuildContext context, Widget? child) { | ||||
|         List<Widget> children = []; | ||||
|         for (Landmark landmark in widget.trip!.landmarks) { | ||||
|           children.add( | ||||
|             Dismissible( | ||||
|               key: ValueKey<int>(landmark.hashCode), | ||||
|               child: LandmarkCard(landmark), | ||||
|               dismissThresholds: {DismissDirection.endToStart: 0.6}, | ||||
|               onDismissed: (direction) { | ||||
|                 // Remove the item from the data source. | ||||
|                   log(landmark.name); | ||||
|                 setState(() { | ||||
|                   widget.trip!.removeLandmark(landmark); | ||||
|                 }); | ||||
|                 // Then show a snackbar. | ||||
|                 ScaffoldMessenger.of(context) | ||||
|                     .showSnackBar(SnackBar(content: Text("We won't show ${landmark.name} again"))); | ||||
|               }, | ||||
|               background: Container(color: Colors.red), | ||||
|               secondaryBackground: Container( | ||||
|                 color: Colors.red, | ||||
|                 child: Icon( | ||||
|                   Icons.delete, | ||||
|                   color: Colors.white, | ||||
|                 ), | ||||
|                 padding: EdgeInsets.all(15), | ||||
|                 alignment: Alignment.centerRight, | ||||
|               ), | ||||
|             ) | ||||
|           ); | ||||
|           if (landmark.next != null) { | ||||
|             Widget step = stepBetweenLandmarks(landmark, landmark.next!); | ||||
|             children.add(step); | ||||
|           } | ||||
|         } | ||||
|         return Column( | ||||
|           children: children   | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| Widget stepBetweenLandmarks(Landmark current, Landmark next) { | ||||
|   int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5; | ||||
|   // ~/ is integer division (rounding) | ||||
|   return Container( | ||||
|     margin: EdgeInsets.all(10), | ||||
|     padding: EdgeInsets.all(10), | ||||
|     decoration: BoxDecoration( | ||||
|       border: Border( | ||||
|         left: BorderSide(width: 3.0, color: Colors.black), | ||||
|       ), | ||||
|       // gradient: LinearGradient( | ||||
|       //   begin: Alignment.topLeft, | ||||
|       //   end: Alignment.bottomRight, | ||||
|       //   colors: [Colors.grey, Colors.white, Colors.white], | ||||
|       // ), | ||||
|     ), | ||||
|     child: Row(  | ||||
|       children: [ | ||||
|         Column( | ||||
|           children: [ | ||||
|             Icon(Icons.directions_walk), | ||||
|             Text("~$timeRounded min", style: TextStyle(fontSize: 10)), | ||||
|           ], | ||||
|         ), | ||||
|         Spacer(), | ||||
|         ElevatedButton( | ||||
|           onPressed: () { | ||||
|             // Open navigation instructions | ||||
|           }, | ||||
|           child: Row( | ||||
|             children: [ | ||||
|               Icon(Icons.directions), | ||||
|               Text("Directions"), | ||||
|             ], | ||||
|           ), | ||||
|         ) | ||||
|       ], | ||||
|     ), | ||||
|   ); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1,151 +0,0 @@ | ||||
| import 'dart:collection'; | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:google_maps_flutter/google_maps_flutter.dart'; | ||||
| import 'package:widget_to_marker/widget_to_marker.dart'; | ||||
|  | ||||
|  | ||||
| class MapWidget extends StatefulWidget { | ||||
|  | ||||
|   final Trip? trip; | ||||
|  | ||||
|   MapWidget({ | ||||
|     this.trip | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   State<MapWidget> createState() => _MapWidgetState(); | ||||
| } | ||||
|  | ||||
| class _MapWidgetState extends State<MapWidget> { | ||||
|   late GoogleMapController mapController; | ||||
|  | ||||
|   CameraPosition _cameraPosition = CameraPosition( | ||||
|     target: LatLng(48.8566, 2.3522), | ||||
|     zoom: 11.0, | ||||
|   ); | ||||
|   Set<Marker> mapMarkers = <Marker>{}; | ||||
|    | ||||
|  | ||||
|   void _onMapCreated(GoogleMapController controller) async { | ||||
|     mapController = controller; | ||||
|     List<double>? newLocation = widget.trip?.landmarks.firstOrNull?.location; | ||||
|     if (newLocation != null) { | ||||
|       CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1])); | ||||
|       controller.moveCamera(update); | ||||
|     } | ||||
|     setMapMarkers(); | ||||
|   } | ||||
|  | ||||
|   void _onCameraIdle() { | ||||
|     // print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0))); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   void setMapMarkers() async { | ||||
|     List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? []; | ||||
|     Set<Marker> newMarkers = <Marker>{}; | ||||
|     for (int i = 0; i < landmarks.length; i++) { | ||||
|       Landmark landmark = landmarks[i]; | ||||
|       List<double> location = landmark.location; | ||||
|       Marker marker = Marker( | ||||
|         markerId: MarkerId(landmark.uuid), | ||||
|         position: LatLng(location[0], location[1]), | ||||
|         icon: await CustomMarker(landmark: landmark, position: i).toBitmapDescriptor( | ||||
|           logicalSize: const Size(150, 150), | ||||
|           imageSize: const Size(150, 150) | ||||
|         ), | ||||
|       ); | ||||
|       newMarkers.add(marker); | ||||
|     } | ||||
|     setState(() { | ||||
|       mapMarkers = newMarkers; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     widget.trip?.addListener(setMapMarkers); | ||||
|     return GoogleMap( | ||||
|       onMapCreated: _onMapCreated, | ||||
|       initialCameraPosition: _cameraPosition, | ||||
|       onCameraIdle: _onCameraIdle, | ||||
|       // onLongPress: , | ||||
|       markers: mapMarkers, | ||||
|       cloudMapId: '41c21ac9b81dbfd8', | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| class CustomMarker extends StatelessWidget { | ||||
|   final Landmark landmark; | ||||
|   final int position; | ||||
|  | ||||
|   CustomMarker({ | ||||
|     super.key, | ||||
|     required this.landmark, | ||||
|     required this.position | ||||
|     }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     // This returns an outlined circle, with an icon corresponding to the landmark type | ||||
|     // As a small dot, the number of the landmark is displayed in the top right | ||||
|     Icon icon; | ||||
|     if (landmark.type == sightseeing) { | ||||
|       icon = Icon(Icons.church, color: Colors.black, size: 50); | ||||
|     } else if (landmark.type == nature) { | ||||
|       icon = Icon(Icons.park, color: Colors.black, size: 50); | ||||
|     } else if (landmark.type == shopping) { | ||||
|       icon = Icon(Icons.shopping_cart, color: Colors.black, size: 50); | ||||
|     } else if (landmark.type == start || landmark.type == finish) { | ||||
|       icon = Icon(Icons.flag, color: Colors.black, size: 50); | ||||
|     } else { | ||||
|       icon = Icon(Icons.location_on, color: Colors.black, size: 50); | ||||
|     } | ||||
|  | ||||
|     Widget? positionIndicator; | ||||
|     if (landmark.type != start && landmark.type != finish) { | ||||
|       positionIndicator = Positioned( | ||||
|         top: 0, | ||||
|         right: 0, | ||||
|         child: Container( | ||||
|           padding: EdgeInsets.all(5), | ||||
|           decoration: BoxDecoration( | ||||
|             color: Theme.of(context).primaryColor, | ||||
|             shape: BoxShape.circle, | ||||
|           ), | ||||
|           child: Text('$position', style: TextStyle(color: Colors.white, fontSize: 20)), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return RepaintBoundary( | ||||
|       child: Stack( | ||||
|         children: [ | ||||
|           Container( | ||||
|             // these are not the final sizes, since the final size is set in the toBitmapDescriptor method | ||||
|             // they are useful nevertheless to ensure the scale of the components are correct | ||||
|             width: 75, | ||||
|             height: 75, | ||||
|             decoration: BoxDecoration( | ||||
|               gradient: LinearGradient( | ||||
|                 begin: Alignment.topLeft, | ||||
|                 end: Alignment.bottomRight, | ||||
|                 colors: [Colors.red, Colors.yellow] | ||||
|               ), | ||||
|               shape: BoxShape.circle, | ||||
|               border: Border.all(color: Colors.black, width: 5), | ||||
|             ), | ||||
|             child: icon, | ||||
|           ), | ||||
|           positionIndicator ?? Container(), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										52
									
								
								frontend/lib/modules/map_chooser.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/lib/modules/map_chooser.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:map_launcher/map_launcher.dart'; | ||||
| import 'package:flutter_svg/flutter_svg.dart'; | ||||
|  | ||||
| showMapChooser(BuildContext context, Landmark current, Landmark next) async { | ||||
|   List availableMaps = []; | ||||
|   try { | ||||
|     availableMaps = await MapLauncher.installedMaps; | ||||
|   } catch (e) { | ||||
|     print(e); | ||||
|   } | ||||
|   if (availableMaps.isEmpty) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   showModalBottomSheet( | ||||
|     context: context, | ||||
|     builder: (BuildContext context) { | ||||
|       return SafeArea( | ||||
|         child: SingleChildScrollView( | ||||
|           child: Container( | ||||
|             child: Wrap( | ||||
|               children: <Widget>[ | ||||
|                 for (var map in availableMaps) | ||||
|                   ListTile( | ||||
|                     onTap: () => map.showDirections( | ||||
|                       origin: Coords(current.location[0], current.location[1]), | ||||
|                       originTitle: current.name, | ||||
|                       destination: Coords(next.location[0], next.location[1]), | ||||
|                       destinationTitle: current.name, | ||||
|                       directionsMode: DirectionsMode.walking | ||||
|                     ), | ||||
|                     title: Text(map.mapName), | ||||
|                     // rounded corners | ||||
|                     leading: ClipRRect( | ||||
|                       borderRadius: BorderRadius.circular(8.0), | ||||
|                       child: SvgPicture.asset( | ||||
|                         map.icon, | ||||
|                         height: 30.0, | ||||
|                         width: 30.0, | ||||
|                       ), | ||||
|                     ) | ||||
|                   ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										64
									
								
								frontend/lib/modules/new_trip_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/lib/modules/new_trip_button.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import 'package:anyway/layout.dart'; | ||||
| import 'package:anyway/structs/preferences.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:anyway/utils/fetch_trip.dart'; | ||||
| import 'package:auto_size_text/auto_size_text.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
|  | ||||
| class NewTripButton extends StatefulWidget { | ||||
|   final Trip trip; | ||||
|  | ||||
|   const NewTripButton({required this.trip}); | ||||
|  | ||||
|   @override | ||||
|   State<NewTripButton> createState() => _NewTripButtonState(); | ||||
| } | ||||
|  | ||||
| class _NewTripButtonState extends State<NewTripButton> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return ListenableBuilder( | ||||
|       listenable: widget.trip, | ||||
|       builder: (BuildContext context, Widget? child) { | ||||
|         if (widget.trip.landmarks.isEmpty){ | ||||
|           return Container(); | ||||
|         } | ||||
|         return FloatingActionButton.extended( | ||||
|           onPressed: () async { | ||||
|             Future<UserPreferences> preferences = loadUserPreferences(); | ||||
|             Trip trip = widget.trip; | ||||
|             fetchTrip(trip, preferences); | ||||
|             Navigator.of(context).push( | ||||
|               MaterialPageRoute( | ||||
|                 builder: (context) => BasePage(mainScreen: "map", trip: trip) | ||||
|               ) | ||||
|             ); | ||||
|           }, | ||||
|           icon: Icon(Icons.add), | ||||
|           label: FutureBuilder( | ||||
|             future: widget.trip.cityName, | ||||
|             builder: (context, snapshot) { | ||||
|               if (snapshot.connectionState == ConnectionState.done) { | ||||
|                 return AutoSizeText( | ||||
|                   'New trip to ${snapshot.data.toString()}', | ||||
|                   style: TextStyle(fontSize: 18), | ||||
|                   maxLines: 2, | ||||
|                 ); | ||||
|               } else { | ||||
|                 return AutoSizeText( | ||||
|                   'New trip to ...', | ||||
|                   style: TextStyle(fontSize: 18), | ||||
|                   maxLines: 2, | ||||
|                 ); | ||||
|               } | ||||
|             }, | ||||
|           ) | ||||
|         ); | ||||
|          | ||||
|       }  | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										64
									
								
								frontend/lib/modules/new_trip_location_search.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/lib/modules/new_trip_location_search.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
|  | ||||
| // A search bar that allow the user to enter a city name | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:geocoding/geocoding.dart'; | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class NewTripLocationSearch extends StatefulWidget { | ||||
|   Trip trip; | ||||
|   NewTripLocationSearch( | ||||
|     this.trip, | ||||
|   ); | ||||
|  | ||||
|  | ||||
|   @override | ||||
|   State<NewTripLocationSearch> createState() => _NewTripLocationSearchState(); | ||||
| } | ||||
|  | ||||
| class _NewTripLocationSearchState extends State<NewTripLocationSearch> { | ||||
|   final TextEditingController _controller = TextEditingController(); | ||||
|  | ||||
|   setTripLocation (String query) async { | ||||
|     List<Location> locations = []; | ||||
|     log('Searching for: $query'); | ||||
|      | ||||
|     try{ | ||||
|       locations = await locationFromAddress(query); | ||||
|     } catch (e) { | ||||
|       log('No results found for: $query : $e'); | ||||
|     } | ||||
|  | ||||
|     if (locations.isNotEmpty) { | ||||
|       Location location = locations.first; | ||||
|       widget.trip.landmarks.clear(); | ||||
|       widget.trip.addLandmark( | ||||
|         Landmark( | ||||
|           uuid: 'pending', | ||||
|           name: query, | ||||
|           location: [location.latitude, location.longitude], | ||||
|           type: start | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SearchBar( | ||||
|       hintText: 'Enter a city name or long press on the map.', | ||||
|       onSubmitted: setTripLocation, | ||||
|       controller: _controller, | ||||
|       leading: Icon(Icons.search), | ||||
|       trailing: [ElevatedButton( | ||||
|         onPressed: () { | ||||
|           setTripLocation(_controller.text); | ||||
|         }, | ||||
|         child: Text('Search'), | ||||
|       ),] | ||||
|  | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										87
									
								
								frontend/lib/modules/new_trip_map.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								frontend/lib/modules/new_trip_map.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
|  | ||||
| // A map that allows the user to select a location for a new trip. | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/modules/themed_marker.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:google_maps_flutter/google_maps_flutter.dart'; | ||||
| import 'package:widget_to_marker/widget_to_marker.dart'; | ||||
|  | ||||
|  | ||||
| class NewTripMap extends StatefulWidget { | ||||
|   Trip trip; | ||||
|   NewTripMap( | ||||
|     this.trip, | ||||
|   ); | ||||
|  | ||||
|   @override | ||||
|   State<NewTripMap> createState() => _NewTripMapState(); | ||||
| } | ||||
|  | ||||
| class _NewTripMapState extends State<NewTripMap> { | ||||
|   final CameraPosition _cameraPosition = CameraPosition( | ||||
|     target: LatLng(48.8566, 2.3522), | ||||
|     zoom: 11.0, | ||||
|   ); | ||||
|   late GoogleMapController _mapController; | ||||
|   final Set<Marker> _markers = <Marker>{}; | ||||
|  | ||||
|   _onLongPress(LatLng location) { | ||||
|     log('Long press: $location'); | ||||
|     widget.trip.landmarks.clear(); | ||||
|     widget.trip.addLandmark( | ||||
|       Landmark( | ||||
|         uuid: 'pending', | ||||
|         name: 'start', | ||||
|         location: [location.latitude, location.longitude], | ||||
|         type: start | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   updateTripDetails() async { | ||||
|     _markers.clear(); | ||||
|     if (widget.trip.landmarks.isNotEmpty) { | ||||
|       Landmark landmark = widget.trip.landmarks.first; | ||||
|       _markers.add( | ||||
|         Marker( | ||||
|           markerId: MarkerId(landmark.uuid), | ||||
|           position: LatLng(landmark.location[0], landmark.location[1]), | ||||
|           icon: await ThemedMarker(landmark: landmark, position: 0).toBitmapDescriptor( | ||||
|             logicalSize: const Size(150, 150), | ||||
|             imageSize: const Size(150, 150) | ||||
|           ), | ||||
|         ) | ||||
|       ); | ||||
|       _mapController.moveCamera( | ||||
|         CameraUpdate.newLatLng( | ||||
|           LatLng(landmark.location[0], landmark.location[1]) | ||||
|         ) | ||||
|       ); | ||||
|       setState(() {}); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _onMapCreated(GoogleMapController controller) async { | ||||
|     _mapController = controller; | ||||
|   } | ||||
|  | ||||
|  | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     widget.trip.addListener(updateTripDetails); | ||||
|     return GoogleMap( | ||||
|       onMapCreated: _onMapCreated, | ||||
|       initialCameraPosition: _cameraPosition, | ||||
|       onLongPress: _onLongPress, | ||||
|       markers: _markers, | ||||
|       cloudMapId: MAP_ID, | ||||
|       mapToolbarEnabled: false, | ||||
|       zoomControlsEnabled: false, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										56
									
								
								frontend/lib/modules/step_between_landmarks.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								frontend/lib/modules/step_between_landmarks.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:anyway/modules/map_chooser.dart'; | ||||
|  | ||||
| class StepBetweenLandmarks extends StatefulWidget { | ||||
|   final Landmark current; | ||||
|   final Landmark next; | ||||
|  | ||||
|   const StepBetweenLandmarks({ | ||||
|     super.key, | ||||
|     required this.current, | ||||
|     required this.next | ||||
|     }); | ||||
|  | ||||
|   @override | ||||
|   State<StepBetweenLandmarks> createState() => _StepBetweenLandmarksState(); | ||||
| } | ||||
|  | ||||
| class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     int timeRounded = 5 * ((widget.current.tripTime?.inMinutes ?? 0) ~/ 5); | ||||
|     // ~/ is integer division (rounding) | ||||
|     return Container( | ||||
|       margin: EdgeInsets.all(10), | ||||
|       padding: EdgeInsets.all(10), | ||||
|       decoration: BoxDecoration( | ||||
|         border: Border( | ||||
|           left: BorderSide(width: 3.0, color: Colors.black), | ||||
|         ), | ||||
|       ), | ||||
|       child: Row(  | ||||
|         children: [ | ||||
|           Column( | ||||
|             children: [ | ||||
|               Icon(Icons.directions_walk), | ||||
|               Text("~$timeRounded min", style: TextStyle(fontSize: 10)), | ||||
|             ], | ||||
|           ), | ||||
|           Spacer(), | ||||
|           ElevatedButton( | ||||
|             onPressed: () async { | ||||
|               showMapChooser(context, widget.current, widget.next); | ||||
|             }, | ||||
|             child: Row( | ||||
|               children: [ | ||||
|                 Icon(Icons.directions), | ||||
|                 Text("Directions"), | ||||
|               ], | ||||
|             ), | ||||
|           ) | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										68
									
								
								frontend/lib/modules/themed_marker.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								frontend/lib/modules/themed_marker.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
|  | ||||
| class ThemedMarker extends StatelessWidget { | ||||
|   final Landmark landmark; | ||||
|   final int position; | ||||
|  | ||||
|   ThemedMarker({ | ||||
|     super.key, | ||||
|     required this.landmark, | ||||
|     required this.position | ||||
|     }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     // This returns an outlined circle, with an icon corresponding to the landmark type | ||||
|     // As a small dot, the number of the landmark is displayed in the top right | ||||
|     Icon icon; | ||||
|     if (landmark.type == sightseeing) { | ||||
|       icon = Icon(Icons.church, color: Colors.black, size: 50); | ||||
|     } else if (landmark.type == nature) { | ||||
|       icon = Icon(Icons.park, color: Colors.black, size: 50); | ||||
|     } else if (landmark.type == shopping) { | ||||
|       icon = Icon(Icons.shopping_cart, color: Colors.black, size: 50); | ||||
|     } else if (landmark.type == start || landmark.type == finish) { | ||||
|       icon = Icon(Icons.flag, color: Colors.black, size: 50); | ||||
|     } else { | ||||
|       icon = Icon(Icons.location_on, color: Colors.black, size: 50); | ||||
|     } | ||||
|  | ||||
|     Widget? positionIndicator; | ||||
|     if (landmark.type != start && landmark.type != finish) { | ||||
|       positionIndicator = Positioned( | ||||
|         top: 0, | ||||
|         right: 0, | ||||
|         child: Container( | ||||
|           padding: EdgeInsets.all(5), | ||||
|           decoration: BoxDecoration( | ||||
|             color: Colors.grey[100], | ||||
|             shape: BoxShape.circle, | ||||
|           ), | ||||
|           child: Text('$position', style: TextStyle(color: Colors.black, fontSize: 25)), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return RepaintBoundary( | ||||
|       child: Stack( | ||||
|         alignment: Alignment.topRight, | ||||
|         children: [ | ||||
|           Container( | ||||
|             decoration: BoxDecoration( | ||||
|               gradient: LinearGradient( | ||||
|                 colors: [Colors.red, Colors.yellow] | ||||
|               ), | ||||
|               shape: BoxShape.circle, | ||||
|               border: Border.all(color: Colors.black, width: 5), | ||||
|             ), | ||||
|             padding: EdgeInsets.all(5), | ||||
|             child: icon | ||||
|           ), | ||||
|           if (positionIndicator != null) positionIndicator, | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -16,8 +16,6 @@ class TripsOverview extends StatefulWidget { | ||||
| } | ||||
| 
 | ||||
| class _TripsOverviewState extends State<TripsOverview> { | ||||
|   // final Future<List<Trip>> _trips = loadTrips(); | ||||
| 
 | ||||
| 
 | ||||
|   Widget listBuild (BuildContext context, AsyncSnapshot<List<Trip>> snapshot) { | ||||
|     List<Widget> children; | ||||
| @@ -65,6 +63,7 @@ class _TripsOverviewState extends State<TripsOverview> { | ||||
| 
 | ||||
|     return ListView( | ||||
|       children: children, | ||||
|       padding: const EdgeInsets.only(top: 0), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
							
								
								
									
										84
									
								
								frontend/lib/pages/current_trip.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								frontend/lib/pages/current_trip.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| import 'package:anyway/modules/current_trip_save_button.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:sliding_up_panel/sliding_up_panel.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:anyway/modules/current_trip_landmarks_list.dart'; | ||||
| import 'package:anyway/modules/current_trip_greeter.dart'; | ||||
| import 'package:anyway/modules/current_trip_map.dart'; | ||||
|  | ||||
|  | ||||
|  | ||||
| class TripPage extends StatefulWidget { | ||||
|   final Trip trip; | ||||
|  | ||||
|   TripPage({ | ||||
|     required this.trip, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   State<TripPage> createState() => _TripPageState(); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| class _TripPageState extends State<TripPage> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SlidingUpPanel( | ||||
|         panelBuilder: (sc) => _panelFull(sc), | ||||
|         // collapsed: _floatingCollapsed(), | ||||
|         body: MapWidget(trip: widget.trip), | ||||
|         // renderPanelSheet: false, | ||||
|         // backdropEnabled: true, | ||||
|         maxHeight: MediaQuery.of(context).size.height * 0.8, | ||||
|         padding: EdgeInsets.only(left: 10, right: 10, top: 25, bottom: 10), | ||||
|         // panelSnapping: false, | ||||
|         borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), | ||||
|         boxShadow: [ | ||||
|           BoxShadow( | ||||
|             blurRadius: 20.0, | ||||
|             color: Colors.black, | ||||
|           ) | ||||
|         ], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|    | ||||
|   Widget _panelFull(ScrollController sc) { | ||||
|     return ListenableBuilder( | ||||
|       listenable: widget.trip, | ||||
|       builder: (context, child) { | ||||
|         if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') { | ||||
|           return ListView( | ||||
|             controller: sc, | ||||
|             padding: EdgeInsets.only(bottom: 35), | ||||
|             children: [ | ||||
|               Greeter(trip: widget.trip), | ||||
|               ...landmarksList(widget.trip), | ||||
|               Padding(padding: EdgeInsets.only(top: 10)), | ||||
|               Center(child: saveButton(widget.trip)), | ||||
|             ], | ||||
|           ); | ||||
|         } else if(widget.trip.uuid == 'pending') { | ||||
|           return Greeter(trip: widget.trip); | ||||
|         } else { | ||||
|           return Column( | ||||
|             children: [ | ||||
|               const Icon( | ||||
|                 Icons.error_outline, | ||||
|                 color: Colors.red, | ||||
|                 size: 60, | ||||
|               ), | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(top: 16), | ||||
|                 child: Text('Error: ${widget.trip.errorDescription}'), | ||||
|               ), | ||||
|             ], | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:anyway/modules/new_trip_button.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:geocoding/geocoding.dart'; | ||||
| @@ -6,7 +7,8 @@ import 'package:anyway/layout.dart'; | ||||
| import 'package:anyway/utils/fetch_trip.dart'; | ||||
| import 'package:anyway/structs/preferences.dart'; | ||||
| import "package:anyway/structs/trip.dart"; | ||||
|  | ||||
| import 'package:anyway/modules/new_trip_location_search.dart'; | ||||
| import 'package:anyway/modules/new_trip_map.dart'; | ||||
|  | ||||
|  | ||||
| class NewTripPage extends StatefulWidget { | ||||
| @@ -20,74 +22,25 @@ class _NewTripPageState extends State<NewTripPage> { | ||||
|   final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); | ||||
|   final TextEditingController latController = TextEditingController(); | ||||
|   final TextEditingController lonController = TextEditingController(); | ||||
|   Trip trip = Trip(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     // floating search bar and map as a background | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         title: const Text('New Trip'), | ||||
|       ), | ||||
|       body: Form( | ||||
|         key: _formKey, | ||||
|         child: Padding( | ||||
|           padding: const EdgeInsets.all(15.0), | ||||
|           child: Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|              | ||||
|             children: <Widget>[ | ||||
|               TextFormField( | ||||
|                 decoration: const InputDecoration(hintText: 'Lat'), | ||||
|                 controller: latController, | ||||
|                 validator: (String? value) { | ||||
|                   if (value == null || value.isEmpty || double.tryParse(value) == null){ | ||||
|                     return 'Please enter a floating point number'; | ||||
|                   } | ||||
|                   return null; | ||||
|                 }, | ||||
|               ), | ||||
|               TextFormField( | ||||
|                 decoration: const InputDecoration(hintText: 'Lon'), | ||||
|                 controller: lonController, | ||||
|  | ||||
|                 validator: (String? value) { | ||||
|                   if (value == null || value.isEmpty || double.tryParse(value) == null){ | ||||
|                     return 'Please enter a floating point number'; | ||||
|                   } | ||||
|                   return null; | ||||
|                 }, | ||||
|               ), | ||||
|               Divider(height: 15, color: Colors.transparent), | ||||
|               ElevatedButton( | ||||
|                 child: const Text('Create trip'), | ||||
|                 onPressed: () { | ||||
|                   if (_formKey.currentState!.validate()) { | ||||
|                     List<double> startPoint = [ | ||||
|                       double.parse(latController.text), | ||||
|                       double.parse(lonController.text) | ||||
|                     ]; | ||||
|                     Future<UserPreferences> preferences = loadUserPreferences(); | ||||
|                     Trip trip = Trip(); | ||||
|                     trip.landmarks.add( | ||||
|                       Landmark( | ||||
|                         location: startPoint, | ||||
|                         name: "Start", | ||||
|                         type: start, | ||||
|                         uuid: "pending" | ||||
|                       ) | ||||
|                     ); | ||||
|                     fetchTrip(trip, preferences); | ||||
|                     Navigator.of(context).push( | ||||
|                       MaterialPageRoute( | ||||
|                         builder: (context) => BasePage(mainScreen: "map", trip: trip) | ||||
|                       ) | ||||
|                     ); | ||||
|                   } | ||||
|                 }, | ||||
|       body: Stack( | ||||
|         children: [ | ||||
|           NewTripMap(trip), | ||||
|           Padding( | ||||
|             padding: EdgeInsets.all(15), | ||||
|             child: NewTripLocationSearch(trip), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|         ) | ||||
|       ) | ||||
|       floatingActionButton: NewTripButton(trip: trip), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,82 +0,0 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:sliding_up_panel/sliding_up_panel.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
|  | ||||
| import 'package:anyway/modules/landmarks_overview.dart'; | ||||
| import 'package:anyway/modules/map.dart'; | ||||
| import 'package:anyway/modules/greeter.dart'; | ||||
|  | ||||
|  | ||||
|  | ||||
| class NavigationOverview extends StatefulWidget { | ||||
|   final Trip trip; | ||||
|  | ||||
|   NavigationOverview({ | ||||
|     required this.trip, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   State<NavigationOverview> createState() => _NavigationOverviewState(); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| class _NavigationOverviewState extends State<NavigationOverview> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SlidingUpPanel( | ||||
|         panel: _floatingPanel(), | ||||
|         // collapsed: _floatingCollapsed(), | ||||
|         body: MapWidget(trip: widget.trip), | ||||
|         // renderPanelSheet: false, | ||||
|         // backdropEnabled: true, | ||||
|         maxHeight: MediaQuery.of(context).size.height * 0.8, | ||||
|         padding: EdgeInsets.all(10), | ||||
|         // panelSnapping: false, | ||||
|         borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), | ||||
|         boxShadow: [ | ||||
|           BoxShadow( | ||||
|             blurRadius: 20.0, | ||||
|             color: Colors.black, | ||||
|           ) | ||||
|         ], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _floatingCollapsed(){ | ||||
|     return Greeter( | ||||
|       trip: widget.trip | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _floatingPanel(){ | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Padding( | ||||
|           padding: const EdgeInsets.all(15), | ||||
|           child:  | ||||
|             Center( | ||||
|               child: Container( | ||||
|                 width: 40, | ||||
|                 height: 5, | ||||
|                 decoration: BoxDecoration( | ||||
|                   color: Colors.grey[300], | ||||
|                   borderRadius: BorderRadius.all(Radius.circular(12.0)), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|         ), | ||||
|         Expanded( | ||||
|           child: ListView( | ||||
|             children: [ | ||||
|               Greeter(trip: widget.trip), | ||||
|               LandmarksOverview(trip: widget.trip) | ||||
|             ] | ||||
|           ) | ||||
|         ) | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -3,6 +3,7 @@ | ||||
|  | ||||
| import 'dart:collection'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| @@ -75,8 +76,11 @@ class Trip with ChangeNotifier { | ||||
|     String? content = prefs.getString('trip_$uuid'); | ||||
|     Map<String, dynamic> json = jsonDecode(content!); | ||||
|     Trip trip = Trip.fromJson(json); | ||||
|     String? firstUUID = json['entry_uuid']; | ||||
|     readLandmarks(trip.landmarks, prefs, firstUUID); | ||||
|     String? firstUUID = json['first_landmark_uuid']; | ||||
|     log('Loading trip $uuid with first landmark $firstUUID'); | ||||
|     LinkedList<Landmark> landmarks = readLandmarks(prefs, firstUUID); | ||||
|     trip.landmarks = landmarks; | ||||
|     // notifyListeners(); | ||||
|     return trip; | ||||
|   } | ||||
|  | ||||
| @@ -90,6 +94,7 @@ class Trip with ChangeNotifier { | ||||
|  | ||||
|   void toPrefs(SharedPreferences prefs){ | ||||
|     Map<String, dynamic> json = toJson(); | ||||
|     log('Saving trip $uuid : $json'); | ||||
|     prefs.setString('trip_$uuid', jsonEncode(json)); | ||||
|     for (Landmark landmark in landmarks) { | ||||
|       landmarkToPrefs(prefs, landmark, landmark.next); | ||||
| @@ -99,12 +104,14 @@ class Trip with ChangeNotifier { | ||||
|  | ||||
|  | ||||
| // Helper | ||||
| readLandmarks(LinkedList<Landmark> landmarks, SharedPreferences prefs, String? firstUUID) { | ||||
| LinkedList<Landmark> readLandmarks(SharedPreferences prefs, String? firstUUID) { | ||||
|   LinkedList<Landmark> landmarks = LinkedList<Landmark>(); | ||||
|   while (firstUUID != null) { | ||||
|     var (head, nextUUID) = getLandmarkFromPrefs(prefs, firstUUID); | ||||
|     landmarks.add(head); | ||||
|     firstUUID = nextUUID; | ||||
|   } | ||||
|   return landmarks; | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -15,105 +15,5 @@ Future<List<Trip>> loadTrips() async { | ||||
|       trips.add(Trip.fromPrefs(prefs, uuid)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (trips.isEmpty) { | ||||
|     Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>()); | ||||
|     t1.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: '1', | ||||
|         name: "Eiffel Tower", | ||||
|         location: [48.859, 2.295], | ||||
|         type: LandmarkType(name: "Tower"), | ||||
|         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg" | ||||
|       ), | ||||
|     ); | ||||
|     t1.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: "2", | ||||
|         name: "Notre Dame Cathedral", | ||||
|         location: [48.8530, 2.3498], | ||||
|         type: LandmarkType(name: "Monument"), | ||||
|         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Notre-Dame_de_Paris%2C_4_October_2017.jpg/440px-Notre-Dame_de_Paris%2C_4_October_2017.jpg" | ||||
|       ), | ||||
|     ); | ||||
|     t1.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: "3", | ||||
|         name: "Louvre palace", | ||||
|         location: [48.8606, 2.3376], | ||||
|         type: LandmarkType(name: "Museum"), | ||||
|         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Louvre_Museum_Wikimedia_Commons.jpg/540px-Louvre_Museum_Wikimedia_Commons.jpg" | ||||
|       ), | ||||
|     ); | ||||
|     t1.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: "4", | ||||
|         name: "Pont-des-arts", | ||||
|         location: [48.8585, 2.3376], | ||||
|         type: LandmarkType(name: "Bridge"), | ||||
|         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg/560px-Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg" | ||||
|       ), | ||||
|     ); | ||||
|     t1.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: "5", | ||||
|         name: "Panthéon", | ||||
|         location: [48.847, 2.347], | ||||
|         type: LandmarkType(name: "Monument"), | ||||
|         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Pantheon_of_Paris_007.JPG/1280px-Pantheon_of_Paris_007.JPG" | ||||
|       ), | ||||
|     ); | ||||
|     trips.add(t1); | ||||
|  | ||||
|  | ||||
|     Trip t2 = Trip(uuid: '2', landmarks: LinkedList<Landmark>()); | ||||
|  | ||||
|     t2.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: '21', | ||||
|         name: "St. Charles's Church", | ||||
|         location: [48.1924563,16.3334399], | ||||
|         type: LandmarkType(name: "Monument"), | ||||
|         imageURL: "https://lh5.googleusercontent.com/p/AF1QipNNmA76Ps71NCL9rOOFoyheCEOyXWdHcUgQx9jd=w408-h305-k-no" | ||||
|       ), | ||||
|     ); | ||||
|     t2.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: "22", | ||||
|         name: "Vienna State Opera", | ||||
|         location: [48.1949124,16.3483292], | ||||
|         type: LandmarkType(name: "Culture"), | ||||
|         imageURL: "https://lh5.googleusercontent.com/p/AF1QipMOx398kcoeDXFruSHNsb4lmZtdT8vibtK0cLi-=w408-h306-k-no" | ||||
|       ), | ||||
|     ); | ||||
|     t2.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: "23", | ||||
|         name: "Belvedere-Schlossgarten", | ||||
|         location: [48.1956427,16.3711521], | ||||
|         type: LandmarkType(name: "Nature"), | ||||
|         imageURL: "https://lh5.googleusercontent.com/p/AF1QipNcI5LImH2Qdzx0GmF-5CY1wRKINFZ7HkahPEy1=w408-h306-k-no" | ||||
|       ), | ||||
|     ); | ||||
|     t2.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: "24", | ||||
|         name: "Kunsthistorisches Museum Wien", | ||||
|         location: [48.2047501,16.3581904], | ||||
|         type: LandmarkType(name: "Museum"), | ||||
|         imageURL: "https://lh5.googleusercontent.com/p/AF1QipPuDu-kCCowO4TcawjziE8AhDVAANagVtRYBjlv=w408-h450-k-no" | ||||
|       ), | ||||
|     ); | ||||
|     t2.landmarks.add( | ||||
|       Landmark( | ||||
|         uuid: "25", | ||||
|         name: "Salztorbrücke", | ||||
|         location: [48.2132382,16.369051], | ||||
|         type: LandmarkType(name: "Bridge"), | ||||
|       ), | ||||
|     ); | ||||
|     trips.add(t2); | ||||
|  | ||||
|   } | ||||
|   return trips; | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,14 @@ | ||||
| # Generated by pub | ||||
| # See https://dart.dev/tools/pub/glossary#lockfile | ||||
| packages: | ||||
|   args: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: args | ||||
|       sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.5.0" | ||||
|   async: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -174,6 +182,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.21" | ||||
|   flutter_svg: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_svg | ||||
|       sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.10+1" | ||||
|   flutter_test: | ||||
|     dependency: "direct dev" | ||||
|     description: flutter | ||||
| @@ -292,18 +308,18 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: leak_tracker | ||||
|       sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" | ||||
|       sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "10.0.4" | ||||
|     version: "10.0.5" | ||||
|   leak_tracker_flutter_testing: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: leak_tracker_flutter_testing | ||||
|       sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" | ||||
|       sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.3" | ||||
|     version: "3.0.5" | ||||
|   leak_tracker_testing: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -320,6 +336,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.0.0" | ||||
|   map_launcher: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: map_launcher | ||||
|       sha256: af59b9f79f641022e06761c9d4217c6c57b9ef9020af2fdb23155ec87af79e61 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.3.1" | ||||
|   matcher: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -332,18 +356,18 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: material_color_utilities | ||||
|       sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" | ||||
|       sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.8.0" | ||||
|     version: "0.11.1" | ||||
|   meta: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: meta | ||||
|       sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" | ||||
|       sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.12.0" | ||||
|     version: "1.15.0" | ||||
|   nested: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -368,6 +392,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.9.0" | ||||
|   path_parsing: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: path_parsing | ||||
|       sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|   path_provider: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -416,6 +448,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.0" | ||||
|   petitparser: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: petitparser | ||||
|       sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.0.2" | ||||
|   platform: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -609,10 +649,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: test_api | ||||
|       sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" | ||||
|       sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.7.0" | ||||
|     version: "0.7.2" | ||||
|   typed_data: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -629,6 +669,30 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.4.2" | ||||
|   vector_graphics: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: vector_graphics | ||||
|       sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.11+1" | ||||
|   vector_graphics_codec: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: vector_graphics_codec | ||||
|       sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.11+1" | ||||
|   vector_graphics_compiler: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: vector_graphics_compiler | ||||
|       sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.11+1" | ||||
|   vector_math: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -641,10 +705,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: vm_service | ||||
|       sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" | ||||
|       sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "14.2.1" | ||||
|     version: "14.2.4" | ||||
|   web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -669,6 +733,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.4" | ||||
|   xml: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: xml | ||||
|       sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.5.0" | ||||
| sdks: | ||||
|   dart: ">=3.4.0 <4.0.0" | ||||
|   flutter: ">=3.22.0" | ||||
|   | ||||
| @@ -45,6 +45,8 @@ dependencies: | ||||
|   widget_to_marker: ^1.0.6 | ||||
|   provider: ^6.1.2 | ||||
|   auto_size_text: ^3.0.0 | ||||
|   map_launcher: ^3.3.1 | ||||
|   flutter_svg: ^2.0.10+1 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user