location picker and ui fixes #17
| @@ -2,3 +2,4 @@ const String APP_NAME = 'AnyWay'; | |||||||
|  |  | ||||||
| String API_URL_BASE = 'https://anyway.kluster.moll.re'; | 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/constants.dart'; | ||||||
|  |  | ||||||
| import 'package:anyway/structs/trip.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/utils/load_trips.dart'; | ||||||
|  |  | ||||||
| import 'package:anyway/pages/new_trip.dart'; | import 'package:anyway/pages/new_trip.dart'; | ||||||
| import 'package:anyway/pages/tutorial.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'; | import 'package:anyway/pages/profile.dart'; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| // BasePage is the scaffold that holds all other pages | // BasePage is the scaffold that holds all other pages | ||||||
| // A side drawer is used to switch between pages | // A side drawer is used to switch between pages | ||||||
| class BasePage extends StatefulWidget { | class BasePage extends StatefulWidget { | ||||||
| @@ -39,7 +42,25 @@ class _BasePageState extends State<BasePage> { | |||||||
|      |      | ||||||
|      |      | ||||||
|     if (widget.mainScreen == "map") { |     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") { |     } else if (widget.mainScreen == "tutorial") { | ||||||
|       currentView = TutorialPage(); |       currentView = TutorialPage(); | ||||||
|     } else if (widget.mainScreen == "profile") { |     } 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()); | void main() => runApp(const App()); | ||||||
|  |  | ||||||
|  | final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); | ||||||
|  |  | ||||||
| class App extends StatelessWidget { | class App extends StatelessWidget { | ||||||
|   const App({super.key}); |   const App({super.key}); | ||||||
|  |  | ||||||
| @@ -14,6 +16,7 @@ class App extends StatelessWidget { | |||||||
|       title: APP_NAME, |       title: APP_NAME, | ||||||
|       home: BasePage(mainScreen: "map"), |       home: BasePage(mainScreen: "map"), | ||||||
|       theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]), |       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:flutter/material.dart'; | ||||||
|  | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
|  |  | ||||||
|  | import 'package:anyway/structs/landmark.dart'; | ||||||
|  |  | ||||||
|  |  | ||||||
| class LandmarkCard extends StatefulWidget { | class LandmarkCard extends StatefulWidget { | ||||||
|   final Landmark landmark; |   final Landmark landmark; | ||||||
|  |    | ||||||
|  |   LandmarkCard(this.landmark); | ||||||
|  |    | ||||||
|   @override |   @override | ||||||
|   _LandmarkCardState createState() => _LandmarkCardState(); |   _LandmarkCardState createState() => _LandmarkCardState(); | ||||||
|  |  | ||||||
|   LandmarkCard(this.landmark); |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| class _LandmarkCardState extends State<LandmarkCard> { | class _LandmarkCardState extends State<LandmarkCard> { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   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> { | class _TripsOverviewState extends State<TripsOverview> { | ||||||
|   // final Future<List<Trip>> _trips = loadTrips(); |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|   Widget listBuild (BuildContext context, AsyncSnapshot<List<Trip>> snapshot) { |   Widget listBuild (BuildContext context, AsyncSnapshot<List<Trip>> snapshot) { | ||||||
|     List<Widget> children; |     List<Widget> children; | ||||||
| @@ -65,6 +63,7 @@ class _TripsOverviewState extends State<TripsOverview> { | |||||||
| 
 | 
 | ||||||
|     return ListView( |     return ListView( | ||||||
|       children: children, |       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:anyway/structs/landmark.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:geocoding/geocoding.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/utils/fetch_trip.dart'; | ||||||
| import 'package:anyway/structs/preferences.dart'; | import 'package:anyway/structs/preferences.dart'; | ||||||
| import "package:anyway/structs/trip.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 { | class NewTripPage extends StatefulWidget { | ||||||
| @@ -20,74 +22,25 @@ class _NewTripPageState extends State<NewTripPage> { | |||||||
|   final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); |   final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); | ||||||
|   final TextEditingController latController = TextEditingController(); |   final TextEditingController latController = TextEditingController(); | ||||||
|   final TextEditingController lonController = TextEditingController(); |   final TextEditingController lonController = TextEditingController(); | ||||||
|  |   Trip trip = Trip(); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|  |     // floating search bar and map as a background | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: const Text('New Trip'), |         title: const Text('New Trip'), | ||||||
|       ), |       ), | ||||||
|       body: Form( |       body: Stack( | ||||||
|         key: _formKey, |         children: [ | ||||||
|         child: Padding( |           NewTripMap(trip), | ||||||
|           padding: const EdgeInsets.all(15.0), |           Padding( | ||||||
|           child: Column( |             padding: EdgeInsets.all(15), | ||||||
|             crossAxisAlignment: CrossAxisAlignment.start, |             child: NewTripLocationSearch(trip), | ||||||
|              |  | ||||||
|             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) |  | ||||||
|                       ) |  | ||||||
|                     ); |  | ||||||
|                   } |  | ||||||
|                 }, |  | ||||||
|               ), |  | ||||||
|             ], |  | ||||||
|           ), |           ), | ||||||
|         ) |         ], | ||||||
|       ) |       ), | ||||||
|  |       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:collection'; | ||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
|  | import 'dart:developer'; | ||||||
|  |  | ||||||
| import 'package:anyway/structs/landmark.dart'; | import 'package:anyway/structs/landmark.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| @@ -75,8 +76,11 @@ class Trip with ChangeNotifier { | |||||||
|     String? content = prefs.getString('trip_$uuid'); |     String? content = prefs.getString('trip_$uuid'); | ||||||
|     Map<String, dynamic> json = jsonDecode(content!); |     Map<String, dynamic> json = jsonDecode(content!); | ||||||
|     Trip trip = Trip.fromJson(json); |     Trip trip = Trip.fromJson(json); | ||||||
|     String? firstUUID = json['entry_uuid']; |     String? firstUUID = json['first_landmark_uuid']; | ||||||
|     readLandmarks(trip.landmarks, prefs, firstUUID); |     log('Loading trip $uuid with first landmark $firstUUID'); | ||||||
|  |     LinkedList<Landmark> landmarks = readLandmarks(prefs, firstUUID); | ||||||
|  |     trip.landmarks = landmarks; | ||||||
|  |     // notifyListeners(); | ||||||
|     return trip; |     return trip; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -90,6 +94,7 @@ class Trip with ChangeNotifier { | |||||||
|  |  | ||||||
|   void toPrefs(SharedPreferences prefs){ |   void toPrefs(SharedPreferences prefs){ | ||||||
|     Map<String, dynamic> json = toJson(); |     Map<String, dynamic> json = toJson(); | ||||||
|  |     log('Saving trip $uuid : $json'); | ||||||
|     prefs.setString('trip_$uuid', jsonEncode(json)); |     prefs.setString('trip_$uuid', jsonEncode(json)); | ||||||
|     for (Landmark landmark in landmarks) { |     for (Landmark landmark in landmarks) { | ||||||
|       landmarkToPrefs(prefs, landmark, landmark.next); |       landmarkToPrefs(prefs, landmark, landmark.next); | ||||||
| @@ -99,12 +104,14 @@ class Trip with ChangeNotifier { | |||||||
|  |  | ||||||
|  |  | ||||||
| // Helper | // Helper | ||||||
| readLandmarks(LinkedList<Landmark> landmarks, SharedPreferences prefs, String? firstUUID) { | LinkedList<Landmark> readLandmarks(SharedPreferences prefs, String? firstUUID) { | ||||||
|  |   LinkedList<Landmark> landmarks = LinkedList<Landmark>(); | ||||||
|   while (firstUUID != null) { |   while (firstUUID != null) { | ||||||
|     var (head, nextUUID) = getLandmarkFromPrefs(prefs, firstUUID); |     var (head, nextUUID) = getLandmarkFromPrefs(prefs, firstUUID); | ||||||
|     landmarks.add(head); |     landmarks.add(head); | ||||||
|     firstUUID = nextUUID; |     firstUUID = nextUUID; | ||||||
|   } |   } | ||||||
|  |   return landmarks; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,105 +15,5 @@ Future<List<Trip>> loadTrips() async { | |||||||
|       trips.add(Trip.fromPrefs(prefs, uuid)); |       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; |   return trips; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,14 @@ | |||||||
| # Generated by pub | # Generated by pub | ||||||
| # See https://dart.dev/tools/pub/glossary#lockfile | # See https://dart.dev/tools/pub/glossary#lockfile | ||||||
| packages: | packages: | ||||||
|  |   args: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: args | ||||||
|  |       sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.5.0" | ||||||
|   async: |   async: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -174,6 +182,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.0.21" |     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: |   flutter_test: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: flutter |     description: flutter | ||||||
| @@ -292,18 +308,18 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: leak_tracker |       name: leak_tracker | ||||||
|       sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" |       sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "10.0.4" |     version: "10.0.5" | ||||||
|   leak_tracker_flutter_testing: |   leak_tracker_flutter_testing: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: leak_tracker_flutter_testing |       name: leak_tracker_flutter_testing | ||||||
|       sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" |       sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.0.3" |     version: "3.0.5" | ||||||
|   leak_tracker_testing: |   leak_tracker_testing: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -320,6 +336,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.0.0" |     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: |   matcher: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -332,18 +356,18 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: material_color_utilities |       name: material_color_utilities | ||||||
|       sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" |       sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.8.0" |     version: "0.11.1" | ||||||
|   meta: |   meta: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: meta |       name: meta | ||||||
|       sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" |       sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.12.0" |     version: "1.15.0" | ||||||
|   nested: |   nested: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -368,6 +392,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.9.0" |     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: |   path_provider: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -416,6 +448,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.3.0" |     version: "2.3.0" | ||||||
|  |   petitparser: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: petitparser | ||||||
|  |       sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "6.0.2" | ||||||
|   platform: |   platform: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -609,10 +649,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: test_api |       name: test_api | ||||||
|       sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" |       sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.7.0" |     version: "0.7.2" | ||||||
|   typed_data: |   typed_data: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -629,6 +669,30 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.4.2" |     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: |   vector_math: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -641,10 +705,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: vm_service |       name: vm_service | ||||||
|       sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" |       sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "14.2.1" |     version: "14.2.4" | ||||||
|   web: |   web: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -669,6 +733,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.4" |     version: "1.0.4" | ||||||
|  |   xml: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: xml | ||||||
|  |       sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "6.5.0" | ||||||
| sdks: | sdks: | ||||||
|   dart: ">=3.4.0 <4.0.0" |   dart: ">=3.4.0 <4.0.0" | ||||||
|   flutter: ">=3.22.0" |   flutter: ">=3.22.0" | ||||||
|   | |||||||
| @@ -45,6 +45,8 @@ dependencies: | |||||||
|   widget_to_marker: ^1.0.6 |   widget_to_marker: ^1.0.6 | ||||||
|   provider: ^6.1.2 |   provider: ^6.1.2 | ||||||
|   auto_size_text: ^3.0.0 |   auto_size_text: ^3.0.0 | ||||||
|  |   map_launcher: ^3.3.1 | ||||||
|  |   flutter_svg: ^2.0.10+1 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user