reworked page layout inheritence
This commit is contained in:
		| @@ -1,72 +1,51 @@ | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:anyway/modules/help_dialog.dart'; | ||||
| import 'package:anyway/pages/current_trip.dart'; | ||||
| import 'package:anyway/pages/settings.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| import 'package:anyway/constants.dart'; | ||||
| 
 | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:anyway/modules/help_dialog.dart'; | ||||
| import 'package:anyway/modules/trips_saved_list.dart'; | ||||
| import 'package:anyway/utils/load_trips.dart'; | ||||
| 
 | ||||
| import 'package:anyway/pages/new_trip_location.dart'; | ||||
| import 'package:anyway/pages/onboarding.dart'; | ||||
| import 'package:anyway/pages/current_trip.dart'; | ||||
| import 'package:anyway/pages/settings.dart'; | ||||
| import 'package:anyway/pages/new_trip_location.dart'; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| // BasePage is the scaffold that holds a child page and a side drawer | ||||
| // The side drawer is the main way to switch between pages | ||||
| 
 | ||||
| class BasePage extends StatefulWidget { | ||||
|   final Widget mainScreen; | ||||
|   final Widget title; | ||||
|   final List<String> helpTexts; | ||||
| 
 | ||||
|   const BasePage({ | ||||
|     super.key, | ||||
|     required this.mainScreen, | ||||
|     this.title = const Text(APP_NAME), | ||||
|     this.helpTexts = const [], | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<BasePage> createState() => _BasePageState(); | ||||
| } | ||||
| 
 | ||||
| class _BasePageState extends State<BasePage> { | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     savedTrips.loadTrips(); | ||||
| 
 | ||||
| 
 | ||||
| mixin ScaffoldLayout<T extends StatefulWidget> on State<T> { | ||||
|   Widget mainScaffold( | ||||
|     BuildContext context, | ||||
|     { | ||||
|       Widget child = const Text("emptiness"), | ||||
|       Widget title = const Text(APP_NAME), | ||||
|       List<String> helpTexts = const [] | ||||
|     } | ||||
|   ) { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         title: widget.title, | ||||
|         title: title, | ||||
|         actions: [ | ||||
|           IconButton( | ||||
|             icon: const Icon(Icons.help), | ||||
|             tooltip: 'Help', | ||||
|             onPressed: () { | ||||
|               if (widget.helpTexts.isNotEmpty) { | ||||
|                 helpDialog(context, widget.helpTexts[0], widget.helpTexts[1]); | ||||
|               if (helpTexts.isNotEmpty) { | ||||
|                 helpDialog(context, helpTexts[0], helpTexts[1]); | ||||
|               } | ||||
|             } | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       body: Center(child: widget.mainScreen), | ||||
|       body: Center(child: child), | ||||
|       drawer: Drawer( | ||||
|         child: Column( | ||||
|           children: [ | ||||
|             Container( | ||||
|               decoration: BoxDecoration( | ||||
|               decoration: const BoxDecoration( | ||||
|                 gradient: APP_GRADIENT, | ||||
|               ), | ||||
|               height: 150, | ||||
|               child: Center( | ||||
|               child: const Center( | ||||
|                 child: Text( | ||||
|                   APP_NAME, | ||||
|                   style: TextStyle( | ||||
| @@ -81,8 +60,7 @@ class _BasePageState extends State<BasePage> { | ||||
|             ListTile( | ||||
|               title: const Text('Your Trips'), | ||||
|               leading: const Icon(Icons.map), | ||||
|               // TODO: this is not working! | ||||
|               selected: widget.mainScreen is TripPage, | ||||
|               selected: widget is TripPage, | ||||
|               onTap: () {}, | ||||
|               trailing: ElevatedButton( | ||||
|                 onPressed: () { | ||||
| @@ -111,13 +89,12 @@ class _BasePageState extends State<BasePage> { | ||||
|             const Divider(indent: 10, endIndent: 10), | ||||
|             ListTile( | ||||
|               title: const Text('How to use'), | ||||
|               leading: Icon(Icons.help), | ||||
|               // TODO: this is not working! | ||||
|               selected: widget.mainScreen is OnboardingPage, | ||||
|               leading: const Icon(Icons.help), | ||||
|               selected: widget is OnboardingPage, | ||||
|               onTap: () { | ||||
|                 Navigator.of(context).push( | ||||
|                   MaterialPageRoute( | ||||
|                     builder: (context) => OnboardingPage() | ||||
|                     builder: (context) => const OnboardingPage() | ||||
|                   ) | ||||
|                 ); | ||||
|               }, | ||||
| @@ -127,8 +104,7 @@ class _BasePageState extends State<BasePage> { | ||||
|             ListTile( | ||||
|               title: const Text('Settings'), | ||||
|               leading: const Icon(Icons.settings), | ||||
|               // TODO: this is not working! | ||||
|               selected: widget.mainScreen is SettingsPage, | ||||
|               selected: widget is SettingsPage, | ||||
|               onTap: () { | ||||
|                 Navigator.of(context).push( | ||||
|                   MaterialPageRoute( | ||||
| @@ -1,24 +1,27 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/utils/get_first_page.dart'; | ||||
| import 'package:anyway/utils/load_trips.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:anyway/constants.dart'; | ||||
|  | ||||
|  | ||||
| void main() => runApp(const App()); | ||||
|  | ||||
| // Some global variables | ||||
| final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); | ||||
| final SavedTrips savedTrips = SavedTrips(); | ||||
| // the list of saved trips is then populated implicitly by getFirstPage() | ||||
|  | ||||
|  | ||||
| class App extends StatelessWidget { | ||||
|   const App({super.key}); | ||||
|  | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return MaterialApp( | ||||
|       title: APP_NAME, | ||||
|       home: getFirstPage(), | ||||
|       theme: APP_THEME, | ||||
|       scaffoldMessengerKey: rootScaffoldMessengerKey | ||||
|     ); | ||||
|   } | ||||
|   Widget build(BuildContext context) => MaterialApp( | ||||
|     title: APP_NAME, | ||||
|     home: getFirstPage(), | ||||
|     theme: APP_THEME, | ||||
|     scaffoldMessengerKey: rootScaffoldMessengerKey | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,9 @@ | ||||
| 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/modules/step_between_landmarks.dart'; | ||||
| import 'package:anyway/modules/landmark_card.dart'; | ||||
|  | ||||
|  | ||||
| // Returns a list of widgets that represent the landmarks matching the given selector | ||||
| @@ -35,4 +34,3 @@ List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector | ||||
|  | ||||
|   return children; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -35,29 +35,29 @@ class _CurrentTripLoadingIndicatorState extends State<CurrentTripLoadingIndicato | ||||
|       // In the very center of the panel, show the greeter which tells the user that the trip is being generated | ||||
|       Center(child: loadingText(widget.trip)), | ||||
|       // As a gimmick, and a way to show that the app is still working, show a few loading dots | ||||
|       Align( | ||||
|       const Align( | ||||
|         alignment: Alignment.bottomCenter, | ||||
|         child: statusText(), | ||||
|         child: StatusText(), | ||||
|       ) | ||||
|     ], | ||||
|   ); | ||||
| } | ||||
|  | ||||
| // automatically cycle through the greeter texts | ||||
| class statusText extends StatefulWidget { | ||||
|   const statusText({Key? key}) : super(key: key); | ||||
| class StatusText extends StatefulWidget { | ||||
|   const StatusText({Key? key}) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   _statusTextState createState() => _statusTextState(); | ||||
|   _StatusTextState createState() => _StatusTextState(); | ||||
| } | ||||
|  | ||||
| class _statusTextState extends State<statusText> { | ||||
| class _StatusTextState extends State<StatusText> { | ||||
|   int statusIndex = 0; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     Future.delayed(Duration(seconds: 5), () { | ||||
|     Future.delayed(const Duration(seconds: 5), () { | ||||
|       setState(() { | ||||
|         statusIndex = (statusIndex + 1) % statusTexts.length; | ||||
|       }); | ||||
| @@ -159,4 +159,3 @@ class _AnimatedGradientTextState extends State<AnimatedGradientText> with Single | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,13 +1,14 @@ | ||||
| import 'dart:collection'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:google_maps_flutter/google_maps_flutter.dart'; | ||||
| import 'package:widget_to_marker/widget_to_marker.dart'; | ||||
|  | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/modules/landmark_map_marker.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:google_maps_flutter/google_maps_flutter.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:widget_to_marker/widget_to_marker.dart'; | ||||
| import 'package:anyway/modules/landmark_map_marker.dart'; | ||||
|  | ||||
|  | ||||
| class CurrentTripMap extends StatefulWidget { | ||||
|   final Trip? trip; | ||||
| @@ -60,25 +61,29 @@ class _CurrentTripMapState extends State<CurrentTripMap> { | ||||
|   } | ||||
|  | ||||
|   void setMapMarkers() async { | ||||
|     List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? []; | ||||
|     Set<Marker> markers = <Marker>{}; | ||||
|     Iterator<(int, Landmark)> it = (widget.trip?.landmarks.toList() ?? []).indexed.iterator; | ||||
|  | ||||
|     for (int i = 0; i < landmarks.length; i++) { | ||||
|       Landmark landmark = landmarks[i]; | ||||
|     while (it.moveNext()) { | ||||
|       int i = it.current.$1; | ||||
|       Landmark landmark = it.current.$2; | ||||
|  | ||||
|       MarkerId markerId = MarkerId("${landmark.uuid} - ${landmark.visited}"); | ||||
|       List<double> location = landmark.location; | ||||
|       Marker marker = Marker( | ||||
|         markerId: MarkerId(landmark.uuid), | ||||
|         position: LatLng(location[0], location[1]), | ||||
|         icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor( | ||||
|           logicalSize: const Size(150, 150), | ||||
|           imageSize: const Size(150, 150), | ||||
|         ), | ||||
|       ); | ||||
|       markers.add(marker); | ||||
|       // only create a new marker, if there is no marker for this landmark | ||||
|       if (!mapMarkers.any((Marker marker) => marker.markerId == markerId)) { | ||||
|         Marker marker = Marker( | ||||
|           markerId: markerId, | ||||
|           position: LatLng(location[0], location[1]), | ||||
|           icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor( | ||||
|             logicalSize: const Size(150, 150), | ||||
|             imageSize: const Size(150, 150), | ||||
|           ) | ||||
|         ); | ||||
|         setState(() { | ||||
|           mapMarkers.add(marker); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|     setState(() { | ||||
|       mapMarkers = markers; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   void setMapRoute() async { | ||||
| @@ -98,8 +103,8 @@ class _CurrentTripMapState extends State<CurrentTripMap> { | ||||
|         Polyline stepLine = Polyline( | ||||
|           polylineId: PolylineId('step-${landmark.uuid}'), | ||||
|           points: step, | ||||
|           color: landmark.visited ? Colors.grey : PRIMARY_COLOR, | ||||
|           width: 5, | ||||
|           color: landmark.visited || (landmark.next?.visited ?? false) ? Colors.grey : PRIMARY_COLOR, | ||||
|           width: 5 | ||||
|         ); | ||||
|         polyLines.add(stepLine); | ||||
|       } | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| 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/constants.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
|  | ||||
| import 'package:anyway/modules/current_trip_error_message.dart'; | ||||
| import 'package:anyway/modules/current_trip_loading_indicator.dart'; | ||||
| import 'package:anyway/modules/current_trip_summary.dart'; | ||||
| import 'package:anyway/modules/current_trip_save_button.dart'; | ||||
| import 'package:anyway/modules/current_trip_landmarks_list.dart'; | ||||
| @@ -74,20 +76,21 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> { | ||||
|                   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), | ||||
|                       if (widget.trip.landmarks.where((Landmark landmark) => landmark.visited).isNotEmpty) | ||||
|                         ExpansionTile( | ||||
|                           leading: const Icon(Icons.location_on), | ||||
|                           title: const 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), | ||||
|                           ), | ||||
|                         ), | ||||
|                         shape: RoundedRectangleBorder( | ||||
|                           borderRadius: BorderRadius.circular(10), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ), | ||||
| @@ -108,4 +111,4 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> { | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
| }   | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:auto_size_text/auto_size_text.dart'; | ||||
|  | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:auto_size_text/auto_size_text.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
|  | ||||
| class saveButton extends StatefulWidget { | ||||
| @@ -52,4 +52,3 @@ class _saveButtonState extends State<saveButton> { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
|  | ||||
| class CurrentTripSummary extends StatefulWidget { | ||||
|   final Trip trip; | ||||
|   const CurrentTripSummary({ | ||||
| @@ -16,22 +17,22 @@ class _CurrentTripSummaryState extends State<CurrentTripSummary> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Padding( | ||||
|       padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), | ||||
|       padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), | ||||
|       child: Row( | ||||
|         mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|         children: [ | ||||
|           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,), | ||||
|               const Icon(Icons.flag, size: 20), | ||||
|               const 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,), | ||||
|               const Icon(Icons.hourglass_bottom_rounded, size: 20), | ||||
|               const Padding(padding: EdgeInsets.only(right: 10)), | ||||
|               Text('Duration: ${widget.trip.totalTime} minutes', style: Theme.of(context).textTheme.bodyLarge), | ||||
|             ] | ||||
|           ), | ||||
|         ], | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
|  | ||||
| Future<void> helpDialog(BuildContext context, String title, String content) { | ||||
|   return showDialog<void>( | ||||
|     context: context, | ||||
|   | ||||
| @@ -1,12 +1,15 @@ | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
|  | ||||
| import 'package:anyway/constants.dart'; | ||||
|  | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
|  | ||||
|  | ||||
|  | ||||
| class LandmarkCard extends StatefulWidget { | ||||
|   final Landmark landmark; | ||||
|   final Trip parentTrip; | ||||
| @@ -23,20 +26,11 @@ class LandmarkCard extends StatefulWidget { | ||||
|  | ||||
| class _LandmarkCardState extends State<LandmarkCard> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (widget.landmark.type == typeStart || widget.landmark.type == typeFinish) { | ||||
|       return TextButton.icon( | ||||
|         onPressed: () {}, | ||||
|         icon: widget.landmark.type.icon, | ||||
|         label: Text(widget.landmark.name), | ||||
|       ); | ||||
|  | ||||
|     } | ||||
|  | ||||
|   Widget build(BuildContext context) {       | ||||
|     return Container( | ||||
|       constraints: BoxConstraints( | ||||
|         minHeight: 50, | ||||
|         maxHeight: 200, | ||||
|         // express the max height in terms text lines | ||||
|         maxHeight: 7 * (Theme.of(context).textTheme.titleMedium!.fontSize! + 10), | ||||
|       ), | ||||
|       child: Card( | ||||
|         shape: RoundedRectangleBorder( | ||||
| @@ -79,23 +73,23 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|  | ||||
|                   Container( | ||||
|                     color: PRIMARY_COLOR, | ||||
|                     child: Center( | ||||
|                       child: Padding( | ||||
|                         padding: EdgeInsets.all(5), | ||||
|                         child: Row( | ||||
|                           mainAxisAlignment: MainAxisAlignment.center, | ||||
|                           spacing: 5, | ||||
|                           children: [ | ||||
|                             Icon(widget.landmark.type.icon.icon, size: 16), | ||||
|                             Text(widget.landmark.type.name, style: TextStyle(fontWeight: FontWeight.bold)), | ||||
|                           ], | ||||
|                   if (widget.landmark.type != typeStart && widget.landmark.type != typeFinish) | ||||
|                     Container( | ||||
|                       color: PRIMARY_COLOR, | ||||
|                       child: Center( | ||||
|                         child: Padding( | ||||
|                           padding: EdgeInsets.all(5), | ||||
|                           child: Row( | ||||
|                             mainAxisAlignment: MainAxisAlignment.center, | ||||
|                             spacing: 5, | ||||
|                             children: [ | ||||
|                               Icon(Icons.timer_outlined, size: 16), | ||||
|                               Text("${widget.landmark.duration?.inMinutes} minutes"), | ||||
|                             ], | ||||
|                           ) | ||||
|                         ) | ||||
|                       ) | ||||
|                     ), | ||||
|                   ) | ||||
|                       ), | ||||
|                     ) | ||||
|                 ], | ||||
|               ) | ||||
|             ), | ||||
| @@ -133,12 +127,6 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|                         // show the type, the website, and the wikipedia link as buttons/labels in a row | ||||
|                         children: [ | ||||
|                           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) | ||||
|                             websiteButton(), | ||||
|                            | ||||
| @@ -172,33 +160,35 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|       // open a browser with the website link | ||||
|       await launchUrl(Uri.parse(widget.landmark.websiteURL!)); | ||||
|     }, | ||||
|     icon: Icon(Icons.link), | ||||
|     label: Text('Website'), | ||||
|     icon: const Icon(Icons.link), | ||||
|     label: const Text('Website'), | ||||
|   ); | ||||
|  | ||||
|  | ||||
|   Widget optionsButton () => PopupMenuButton( | ||||
|     icon: Icon(Icons.settings), | ||||
|     icon: const Icon(Icons.settings), | ||||
|     style: TextButtonTheme.of(context).style, | ||||
|     itemBuilder: (context) => [ | ||||
|       PopupMenuItem( | ||||
|         child: ListTile( | ||||
|           leading: Icon(Icons.delete), | ||||
|           title: Text('Delete'), | ||||
|           leading: const Icon(Icons.delete), | ||||
|           title: const Text('Delete'), | ||||
|           onTap: () async { | ||||
|             widget.parentTrip.removeLandmark(widget.landmark); | ||||
|             rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|               SnackBar(content: Text("We won't show ${widget.landmark.name} again")) | ||||
|               SnackBar(content: Text("${widget.landmark.name} won't be shown again")) | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|       ), | ||||
|       PopupMenuItem( | ||||
|         child: ListTile( | ||||
|           leading: Icon(Icons.star), | ||||
|           title: Text('Favorite'), | ||||
|           leading: const Icon(Icons.star), | ||||
|           title: const Text('Favorite'), | ||||
|           onTap: () async { | ||||
|             // delete the landmark | ||||
|             // await deleteLandmark(widget.landmark); | ||||
|             rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|               SnackBar(content: Text("Not implemented yet")) | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|       ), | ||||
|   | ||||
| @@ -46,11 +46,11 @@ class _NewTripButtonState extends State<NewTripButton> { | ||||
|     UserPreferences preferences = widget.preferences; | ||||
|     if (preferences.nature.value == 0 && preferences.shopping.value == 0 && preferences.sightseeing.value == 0){ | ||||
|       rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|         SnackBar(content: Text("Please specify at least one preference")) | ||||
|         const SnackBar(content: Text("Please specify at least one preference")) | ||||
|       ); | ||||
|     } else if (preferences.maxTime.value == 0){ | ||||
|       rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|         SnackBar(content: Text("Please choose a longer duration")) | ||||
|         const SnackBar(content: Text("Please choose a longer duration")) | ||||
|       ); | ||||
|     } else { | ||||
|       Trip trip = widget.trip; | ||||
| @@ -63,4 +63,3 @@ class _NewTripButtonState extends State<NewTripButton> { | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| // A map that allows the user to select a location for a new trip. | ||||
| import 'dart:developer'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:google_maps_flutter/google_maps_flutter.dart'; | ||||
| import 'package:widget_to_marker/widget_to_marker.dart'; | ||||
|  | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/modules/landmark_map_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:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:widget_to_marker/widget_to_marker.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/modules/landmark_map_marker.dart'; | ||||
|  | ||||
|  | ||||
| class NewTripMap extends StatefulWidget { | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/modules/map_chooser.dart'; | ||||
|  | ||||
|  | ||||
| class StepBetweenLandmarks extends StatefulWidget { | ||||
|   final Landmark current; | ||||
|   final Landmark next; | ||||
| @@ -19,12 +21,15 @@ class StepBetweenLandmarks extends StatefulWidget { | ||||
| class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     int timeRounded = 5 * ((widget.current.tripTime?.inMinutes ?? 0) ~/ 5); | ||||
|     // ~/ is integer division (rounding) | ||||
|     int? time = widget.current.tripTime?.inMinutes; | ||||
|     if (time != null && time < 1) { | ||||
|       time = 1; | ||||
|     } | ||||
|  | ||||
|     return Container( | ||||
|       margin: EdgeInsets.all(10), | ||||
|       padding: EdgeInsets.all(10), | ||||
|       decoration: BoxDecoration( | ||||
|       margin: const EdgeInsets.all(10), | ||||
|       padding: const EdgeInsets.all(10), | ||||
|       decoration: const BoxDecoration( | ||||
|         border: Border( | ||||
|           left: BorderSide(width: 3.0, color: Colors.black), | ||||
|         ), | ||||
| @@ -33,21 +38,22 @@ class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> { | ||||
|         children: [ | ||||
|           Column( | ||||
|             children: [ | ||||
|               Icon(Icons.directions_walk), | ||||
|               Text("~$timeRounded min", style: TextStyle(fontSize: 10)), | ||||
|               const Icon(Icons.directions_walk), | ||||
|               Text( | ||||
|                 time == null ? "" : "About $time min", | ||||
|                 style: const TextStyle(fontSize: 10) | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           Spacer(), | ||||
|           ElevatedButton( | ||||
|  | ||||
|           const Spacer(), | ||||
|  | ||||
|           ElevatedButton.icon( | ||||
|             onPressed: () async { | ||||
|               showMapChooser(context, widget.current, widget.next); | ||||
|             }, | ||||
|             child: Row( | ||||
|               children: [ | ||||
|                 Icon(Icons.directions), | ||||
|                 Text("Directions"), | ||||
|               ], | ||||
|             ), | ||||
|             icon: const Icon(Icons.directions), | ||||
|             label: const Text("Directions"), | ||||
|           ) | ||||
|         ], | ||||
|       ), | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/pages/base_page.dart'; | ||||
| import 'package:anyway/layouts/scaffold.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:sliding_up_panel/sliding_up_panel.dart'; | ||||
|  | ||||
| @@ -28,12 +28,13 @@ class TripPage extends StatefulWidget { | ||||
|  | ||||
|  | ||||
|  | ||||
| class _TripPageState extends State<TripPage> { | ||||
| class _TripPageState extends State<TripPage> with ScaffoldLayout{ | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return BasePage( | ||||
|       mainScreen: SlidingUpPanel( | ||||
|     return mainScaffold( | ||||
|       context, | ||||
|       child: SlidingUpPanel( | ||||
|         // use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview | ||||
|         panelBuilder: (scrollcontroller) => CurrentTripPanel(controller: scrollcontroller, trip: widget.trip), | ||||
|         // using collapsed and panelBuilder seems to show both at the same time, so we include the greeter in the panelBuilder | ||||
| @@ -58,9 +59,13 @@ class _TripPageState extends State<TripPage> { | ||||
|       title: FutureBuilder( | ||||
|         future: widget.trip.cityName, | ||||
|         builder: (context, snapshot) => Text( | ||||
|           'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}', | ||||
|           'Trip to ${snapshot.hasData ? snapshot.data! : "..."}', | ||||
|         ) | ||||
|       ), | ||||
|       helpTexts: [ | ||||
|         'Current trip', | ||||
|         'You can see and edit your current trip here. Swipe up from the bottom to see a detailed view of the recommendations.' | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import 'package:anyway/layouts/scaffold.dart'; | ||||
| import 'package:anyway/modules/new_trip_options_button.dart'; | ||||
| import 'package:anyway/pages/base_page.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import "package:anyway/structs/trip.dart"; | ||||
| @@ -14,7 +14,7 @@ class NewTripPage extends StatefulWidget { | ||||
|   _NewTripPageState createState() => _NewTripPageState(); | ||||
| } | ||||
|  | ||||
| class _NewTripPageState extends State<NewTripPage> { | ||||
| class _NewTripPageState extends State<NewTripPage> with ScaffoldLayout { | ||||
|   final TextEditingController latController = TextEditingController(); | ||||
|   final TextEditingController lonController = TextEditingController(); | ||||
|   Trip trip = Trip(); | ||||
| @@ -23,8 +23,9 @@ class _NewTripPageState extends State<NewTripPage> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     // floating search bar and map as a background | ||||
|     return BasePage( | ||||
|       mainScreen: Scaffold( | ||||
|     return mainScaffold( | ||||
|       context, | ||||
|       child: Scaffold( | ||||
|         body: Stack( | ||||
|           children: [ | ||||
|             NewTripMap(trip), | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import 'package:anyway/layouts/scaffold.dart'; | ||||
| import 'package:anyway/modules/new_trip_button.dart'; | ||||
| import 'package:anyway/pages/base_page.dart'; | ||||
| import 'package:anyway/structs/preferences.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| @@ -15,13 +15,14 @@ class NewTripPreferencesPage extends StatefulWidget { | ||||
|   _NewTripPreferencesPageState createState() => _NewTripPreferencesPageState(); | ||||
| } | ||||
|  | ||||
| class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> { | ||||
| class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> with ScaffoldLayout { | ||||
|   UserPreferences preferences = UserPreferences(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return BasePage( | ||||
|       mainScreen: Scaffold( | ||||
|     return mainScaffold( | ||||
|       context, | ||||
|       child: Scaffold( | ||||
|         body: ListView( | ||||
|           children: [ | ||||
|             // Center( | ||||
| @@ -41,23 +42,22 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> { | ||||
|             //   ) | ||||
|             // ), | ||||
|  | ||||
|             Center( | ||||
|               child: Padding( | ||||
|               padding: EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 0), | ||||
|                 child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18)) | ||||
|               ), | ||||
|           Center( | ||||
|             child: Padding( | ||||
|             padding: EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 0), | ||||
|               child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18)) | ||||
|             ), | ||||
|           ), | ||||
|  | ||||
|             Divider(indent: 25, endIndent: 25, height: 50), | ||||
|           Divider(indent: 25, endIndent: 25, height: 50), | ||||
|  | ||||
|             durationPicker(preferences.maxTime), | ||||
|           durationPicker(preferences.maxTime), | ||||
|  | ||||
|             preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]), | ||||
|           ] | ||||
|         ), | ||||
|         floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences), | ||||
|           preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]), | ||||
|         ] | ||||
|       ), | ||||
|       floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences), | ||||
|       ), | ||||
|        | ||||
|       title: FutureBuilder( | ||||
|         future: widget.trip.cityName, | ||||
|         builder: (context, snapshot) => Text( | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/layouts/scaffold.dart'; | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:anyway/pages/base_page.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:permission_handler/permission_handler.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| @@ -14,42 +14,41 @@ class SettingsPage extends StatefulWidget { | ||||
|   _SettingsPageState createState() => _SettingsPageState(); | ||||
| } | ||||
|  | ||||
| class _SettingsPageState extends State<SettingsPage> { | ||||
| class _SettingsPageState extends State<SettingsPage> with ScaffoldLayout { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return BasePage( | ||||
|       mainScreen: ListView( | ||||
|         padding: EdgeInsets.all(15), | ||||
|         children: [ | ||||
|           // First a round, centered image | ||||
|           Center( | ||||
|             child: CircleAvatar( | ||||
|               radius: 75, | ||||
|               child: Icon(Icons.settings, size: 100), | ||||
|             ) | ||||
|           ), | ||||
|           Center( | ||||
|             child: Text('Global settings', style: TextStyle(fontSize: 24)) | ||||
|           ), | ||||
|   Widget build (BuildContext context) => mainScaffold( | ||||
|     context, | ||||
|     child: ListView( | ||||
|       padding: EdgeInsets.all(15), | ||||
|       children: [ | ||||
|         // First a round, centered image | ||||
|         Center( | ||||
|           child: CircleAvatar( | ||||
|             radius: 75, | ||||
|             child: Icon(Icons.settings, size: 100), | ||||
|           ) | ||||
|         ), | ||||
|         Center( | ||||
|           child: Text('Global settings', style: TextStyle(fontSize: 24)) | ||||
|         ), | ||||
|  | ||||
|           Divider(indent: 25, endIndent: 25, height: 50), | ||||
|         Divider(indent: 25, endIndent: 25, height: 50), | ||||
|  | ||||
|           darkMode(), | ||||
|           setLocationUsage(), | ||||
|           setDebugMode(), | ||||
|         darkMode(), | ||||
|         setLocationUsage(), | ||||
|         setDebugMode(), | ||||
|  | ||||
|           Divider(indent: 25, endIndent: 25, height: 50), | ||||
|         Divider(indent: 25, endIndent: 25, height: 50), | ||||
|  | ||||
|           privacyInfo(), | ||||
|         ] | ||||
|       ), | ||||
|       title: Text('Settings'), | ||||
|       helpTexts: [ | ||||
|         'Settings', | ||||
|         'Preferences set in this page are global and will affect the entire application.' | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|         privacyInfo(), | ||||
|       ] | ||||
|     ), | ||||
|     title: Text('Settings'), | ||||
|     helpTexts: [ | ||||
|       'Settings', | ||||
|       'Preferences set in this page are global and will affect the entire application.' | ||||
|     ], | ||||
|   ); | ||||
|  | ||||
|   Widget setDebugMode() { | ||||
|     return Row( | ||||
|   | ||||
| @@ -70,10 +70,10 @@ final class Landmark extends LinkedListEntry<Landmark>{ | ||||
|       final websiteURL = json['website_url'] as String?; | ||||
|       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'] ?? false as bool; | ||||
|       var duration = Duration(minutes: json['duration']); | ||||
|       final visited = json['visited'] ?? false; | ||||
|       var tripTime = Duration(minutes: json['time_to_reach_next'] ?? 0) as Duration?; | ||||
|        | ||||
|  | ||||
|       return Landmark( | ||||
|         uuid: uuid, | ||||
|         name: name, | ||||
|   | ||||
| @@ -29,6 +29,18 @@ class Trip with ChangeNotifier { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<int> landmarkPosition (Landmark landmark) async { | ||||
|     int i = 0; | ||||
|     for (Landmark l in landmarks) { | ||||
|       if (l.uuid == landmark.uuid) { | ||||
|         return i; | ||||
|       } else if (l.type != typeStart && l.type != typeFinish) { | ||||
|       i++; | ||||
|       } | ||||
|     } | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   Trip({ | ||||
|     this.uuid = 'pending', | ||||
|   | ||||
| @@ -1,33 +1,33 @@ | ||||
| import "dart:convert"; | ||||
| import "dart:developer"; | ||||
| import "package:anyway/utils/load_landmark_image.dart"; | ||||
| import 'package:dio/dio.dart'; | ||||
|  | ||||
| import 'package:anyway/constants.dart'; | ||||
| import "package:anyway/utils/load_landmark_image.dart"; | ||||
| import "package:anyway/structs/landmark.dart"; | ||||
| import "package:anyway/structs/trip.dart"; | ||||
| import "package:anyway/structs/preferences.dart"; | ||||
|  | ||||
|  | ||||
| Dio dio = Dio( | ||||
|     BaseOptions( | ||||
|       baseUrl: API_URL_BASE, | ||||
|       connectTimeout: const Duration(seconds: 5), | ||||
|       receiveTimeout: const Duration(seconds: 120), | ||||
|       // also accept 500 errors, since we cannot rule out that the server is at fault. We still want to gracefully handle these errors | ||||
|       validateStatus: (status) => status! <= 500, | ||||
|       receiveDataWhenStatusError: true, | ||||
|       // api is notoriously slow | ||||
|       // headers: { | ||||
|       //   HttpHeaders.userAgentHeader: 'dio', | ||||
|       //   'api': '1.0.0', | ||||
|       // }, | ||||
|       contentType: Headers.jsonContentType, | ||||
|       responseType: ResponseType.json, | ||||
|          | ||||
|   BaseOptions( | ||||
|     baseUrl: API_URL_BASE, | ||||
|     connectTimeout: const Duration(seconds: 5), | ||||
|     receiveTimeout: const Duration(seconds: 120), | ||||
|     // also accept 500 errors, since we cannot rule out that the server is at fault. We still want to gracefully handle these errors | ||||
|     validateStatus: (status) => status! <= 500, | ||||
|     receiveDataWhenStatusError: true, | ||||
|     // api is notoriously slow | ||||
|     // headers: { | ||||
|     //   HttpHeaders.userAgentHeader: 'dio', | ||||
|     //   'api': '1.0.0', | ||||
|     // }, | ||||
|     contentType: Headers.jsonContentType, | ||||
|     responseType: ResponseType.json,     | ||||
|   ), | ||||
| ); | ||||
|  | ||||
|  | ||||
| fetchTrip( | ||||
|   Trip trip, | ||||
|   UserPreferences preferences, | ||||
|   | ||||
| @@ -1,11 +1,14 @@ | ||||
| import 'package:anyway/pages/current_trip.dart'; | ||||
| import 'package:anyway/pages/onboarding.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:anyway/utils/load_trips.dart'; | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:anyway/utils/load_trips.dart'; | ||||
| import 'package:anyway/pages/current_trip.dart'; | ||||
| import 'package:anyway/pages/onboarding.dart'; | ||||
|  | ||||
|  | ||||
| Widget getFirstPage() { | ||||
|   SavedTrips trips = SavedTrips(); | ||||
|   SavedTrips trips = savedTrips; | ||||
|   trips.loadTrips(); | ||||
|  | ||||
|   return ListenableBuilder( | ||||
| @@ -15,7 +18,7 @@ Widget getFirstPage() { | ||||
|       if (items.isNotEmpty) { | ||||
|         return TripPage(trip: items[0]); | ||||
|       } else { | ||||
|         return OnboardingPage(); | ||||
|         return const OnboardingPage(); | ||||
|       } | ||||
|     } | ||||
|   ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user