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/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,7 @@ class _BasePageState extends State<BasePage> { | |||||||
|      |      | ||||||
|      |      | ||||||
|     if (widget.mainScreen == "map") { |     if (widget.mainScreen == "map") { | ||||||
|       currentView = NavigationOverview(trip: widget.trip ?? getFirstTrip(trips)); |       currentView = TripPage(trip: widget.trip ?? getFirstTrip(trips)); | ||||||
|     } else if (widget.mainScreen == "tutorial") { |     } else if (widget.mainScreen == "tutorial") { | ||||||
|       currentView = TutorialPage(); |       currentView = TutorialPage(); | ||||||
|     } else if (widget.mainScreen == "profile") { |     } else if (widget.mainScreen == "profile") { | ||||||
|   | |||||||
| @@ -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 | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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; | ||||||
|   @override |  | ||||||
|   _LandmarkCardState createState() => _LandmarkCardState(); |  | ||||||
|    |    | ||||||
|   LandmarkCard(this.landmark); |   LandmarkCard(this.landmark); | ||||||
|    |    | ||||||
|  |   @override | ||||||
|  |   _LandmarkCardState createState() => _LandmarkCardState(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| class _LandmarkCardState extends State<LandmarkCard> { | class _LandmarkCardState extends State<LandmarkCard> { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								frontend/lib/modules/landmarks_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/lib/modules/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; | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -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,6 +1,7 @@ | |||||||
| import 'dart:collection'; | import 'dart:collection'; | ||||||
| import 'dart:developer'; |  | ||||||
|  |  | ||||||
|  | import 'package:anyway/constants.dart'; | ||||||
|  | import 'package:anyway/modules/themed_marker.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:anyway/structs/landmark.dart'; | import 'package:anyway/structs/landmark.dart'; | ||||||
| import 'package:anyway/structs/trip.dart'; | import 'package:anyway/structs/trip.dart'; | ||||||
| @@ -54,7 +55,7 @@ class _MapWidgetState extends State<MapWidget> { | |||||||
|       Marker marker = Marker( |       Marker marker = Marker( | ||||||
|         markerId: MarkerId(landmark.uuid), |         markerId: MarkerId(landmark.uuid), | ||||||
|         position: LatLng(location[0], location[1]), |         position: LatLng(location[0], location[1]), | ||||||
|         icon: await CustomMarker(landmark: landmark, position: i).toBitmapDescriptor( |         icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor( | ||||||
|           logicalSize: const Size(150, 150), |           logicalSize: const Size(150, 150), | ||||||
|           imageSize: const Size(150, 150) |           imageSize: const Size(150, 150) | ||||||
|         ), |         ), | ||||||
| @@ -75,77 +76,7 @@ class _MapWidgetState extends State<MapWidget> { | |||||||
|       onCameraIdle: _onCameraIdle, |       onCameraIdle: _onCameraIdle, | ||||||
|       // onLongPress: , |       // onLongPress: , | ||||||
|       markers: mapMarkers, |       markers: mapMarkers, | ||||||
|       cloudMapId: '41c21ac9b81dbfd8', |       cloudMapId: MAP_ID, | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 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(), |  | ||||||
|         ], |  | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
							
								
								
									
										78
									
								
								frontend/lib/modules/new_trip_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								frontend/lib/modules/new_trip_button.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | 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 SizedBox( | ||||||
|  |           width: 200, | ||||||
|  |           child: ElevatedButton( | ||||||
|  |             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) | ||||||
|  |                 ) | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |             child: Row( | ||||||
|  |               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |               children: [ | ||||||
|  |                 Icon( | ||||||
|  |                   Icons.add, | ||||||
|  |                 ), | ||||||
|  |                 Expanded( | ||||||
|  |                   child: Padding( | ||||||
|  |                     padding: EdgeInsets.only(left: 10, top: 5, bottom: 5, right: 5), | ||||||
|  |                     child: 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'), | ||||||
|  |       ),] | ||||||
|  |  | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								frontend/lib/modules/new_trip_map.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								frontend/lib/modules/new_trip_map.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  |  | ||||||
|  | // 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, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								frontend/lib/modules/save_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								frontend/lib/modules/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, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ), | ||||||
|  |   ) | ||||||
|  | ); | ||||||
|  |  | ||||||
							
								
								
									
										60
									
								
								frontend/lib/modules/step_between_landmarks.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								frontend/lib/modules/step_between_landmarks.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import 'package:anyway/structs/landmark.dart'; | ||||||
|  | import 'package:flutter/material.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), | ||||||
|  |         ), | ||||||
|  |         // 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"), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ) | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										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, | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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,31 @@ 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: [ | ||||||
|  |           NewTripMap(trip), | ||||||
|  |           Padding( | ||||||
|  |             padding: EdgeInsets.all(15), | ||||||
|  |             child: NewTripLocationSearch(trip), | ||||||
|  |           ), | ||||||
|  |         Align( | ||||||
|  |           alignment: Alignment.bottomRight, | ||||||
|           child: Padding( |           child: Padding( | ||||||
|           padding: const EdgeInsets.all(15.0), |             padding: EdgeInsets.all(15), | ||||||
|           child: Column( |             child: NewTripButton(trip: trip) | ||||||
|             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) |  | ||||||
|                       ) |  | ||||||
|                     ); |  | ||||||
|                   } |  | ||||||
|                 }, |  | ||||||
|         ), |         ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|         ) |  | ||||||
|       ) |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) |  | ||||||
|             ] |  | ||||||
|           ) |  | ||||||
|         ) |  | ||||||
|       ], |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										84
									
								
								frontend/lib/pages/trip.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								frontend/lib/pages/trip.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | import 'package:anyway/modules/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/landmarks_list.dart'; | ||||||
|  | import 'package:anyway/modules/greeter.dart'; | ||||||
|  | import 'package:anyway/modules/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}'), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user