location picker and ui fixes
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build and release APK / Build APK (pull_request) Successful in 5m25s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build and release APK / Build APK (pull_request) Successful in 5m25s
				
			This commit is contained in:
		| @@ -1,17 +1,19 @@ | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
|  | ||||
|  | ||||
| class LandmarkCard extends StatefulWidget { | ||||
|   final Landmark landmark; | ||||
|    | ||||
|   LandmarkCard(this.landmark); | ||||
|    | ||||
|   @override | ||||
|   _LandmarkCardState createState() => _LandmarkCardState(); | ||||
|  | ||||
|   LandmarkCard(this.landmark); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| class _LandmarkCardState extends State<LandmarkCard> { | ||||
|   @override | ||||
|   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:developer'; | ||||
|  | ||||
| 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'; | ||||
| @@ -54,7 +55,7 @@ class _MapWidgetState extends State<MapWidget> { | ||||
|       Marker marker = Marker( | ||||
|         markerId: MarkerId(landmark.uuid), | ||||
|         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), | ||||
|           imageSize: const Size(150, 150) | ||||
|         ), | ||||
| @@ -75,77 +76,7 @@ class _MapWidgetState extends State<MapWidget> { | ||||
|       onCameraIdle: _onCameraIdle, | ||||
|       // onLongPress: , | ||||
|       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, | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user