more pleasant progress handling, although somewhat flawed
This commit is contained in:
		| @@ -7,14 +7,11 @@ import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
|  | ||||
|  | ||||
|  | ||||
| List<Widget> landmarksList(Trip trip) { | ||||
|   log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
| // Returns a list of widgets that represent the landmarks matching the given selector | ||||
| List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector}) { | ||||
|    | ||||
|   List<Widget> children = []; | ||||
|  | ||||
|   log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
|  | ||||
|   if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == typeStart ) { | ||||
|     children.add( | ||||
|       const Text("No landmarks in this trip"), | ||||
| @@ -23,16 +20,18 @@ List<Widget> landmarksList(Trip trip) { | ||||
|   } | ||||
|  | ||||
|   for (Landmark landmark in trip.landmarks) { | ||||
|     if (selector(landmark)) { | ||||
|       children.add( | ||||
|         LandmarkCard(landmark, trip), | ||||
|       ); | ||||
|  | ||||
|     if (landmark.next != null) { | ||||
|       if (!landmark.visited && landmark.next != null) { | ||||
|         children.add( | ||||
|           StepBetweenLandmarks(current: landmark, next: landmark.next!) | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return children; | ||||
| } | ||||
|   | ||||
| @@ -9,14 +9,10 @@ import 'package:google_maps_flutter/google_maps_flutter.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:widget_to_marker/widget_to_marker.dart'; | ||||
|  | ||||
|  | ||||
| class CurrentTripMap extends StatefulWidget { | ||||
|  | ||||
|   final Trip? trip; | ||||
|  | ||||
|   CurrentTripMap({ | ||||
|     this.trip | ||||
|   }); | ||||
|   CurrentTripMap({this.trip}); | ||||
|  | ||||
|   @override | ||||
|   State<CurrentTripMap> createState() => _CurrentTripMapState(); | ||||
| @@ -30,7 +26,23 @@ class _CurrentTripMapState extends State<CurrentTripMap> { | ||||
|     zoom: 11.0, | ||||
|   ); | ||||
|   Set<Marker> mapMarkers = <Marker>{}; | ||||
|   Set<Polyline> mapPolylines = <Polyline>{}; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     widget.trip?.addListener(setMapMarkers); | ||||
|     widget.trip?.addListener(setMapRoute); | ||||
|  | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     widget.trip?.removeListener(setMapMarkers); | ||||
|     widget.trip?.removeListener(setMapRoute); | ||||
|      | ||||
|     super.dispose(); | ||||
|   } | ||||
|  | ||||
|   void _onMapCreated(GoogleMapController controller) async { | ||||
|     mapController = controller; | ||||
| @@ -40,16 +52,17 @@ class _CurrentTripMapState extends State<CurrentTripMap> { | ||||
|       controller.moveCamera(update); | ||||
|     } | ||||
|     setMapMarkers(); | ||||
|     setMapRoute(); | ||||
|   } | ||||
|  | ||||
|   void _onCameraIdle() { | ||||
|     // print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0))); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   void setMapMarkers() async { | ||||
|     List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? []; | ||||
|     Set<Marker> newMarkers = <Marker>{}; | ||||
|     Set<Marker> markers = <Marker>{}; | ||||
|  | ||||
|     for (int i = 0; i < landmarks.length; i++) { | ||||
|       Landmark landmark = landmarks[i]; | ||||
|       List<double> location = landmark.location; | ||||
| @@ -58,20 +71,47 @@ class _CurrentTripMapState extends State<CurrentTripMap> { | ||||
|         position: LatLng(location[0], location[1]), | ||||
|         icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor( | ||||
|           logicalSize: const Size(150, 150), | ||||
|           imageSize: const Size(150, 150) | ||||
|           imageSize: const Size(150, 150), | ||||
|         ), | ||||
|       ); | ||||
|       newMarkers.add(marker); | ||||
|       markers.add(marker); | ||||
|     } | ||||
|     setState(() { | ||||
|       mapMarkers = newMarkers; | ||||
|       mapMarkers = markers; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   void setMapRoute() async { | ||||
|     List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? []; | ||||
|     Set<Polyline> polyLines = <Polyline>{}; | ||||
|  | ||||
|     if (landmarks.length < 2) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     for (Landmark landmark in landmarks) { | ||||
|       if (landmark.next != null) { | ||||
|         List<LatLng> step = [ | ||||
|           LatLng(landmark.location[0], landmark.location[1]), | ||||
|           LatLng(landmark.next!.location[0], landmark.next!.location[1]) | ||||
|         ]; | ||||
|         Polyline stepLine = Polyline( | ||||
|           polylineId: PolylineId('step-${landmark.uuid}'), | ||||
|           points: step, | ||||
|           color: landmark.visited ? Colors.grey : PRIMARY_COLOR, | ||||
|           width: 5, | ||||
|         ); | ||||
|         polyLines.add(stepLine); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     setState(() { | ||||
|       mapPolylines = polyLines; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     widget.trip?.addListener(setMapMarkers); | ||||
|     Future<SharedPreferences> preferences = SharedPreferences.getInstance(); | ||||
|  | ||||
|     return FutureBuilder( | ||||
| @@ -84,7 +124,7 @@ class _CurrentTripMapState extends State<CurrentTripMap> { | ||||
|         } else { | ||||
|           return const CircularProgressIndicator(); | ||||
|         } | ||||
|       } | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @@ -93,8 +133,8 @@ class _CurrentTripMapState extends State<CurrentTripMap> { | ||||
|       onMapCreated: _onMapCreated, | ||||
|       initialCameraPosition: _cameraPosition, | ||||
|       onCameraIdle: _onCameraIdle, | ||||
|       // onLongPress: , | ||||
|       markers: mapMarkers, | ||||
|       polylines: mapPolylines, | ||||
|       cloudMapId: MAP_ID, | ||||
|       mapToolbarEnabled: false, | ||||
|       zoomControlsEnabled: false, | ||||
| @@ -102,5 +142,4 @@ class _CurrentTripMapState extends State<CurrentTripMap> { | ||||
|       myLocationButtonEnabled: false, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/modules/current_trip_error_message.dart'; | ||||
| import 'package:anyway/modules/current_trip_loading_indicator.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| @@ -63,13 +64,40 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> { | ||||
|                 child: CurrentTripGreeter(trip: widget.trip), | ||||
|               ), | ||||
|  | ||||
|               Padding( | ||||
|                 padding: EdgeInsets.all(10), | ||||
|                 child: Container( | ||||
|                   decoration: BoxDecoration( | ||||
|                     color: Colors.grey[100], | ||||
|                     borderRadius: BorderRadius.circular(10), | ||||
|                   ), | ||||
|                   child: Column( | ||||
|                     children: [ | ||||
|                       CurrentTripSummary(trip: widget.trip), | ||||
|                       ExpansionTile( | ||||
|                         leading: Icon(Icons.location_on), | ||||
|                         title: Text('Visited Landmarks (tap to expand)'), | ||||
|                         children: [ | ||||
|                           ...landmarksList(widget.trip, selector: (Landmark landmark) => landmark.visited), | ||||
|                         ], | ||||
|                         visualDensity: VisualDensity.compact, | ||||
|                         collapsedShape: RoundedRectangleBorder( | ||||
|                           borderRadius: BorderRadius.circular(10), | ||||
|                         ), | ||||
|                         shape: RoundedRectangleBorder( | ||||
|                           borderRadius: BorderRadius.circular(10), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|                | ||||
|  | ||||
|               const Padding(padding: EdgeInsets.only(top: 10)), | ||||
|  | ||||
|               // CurrentTripSummary(trip: widget.trip), | ||||
|  | ||||
|               // const Divider(), | ||||
|  | ||||
|               ...landmarksList(widget.trip), | ||||
|               // upcoming landmarks | ||||
|               ...landmarksList(widget.trip, selector: (Landmark landmark) => landmark.visited == false), | ||||
|  | ||||
|               const Padding(padding: EdgeInsets.only(top: 10)), | ||||
|  | ||||
|   | ||||
| @@ -15,17 +15,27 @@ class CurrentTripSummary extends StatefulWidget { | ||||
| class _CurrentTripSummaryState extends State<CurrentTripSummary> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Column( | ||||
|     return Padding( | ||||
|       padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), | ||||
|       child: Row( | ||||
|         mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|         children: [ | ||||
|         Text('Summary'), | ||||
|         // Text('Start: ${widget.trip.start}'), | ||||
|         // Text('End: ${widget.trip.end}'), | ||||
|         Text('Total duration: ${widget.trip.totalTime}'), | ||||
|         Text('Total distance: ${widget.trip.totalTime}'), | ||||
|         // Text('Fuel: ${widget.trip.fuel}'), | ||||
|         // Text('Cost: ${widget.trip.cost}'), | ||||
|           Row( | ||||
|             children: [ | ||||
|               Icon(Icons.flag, size: 20), | ||||
|               Padding(padding: EdgeInsets.only(right: 10)), | ||||
|               Text('Stops: ${widget.trip.landmarks.length}', style: Theme.of(context).textTheme.bodyLarge,), | ||||
|             ] | ||||
|           ), | ||||
|           Row( | ||||
|             children: [ | ||||
|               Icon(Icons.hourglass_bottom_rounded, size: 20), | ||||
|               Padding(padding: EdgeInsets.only(right: 10)), | ||||
|               Text('Duration: ${widget.trip.totalTime} minutes', style: Theme.of(context).textTheme.bodyLarge,), | ||||
|             ] | ||||
|           ), | ||||
|         ], | ||||
|       ) | ||||
|     ); | ||||
|      | ||||
|   } | ||||
| } | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @@ -31,108 +32,151 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|       ); | ||||
|  | ||||
|     } | ||||
|     // else: | ||||
|  | ||||
|     return Container( | ||||
|       constraints: BoxConstraints( | ||||
|         minHeight: 50, | ||||
|         maxHeight: 200, | ||||
|       ), | ||||
|       child: Card( | ||||
|         shape: RoundedRectangleBorder( | ||||
|           borderRadius: BorderRadius.circular(15.0), | ||||
|         ), | ||||
|         elevation: 5, | ||||
|         clipBehavior: Clip.antiAliasWithSaveLayer, | ||||
|         // if the image is available, display it on the left side of the card, otherwise only display the text | ||||
|         child: widget.landmark.imageURL != null ? splitLayout() : textLayout(), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|          | ||||
|   Widget splitLayout() { | ||||
|     // If an image is available, display it on the left side of the card | ||||
|     return Row( | ||||
|         // if the image is available, display it on the left side of the card, otherwise only display the text | ||||
|         child: Row( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|         Container( | ||||
|           // the image on the left | ||||
|           width: 160, | ||||
|           height: 160, | ||||
|  | ||||
|             // Image and landmark "type" on the left | ||||
|             AspectRatio( | ||||
|               aspectRatio: 3 / 4, | ||||
|               child: Column( | ||||
|                 children: [ | ||||
|                   if (widget.landmark.imageURL != null && widget.landmark.imageURL!.isNotEmpty) | ||||
|                     Expanded( | ||||
|                       child: CachedNetworkImage( | ||||
|             imageUrl: widget.landmark.imageURL ?? '', | ||||
|                         imageUrl: widget.landmark.imageURL!, | ||||
|                         placeholder: (context, url) => Center(child: CircularProgressIndicator()), | ||||
|                         errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), | ||||
|                         fit: BoxFit.cover, | ||||
|                       ) | ||||
|                     ) | ||||
|                   else | ||||
|                     Expanded( | ||||
|                       child:  | ||||
|                       Container( | ||||
|                         decoration: BoxDecoration( | ||||
|                           gradient: LinearGradient( | ||||
|                             begin: Alignment.topLeft, | ||||
|                             end: Alignment.bottomRight, | ||||
|                             colors: [GRADIENT_START, GRADIENT_END], | ||||
|                           ), | ||||
|                         ), | ||||
|                         child: Center( | ||||
|                           child: Icon(widget.landmark.type.icon.icon, size: 50), | ||||
|                         ), | ||||
|                       ), | ||||
|         Flexible( | ||||
|           child: textLayout(), | ||||
|                     ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget textLayout() { | ||||
|     return Padding( | ||||
|       padding: EdgeInsets.all(10), | ||||
|       child: Column( | ||||
|                   Container( | ||||
|                     color: PRIMARY_COLOR, | ||||
|                     child: Center( | ||||
|                       child: Padding( | ||||
|                         padding: EdgeInsets.all(5), | ||||
|                         child: Row( | ||||
|                           mainAxisAlignment: MainAxisAlignment.center, | ||||
|                           spacing: 5, | ||||
|                           children: [ | ||||
|           Row( | ||||
|             children: [ | ||||
|               Flexible( | ||||
|                 child: Text( | ||||
|                   widget.landmark.name, | ||||
|                   style: const TextStyle( | ||||
|                     fontSize: 18, | ||||
|                     fontWeight: FontWeight.bold, | ||||
|                             Icon(widget.landmark.type.icon.icon, size: 16), | ||||
|                             Text(widget.landmark.type.name, style: TextStyle(fontWeight: FontWeight.bold)), | ||||
|                           ], | ||||
|                         ) | ||||
|                       ) | ||||
|                     ), | ||||
|                   ) | ||||
|                 ], | ||||
|               ) | ||||
|             ), | ||||
|  | ||||
|             // Main information, useful buttons on the right | ||||
|             Expanded( | ||||
|               child: Padding( | ||||
|                 padding: const EdgeInsets.all(10), | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Text( | ||||
|                       widget.landmark.name, | ||||
|                       style: Theme.of(context).textTheme.titleMedium, | ||||
|                       overflow: TextOverflow.ellipsis, | ||||
|                       maxLines: 2, | ||||
|                     ), | ||||
|               ) | ||||
|             ], | ||||
|           ), | ||||
|                        | ||||
|                     if (widget.landmark.nameEN != null) | ||||
|             Row( | ||||
|               children: [ | ||||
|                 Flexible( | ||||
|                   child: Text( | ||||
|                       Text( | ||||
|                         widget.landmark.nameEN!, | ||||
|                     style: const TextStyle( | ||||
|                       fontSize: 16, | ||||
|                     ), | ||||
|                         style: Theme.of(context).textTheme.bodyMedium, | ||||
|                         maxLines: 1, | ||||
|                         overflow: TextOverflow.ellipsis, | ||||
|                       ), | ||||
|                 ) | ||||
|               ], | ||||
|             ), | ||||
|           Padding(padding: EdgeInsets.only(top: 10)), | ||||
|           Align( | ||||
|             alignment: Alignment.centerLeft, | ||||
|             child: SingleChildScrollView( | ||||
|  | ||||
|                     // fill the vspace | ||||
|                     const Spacer(), | ||||
|  | ||||
|                     SingleChildScrollView( | ||||
|                       // allows the buttons to be scrolled | ||||
|                       scrollDirection: Axis.horizontal, | ||||
|                       child: Wrap( | ||||
|                         spacing: 10, | ||||
|                         // show the type, the website, and the wikipedia link as buttons/labels in a row | ||||
|                         children: [ | ||||
|                   TextButton.icon( | ||||
|                     onPressed: () {}, | ||||
|                     icon: widget.landmark.type.icon, | ||||
|                     label: Text(widget.landmark.type.name), | ||||
|                   ), | ||||
|                   if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0) | ||||
|                     TextButton.icon( | ||||
|                       onPressed: () {}, | ||||
|                       icon: Icon(Icons.hourglass_bottom), | ||||
|                       label: Text('${widget.landmark.duration!.inMinutes} minutes'), | ||||
|                     ), | ||||
|                           doneToggleButton(), | ||||
|                           // if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0) | ||||
|                           //   TextButton.icon( | ||||
|                           //     onPressed: () {}, | ||||
|                           //     icon: Icon(Icons.hourglass_bottom), | ||||
|                           //     label: Text('${widget.landmark.duration!.inMinutes} minutes'), | ||||
|                           //   ), | ||||
|                           if (widget.landmark.websiteURL != null) | ||||
|                     TextButton.icon( | ||||
|                             websiteButton(), | ||||
|                            | ||||
|                           optionsButton() | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ) | ||||
|           ], | ||||
|         ) | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget doneToggleButton() { | ||||
|     return TextButton.icon( | ||||
|       onPressed: () async { | ||||
|         widget.landmark.visited = !widget.landmark.visited; | ||||
|         widget.parentTrip.notifyUpdate(); | ||||
|       }, | ||||
|       icon: Icon(widget.landmark.visited ? Icons.undo_sharp : Icons.check), | ||||
|       label: Text(widget.landmark.visited ? "Add to overview" : "Done!"), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget websiteButton () => TextButton.icon( | ||||
|     onPressed: () async { | ||||
|       // open a browser with the website link | ||||
|       await launchUrl(Uri.parse(widget.landmark.websiteURL!)); | ||||
|     }, | ||||
|     icon: Icon(Icons.link), | ||||
|     label: Text('Website'), | ||||
|                     ), | ||||
|                   PopupMenuButton( | ||||
|   ); | ||||
|  | ||||
|   Widget optionsButton () => PopupMenuButton( | ||||
|     icon: Icon(Icons.settings), | ||||
|     style: TextButtonTheme.of(context).style, | ||||
|     itemBuilder: (context) => [ | ||||
| @@ -159,14 +203,5 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|         ), | ||||
|       ), | ||||
|     ], | ||||
|                   ) | ||||
|                      | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|   ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -40,7 +40,7 @@ class ThemedMarker extends StatelessWidget { | ||||
|         children: [ | ||||
|           Container( | ||||
|             decoration: BoxDecoration( | ||||
|               gradient: APP_GRADIENT, | ||||
|               gradient: landmark.visited ? LinearGradient(colors: [Colors.grey, Colors.white]) : APP_GRADIENT, | ||||
|               shape: BoxShape.circle, | ||||
|               border: Border.all(color: Colors.black, width: 5), | ||||
|             ), | ||||
|   | ||||
| @@ -27,7 +27,7 @@ final class Landmark extends LinkedListEntry<Landmark>{ | ||||
|   String? imageURL; // not final because it can be patched | ||||
|   final String? description; | ||||
|   final Duration? duration; | ||||
|   final bool? visited; | ||||
|   bool visited; | ||||
|  | ||||
|   // Next node | ||||
|   // final Landmark? next; | ||||
| @@ -46,7 +46,7 @@ final class Landmark extends LinkedListEntry<Landmark>{ | ||||
|     this.imageURL, | ||||
|     this.description, | ||||
|     this.duration, | ||||
|     this.visited, | ||||
|     this.visited = false, | ||||
|  | ||||
|     // this.next, | ||||
|     this.tripTime, | ||||
| @@ -71,7 +71,7 @@ final class Landmark extends LinkedListEntry<Landmark>{ | ||||
|       final imageURL = json['image_url'] as String?; | ||||
|       final description = json['description'] as String?; | ||||
|       var duration = Duration(minutes: json['duration'] ?? 0) as Duration?; | ||||
|       final visited = json['visited'] as bool?; | ||||
|       final visited = json['visited'] ?? false as bool; | ||||
|       var tripTime = Duration(minutes: json['time_to_reach_next'] ?? 0) as Duration?; | ||||
|        | ||||
|       return Landmark( | ||||
|   | ||||
| @@ -52,6 +52,7 @@ class Trip with ChangeNotifier { | ||||
|     totalTime = json['total_time']; | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   void addLandmark(Landmark landmark) { | ||||
|     landmarks.add(landmark); | ||||
|     notifyListeners(); | ||||
| @@ -72,6 +73,10 @@ class Trip with ChangeNotifier { | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   void notifyUpdate(){ | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   factory Trip.fromPrefs(SharedPreferences prefs, String uuid) { | ||||
|     String? content = prefs.getString('trip_$uuid'); | ||||
|     Map<String, dynamic> json = jsonDecode(content!); | ||||
| @@ -80,7 +85,6 @@ class Trip with ChangeNotifier { | ||||
|     log('Loading trip $uuid with first landmark $firstUUID'); | ||||
|     LinkedList<Landmark> landmarks = readLandmarks(prefs, firstUUID); | ||||
|     trip.landmarks = landmarks; | ||||
|     // notifyListeners(); | ||||
|     return trip; | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user