reworked page layout inheritence

This commit is contained in:
Remy Moll 2025-02-15 19:36:41 +01:00
parent 8f6dfd404d
commit 56c55883ea
21 changed files with 278 additions and 278 deletions

View File

@ -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:flutter/material.dart';
import 'package:anyway/constants.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/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/onboarding.dart';
import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/pages/settings.dart';
import 'package:anyway/pages/new_trip_location.dart';
mixin ScaffoldLayout<T extends StatefulWidget> on State<T> {
Widget mainScaffold(
// BasePage is the scaffold that holds a child page and a side drawer BuildContext context,
// The side drawer is the main way to switch between pages {
Widget child = const Text("emptiness"),
class BasePage extends StatefulWidget { Widget title = const Text(APP_NAME),
final Widget mainScreen; List<String> helpTexts = const []
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();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: widget.title, title: title,
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.help), icon: const Icon(Icons.help),
tooltip: 'Help', tooltip: 'Help',
onPressed: () { onPressed: () {
if (widget.helpTexts.isNotEmpty) { if (helpTexts.isNotEmpty) {
helpDialog(context, widget.helpTexts[0], widget.helpTexts[1]); helpDialog(context, helpTexts[0], helpTexts[1]);
} }
} }
), ),
], ],
), ),
body: Center(child: widget.mainScreen), body: Center(child: child),
drawer: Drawer( drawer: Drawer(
child: Column( child: Column(
children: [ children: [
Container( Container(
decoration: BoxDecoration( decoration: const BoxDecoration(
gradient: APP_GRADIENT, gradient: APP_GRADIENT,
), ),
height: 150, height: 150,
child: Center( child: const Center(
child: Text( child: Text(
APP_NAME, APP_NAME,
style: TextStyle( style: TextStyle(
@ -81,8 +60,7 @@ class _BasePageState extends State<BasePage> {
ListTile( ListTile(
title: const Text('Your Trips'), title: const Text('Your Trips'),
leading: const Icon(Icons.map), leading: const Icon(Icons.map),
// TODO: this is not working! selected: widget is TripPage,
selected: widget.mainScreen is TripPage,
onTap: () {}, onTap: () {},
trailing: ElevatedButton( trailing: ElevatedButton(
onPressed: () { onPressed: () {
@ -111,13 +89,12 @@ class _BasePageState extends State<BasePage> {
const Divider(indent: 10, endIndent: 10), const Divider(indent: 10, endIndent: 10),
ListTile( ListTile(
title: const Text('How to use'), title: const Text('How to use'),
leading: Icon(Icons.help), leading: const Icon(Icons.help),
// TODO: this is not working! selected: widget is OnboardingPage,
selected: widget.mainScreen is OnboardingPage,
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => OnboardingPage() builder: (context) => const OnboardingPage()
) )
); );
}, },
@ -127,8 +104,7 @@ class _BasePageState extends State<BasePage> {
ListTile( ListTile(
title: const Text('Settings'), title: const Text('Settings'),
leading: const Icon(Icons.settings), leading: const Icon(Icons.settings),
// TODO: this is not working! selected: widget is SettingsPage,
selected: widget.mainScreen is SettingsPage,
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(

View File

@ -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/get_first_page.dart';
import 'package:anyway/utils/load_trips.dart'; import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart';
import 'package:anyway/constants.dart';
void main() => runApp(const App()); void main() => runApp(const App());
// Some global variables
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
final SavedTrips savedTrips = SavedTrips(); final SavedTrips savedTrips = SavedTrips();
// the list of saved trips is then populated implicitly by getFirstPage()
class App extends StatelessWidget { class App extends StatelessWidget {
const App({super.key}); const App({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => MaterialApp(
return MaterialApp( title: APP_NAME,
title: APP_NAME, home: getFirstPage(),
home: getFirstPage(), theme: APP_THEME,
theme: APP_THEME, scaffoldMessengerKey: rootScaffoldMessengerKey
scaffoldMessengerKey: rootScaffoldMessengerKey );
);
}
} }

View File

@ -1,10 +1,9 @@
import 'dart:developer';
import 'package:anyway/modules/step_between_landmarks.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:anyway/modules/landmark_card.dart';
import 'package:anyway/structs/landmark.dart'; import 'package:anyway/structs/landmark.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
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 // 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; return children;
} }

View File

@ -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 // 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)), Center(child: loadingText(widget.trip)),
// As a gimmick, and a way to show that the app is still working, show a few loading dots // 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, alignment: Alignment.bottomCenter,
child: statusText(), child: StatusText(),
) )
], ],
); );
} }
// automatically cycle through the greeter texts // automatically cycle through the greeter texts
class statusText extends StatefulWidget { class StatusText extends StatefulWidget {
const statusText({Key? key}) : super(key: key); const StatusText({Key? key}) : super(key: key);
@override @override
_statusTextState createState() => _statusTextState(); _StatusTextState createState() => _StatusTextState();
} }
class _statusTextState extends State<statusText> { class _StatusTextState extends State<StatusText> {
int statusIndex = 0; int statusIndex = 0;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Future.delayed(Duration(seconds: 5), () { Future.delayed(const Duration(seconds: 5), () {
setState(() { setState(() {
statusIndex = (statusIndex + 1) % statusTexts.length; statusIndex = (statusIndex + 1) % statusTexts.length;
}); });
@ -159,4 +159,3 @@ class _AnimatedGradientTextState extends State<AnimatedGradientText> with Single
); );
} }
} }

View File

@ -1,13 +1,14 @@
import 'dart:collection'; 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/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/landmark.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:anyway/modules/landmark_map_marker.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:widget_to_marker/widget_to_marker.dart';
class CurrentTripMap extends StatefulWidget { class CurrentTripMap extends StatefulWidget {
final Trip? trip; final Trip? trip;
@ -60,25 +61,29 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
} }
void setMapMarkers() async { void setMapMarkers() async {
List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? []; Iterator<(int, Landmark)> it = (widget.trip?.landmarks.toList() ?? []).indexed.iterator;
Set<Marker> markers = <Marker>{};
for (int i = 0; i < landmarks.length; i++) { while (it.moveNext()) {
Landmark landmark = landmarks[i]; int i = it.current.$1;
Landmark landmark = it.current.$2;
MarkerId markerId = MarkerId("${landmark.uuid} - ${landmark.visited}");
List<double> location = landmark.location; List<double> location = landmark.location;
Marker marker = Marker( // only create a new marker, if there is no marker for this landmark
markerId: MarkerId(landmark.uuid), if (!mapMarkers.any((Marker marker) => marker.markerId == markerId)) {
position: LatLng(location[0], location[1]), Marker marker = Marker(
icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor( markerId: markerId,
logicalSize: const Size(150, 150), position: LatLng(location[0], location[1]),
imageSize: const Size(150, 150), icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor(
), logicalSize: const Size(150, 150),
); imageSize: const Size(150, 150),
markers.add(marker); )
);
setState(() {
mapMarkers.add(marker);
});
}
} }
setState(() {
mapMarkers = markers;
});
} }
void setMapRoute() async { void setMapRoute() async {
@ -98,8 +103,8 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
Polyline stepLine = Polyline( Polyline stepLine = Polyline(
polylineId: PolylineId('step-${landmark.uuid}'), polylineId: PolylineId('step-${landmark.uuid}'),
points: step, points: step,
color: landmark.visited ? Colors.grey : PRIMARY_COLOR, color: landmark.visited || (landmark.next?.visited ?? false) ? Colors.grey : PRIMARY_COLOR,
width: 5, width: 5
); );
polyLines.add(stepLine); polyLines.add(stepLine);
} }

View File

@ -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:flutter/material.dart';
import 'package:anyway/constants.dart';
import 'package:anyway/structs/landmark.dart';
import 'package:anyway/structs/trip.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_summary.dart';
import 'package:anyway/modules/current_trip_save_button.dart'; import 'package:anyway/modules/current_trip_save_button.dart';
import 'package:anyway/modules/current_trip_landmarks_list.dart'; import 'package:anyway/modules/current_trip_landmarks_list.dart';
@ -74,20 +76,21 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
child: Column( child: Column(
children: [ children: [
CurrentTripSummary(trip: widget.trip), CurrentTripSummary(trip: widget.trip),
ExpansionTile( if (widget.trip.landmarks.where((Landmark landmark) => landmark.visited).isNotEmpty)
leading: Icon(Icons.location_on), ExpansionTile(
title: Text('Visited Landmarks (tap to expand)'), leading: const Icon(Icons.location_on),
children: [ title: const Text('Visited Landmarks (tap to expand)'),
...landmarksList(widget.trip, selector: (Landmark landmark) => landmark.visited), children: [
], ...landmarksList(widget.trip, selector: (Landmark landmark) => landmark.visited),
visualDensity: VisualDensity.compact, ],
collapsedShape: RoundedRectangleBorder( visualDensity: VisualDensity.compact,
borderRadius: BorderRadius.circular(10), 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> {
} }
); );
} }
} }

View File

@ -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/main.dart';
import 'package:anyway/structs/trip.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 { class saveButton extends StatefulWidget {
@ -52,4 +52,3 @@ class _saveButtonState extends State<saveButton> {
); );
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class CurrentTripSummary extends StatefulWidget { class CurrentTripSummary extends StatefulWidget {
final Trip trip; final Trip trip;
const CurrentTripSummary({ const CurrentTripSummary({
@ -16,22 +17,22 @@ class _CurrentTripSummaryState extends State<CurrentTripSummary> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Row(
children: [ children: [
Icon(Icons.flag, size: 20), const Icon(Icons.flag, size: 20),
Padding(padding: EdgeInsets.only(right: 10)), const Padding(padding: EdgeInsets.only(right: 10)),
Text('Stops: ${widget.trip.landmarks.length}', style: Theme.of(context).textTheme.bodyLarge,), Text('Stops: ${widget.trip.landmarks.length}', style: Theme.of(context).textTheme.bodyLarge),
] ]
), ),
Row( Row(
children: [ children: [
Icon(Icons.hourglass_bottom_rounded, size: 20), const Icon(Icons.hourglass_bottom_rounded, size: 20),
Padding(padding: EdgeInsets.only(right: 10)), const Padding(padding: EdgeInsets.only(right: 10)),
Text('Duration: ${widget.trip.totalTime} minutes', style: Theme.of(context).textTheme.bodyLarge,), Text('Duration: ${widget.trip.totalTime} minutes', style: Theme.of(context).textTheme.bodyLarge),
] ]
), ),
], ],

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Future<void> helpDialog(BuildContext context, String title, String content) { Future<void> helpDialog(BuildContext context, String title, String content) {
return showDialog<void>( return showDialog<void>(
context: context, context: context,

View File

@ -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:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.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'; import 'package:anyway/structs/landmark.dart';
class LandmarkCard extends StatefulWidget { class LandmarkCard extends StatefulWidget {
final Landmark landmark; final Landmark landmark;
final Trip parentTrip; final Trip parentTrip;
@ -23,20 +26,11 @@ class LandmarkCard extends StatefulWidget {
class _LandmarkCardState extends State<LandmarkCard> { class _LandmarkCardState extends State<LandmarkCard> {
@override @override
Widget build(BuildContext context) { 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),
);
}
return Container( return Container(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: 50, // express the max height in terms text lines
maxHeight: 200, maxHeight: 7 * (Theme.of(context).textTheme.titleMedium!.fontSize! + 10),
), ),
child: Card( child: Card(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -79,23 +73,23 @@ class _LandmarkCardState extends State<LandmarkCard> {
), ),
), ),
), ),
if (widget.landmark.type != typeStart && widget.landmark.type != typeFinish)
Container( Container(
color: PRIMARY_COLOR, color: PRIMARY_COLOR,
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(5), padding: EdgeInsets.all(5),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
spacing: 5, spacing: 5,
children: [ children: [
Icon(widget.landmark.type.icon.icon, size: 16), Icon(Icons.timer_outlined, size: 16),
Text(widget.landmark.type.name, style: TextStyle(fontWeight: FontWeight.bold)), 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 // show the type, the website, and the wikipedia link as buttons/labels in a row
children: [ children: [
doneToggleButton(), 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) if (widget.landmark.websiteURL != null)
websiteButton(), websiteButton(),
@ -172,33 +160,35 @@ class _LandmarkCardState extends State<LandmarkCard> {
// open a browser with the website link // open a browser with the website link
await launchUrl(Uri.parse(widget.landmark.websiteURL!)); await launchUrl(Uri.parse(widget.landmark.websiteURL!));
}, },
icon: Icon(Icons.link), icon: const Icon(Icons.link),
label: Text('Website'), label: const Text('Website'),
); );
Widget optionsButton () => PopupMenuButton( Widget optionsButton () => PopupMenuButton(
icon: Icon(Icons.settings), icon: const Icon(Icons.settings),
style: TextButtonTheme.of(context).style, style: TextButtonTheme.of(context).style,
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
child: ListTile( child: ListTile(
leading: Icon(Icons.delete), leading: const Icon(Icons.delete),
title: Text('Delete'), title: const Text('Delete'),
onTap: () async { onTap: () async {
widget.parentTrip.removeLandmark(widget.landmark); widget.parentTrip.removeLandmark(widget.landmark);
rootScaffoldMessengerKey.currentState!.showSnackBar( 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( PopupMenuItem(
child: ListTile( child: ListTile(
leading: Icon(Icons.star), leading: const Icon(Icons.star),
title: Text('Favorite'), title: const Text('Favorite'),
onTap: () async { onTap: () async {
// delete the landmark rootScaffoldMessengerKey.currentState!.showSnackBar(
// await deleteLandmark(widget.landmark); SnackBar(content: Text("Not implemented yet"))
);
}, },
), ),
), ),

View File

@ -46,11 +46,11 @@ class _NewTripButtonState extends State<NewTripButton> {
UserPreferences preferences = widget.preferences; UserPreferences preferences = widget.preferences;
if (preferences.nature.value == 0 && preferences.shopping.value == 0 && preferences.sightseeing.value == 0){ if (preferences.nature.value == 0 && preferences.shopping.value == 0 && preferences.sightseeing.value == 0){
rootScaffoldMessengerKey.currentState!.showSnackBar( 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){ } else if (preferences.maxTime.value == 0){
rootScaffoldMessengerKey.currentState!.showSnackBar( rootScaffoldMessengerKey.currentState!.showSnackBar(
SnackBar(content: Text("Please choose a longer duration")) const SnackBar(content: Text("Please choose a longer duration"))
); );
} else { } else {
Trip trip = widget.trip; Trip trip = widget.trip;
@ -63,4 +63,3 @@ class _NewTripButtonState extends State<NewTripButton> {
} }
} }
} }

View File

@ -1,14 +1,14 @@
// A map that allows the user to select a location for a new trip. // 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/constants.dart';
import 'package:anyway/modules/landmark_map_marker.dart';
import 'package:anyway/structs/landmark.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:flutter/material.dart'; import 'package:anyway/structs/landmark.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:anyway/modules/landmark_map_marker.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:widget_to_marker/widget_to_marker.dart';
class NewTripMap extends StatefulWidget { class NewTripMap extends StatefulWidget {

View File

@ -1,7 +1,9 @@
import 'package:anyway/structs/landmark.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:anyway/structs/landmark.dart';
import 'package:anyway/modules/map_chooser.dart'; import 'package:anyway/modules/map_chooser.dart';
class StepBetweenLandmarks extends StatefulWidget { class StepBetweenLandmarks extends StatefulWidget {
final Landmark current; final Landmark current;
final Landmark next; final Landmark next;
@ -19,11 +21,15 @@ class StepBetweenLandmarks extends StatefulWidget {
class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> { class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
int time = widget.current.tripTime?.inMinutes ?? 0; int? time = widget.current.tripTime?.inMinutes;
if (time != null && time < 1) {
time = 1;
}
return Container( return Container(
margin: EdgeInsets.all(10), margin: const EdgeInsets.all(10),
padding: EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: const BoxDecoration(
border: Border( border: Border(
left: BorderSide(width: 3.0, color: Colors.black), left: BorderSide(width: 3.0, color: Colors.black),
), ),
@ -32,21 +38,22 @@ class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> {
children: [ children: [
Column( Column(
children: [ children: [
Icon(Icons.directions_walk), const Icon(Icons.directions_walk),
Text("$time min", style: TextStyle(fontSize: 10)), Text(
time == null ? "" : "About $time min",
style: const TextStyle(fontSize: 10)
),
], ],
), ),
Spacer(),
ElevatedButton( const Spacer(),
ElevatedButton.icon(
onPressed: () async { onPressed: () async {
showMapChooser(context, widget.current, widget.next); showMapChooser(context, widget.current, widget.next);
}, },
child: Row( icon: const Icon(Icons.directions),
children: [ label: const Text("Directions"),
Icon(Icons.directions),
Text("Directions"),
],
),
) )
], ],
), ),

View File

@ -1,5 +1,5 @@
import 'package:anyway/constants.dart'; 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:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BasePage( return mainScaffold(
mainScreen: SlidingUpPanel( context,
child: SlidingUpPanel(
// use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview // use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview
panelBuilder: (scrollcontroller) => CurrentTripPanel(controller: scrollcontroller, trip: widget.trip), 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 // 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( title: FutureBuilder(
future: widget.trip.cityName, future: widget.trip.cityName,
builder: (context, snapshot) => Text( 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.'
],
); );
} }
} }

View File

@ -1,5 +1,5 @@
import 'package:anyway/layouts/scaffold.dart';
import 'package:anyway/modules/new_trip_options_button.dart'; import 'package:anyway/modules/new_trip_options_button.dart';
import 'package:anyway/pages/base_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:anyway/structs/trip.dart"; import "package:anyway/structs/trip.dart";
@ -14,7 +14,7 @@ class NewTripPage extends StatefulWidget {
_NewTripPageState createState() => _NewTripPageState(); _NewTripPageState createState() => _NewTripPageState();
} }
class _NewTripPageState extends State<NewTripPage> { class _NewTripPageState extends State<NewTripPage> with ScaffoldLayout {
final TextEditingController latController = TextEditingController(); final TextEditingController latController = TextEditingController();
final TextEditingController lonController = TextEditingController(); final TextEditingController lonController = TextEditingController();
Trip trip = Trip(); Trip trip = Trip();
@ -23,8 +23,9 @@ class _NewTripPageState extends State<NewTripPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// floating search bar and map as a background // floating search bar and map as a background
return BasePage( return mainScaffold(
mainScreen: Scaffold( context,
child: Scaffold(
body: Stack( body: Stack(
children: [ children: [
NewTripMap(trip), NewTripMap(trip),

View File

@ -1,5 +1,5 @@
import 'package:anyway/layouts/scaffold.dart';
import 'package:anyway/modules/new_trip_button.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/preferences.dart';
import 'package:anyway/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -15,13 +15,14 @@ class NewTripPreferencesPage extends StatefulWidget {
_NewTripPreferencesPageState createState() => _NewTripPreferencesPageState(); _NewTripPreferencesPageState createState() => _NewTripPreferencesPageState();
} }
class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> { class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> with ScaffoldLayout {
UserPreferences preferences = UserPreferences(); UserPreferences preferences = UserPreferences();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BasePage( return mainScaffold(
mainScreen: Scaffold( context,
child: Scaffold(
body: ListView( body: ListView(
children: [ children: [
// Center( // Center(
@ -41,23 +42,22 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
// ) // )
// ), // ),
Center( Center(
child: Padding( child: Padding(
padding: EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 0), padding: EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 0),
child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18)) 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]), preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]),
] ]
), ),
floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences), floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences),
), ),
title: FutureBuilder( title: FutureBuilder(
future: widget.trip.cityName, future: widget.trip.cityName,
builder: (context, snapshot) => Text( builder: (context, snapshot) => Text(

View File

@ -1,6 +1,6 @@
import 'package:anyway/constants.dart'; import 'package:anyway/constants.dart';
import 'package:anyway/layouts/scaffold.dart';
import 'package:anyway/main.dart'; import 'package:anyway/main.dart';
import 'package:anyway/pages/base_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -14,42 +14,41 @@ class SettingsPage extends StatefulWidget {
_SettingsPageState createState() => _SettingsPageState(); _SettingsPageState createState() => _SettingsPageState();
} }
class _SettingsPageState extends State<SettingsPage> { class _SettingsPageState extends State<SettingsPage> with ScaffoldLayout {
@override @override
Widget build(BuildContext context) { Widget build (BuildContext context) => mainScaffold(
return BasePage( context,
mainScreen: ListView( child: ListView(
padding: EdgeInsets.all(15), padding: EdgeInsets.all(15),
children: [ children: [
// First a round, centered image // First a round, centered image
Center( Center(
child: CircleAvatar( child: CircleAvatar(
radius: 75, radius: 75,
child: Icon(Icons.settings, size: 100), child: Icon(Icons.settings, size: 100),
) )
), ),
Center( Center(
child: Text('Global settings', style: TextStyle(fontSize: 24)) child: Text('Global settings', style: TextStyle(fontSize: 24))
), ),
Divider(indent: 25, endIndent: 25, height: 50), Divider(indent: 25, endIndent: 25, height: 50),
darkMode(), darkMode(),
setLocationUsage(), setLocationUsage(),
setDebugMode(), setDebugMode(),
Divider(indent: 25, endIndent: 25, height: 50), Divider(indent: 25, endIndent: 25, height: 50),
privacyInfo(), privacyInfo(),
] ]
), ),
title: Text('Settings'), title: Text('Settings'),
helpTexts: [ helpTexts: [
'Settings', 'Settings',
'Preferences set in this page are global and will affect the entire application.' 'Preferences set in this page are global and will affect the entire application.'
], ],
); );
}
Widget setDebugMode() { Widget setDebugMode() {
return Row( return Row(

View File

@ -70,10 +70,10 @@ final class Landmark extends LinkedListEntry<Landmark>{
final websiteURL = json['website_url'] as String?; final websiteURL = json['website_url'] as String?;
final imageURL = json['image_url'] as String?; final imageURL = json['image_url'] as String?;
final description = json['description'] as String?; final description = json['description'] as String?;
var duration = Duration(minutes: json['duration'] ?? 0) as Duration?; var duration = Duration(minutes: json['duration']);
final visited = json['visited'] ?? false as bool; final visited = json['visited'] ?? false;
var tripTime = Duration(minutes: json['time_to_reach_next'] ?? 0) as Duration?; var tripTime = Duration(minutes: json['time_to_reach_next'] ?? 0) as Duration?;
return Landmark( return Landmark(
uuid: uuid, uuid: uuid,
name: name, name: name,

View File

@ -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({ Trip({
this.uuid = 'pending', this.uuid = 'pending',

View File

@ -1,33 +1,33 @@
import "dart:convert"; import "dart:convert";
import "dart:developer"; import "dart:developer";
import "package:anyway/utils/load_landmark_image.dart";
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:anyway/constants.dart'; import 'package:anyway/constants.dart';
import "package:anyway/utils/load_landmark_image.dart";
import "package:anyway/structs/landmark.dart"; import "package:anyway/structs/landmark.dart";
import "package:anyway/structs/trip.dart"; import "package:anyway/structs/trip.dart";
import "package:anyway/structs/preferences.dart"; import "package:anyway/structs/preferences.dart";
Dio dio = Dio( Dio dio = Dio(
BaseOptions( BaseOptions(
baseUrl: API_URL_BASE, baseUrl: API_URL_BASE,
connectTimeout: const Duration(seconds: 5), connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 120), 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 // 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, validateStatus: (status) => status! <= 500,
receiveDataWhenStatusError: true, receiveDataWhenStatusError: true,
// api is notoriously slow // api is notoriously slow
// headers: { // headers: {
// HttpHeaders.userAgentHeader: 'dio', // HttpHeaders.userAgentHeader: 'dio',
// 'api': '1.0.0', // 'api': '1.0.0',
// }, // },
contentType: Headers.jsonContentType, contentType: Headers.jsonContentType,
responseType: ResponseType.json, responseType: ResponseType.json,
), ),
); );
fetchTrip( fetchTrip(
Trip trip, Trip trip,
UserPreferences preferences, UserPreferences preferences,

View File

@ -1,11 +1,14 @@
import 'package:anyway/pages/current_trip.dart'; import 'package:anyway/main.dart';
import 'package:anyway/pages/onboarding.dart';
import 'package:anyway/structs/trip.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.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() { Widget getFirstPage() {
SavedTrips trips = SavedTrips(); SavedTrips trips = savedTrips;
trips.loadTrips(); trips.loadTrips();
return ListenableBuilder( return ListenableBuilder(
@ -15,7 +18,7 @@ Widget getFirstPage() {
if (items.isNotEmpty) { if (items.isNotEmpty) {
return TripPage(trip: items[0]); return TripPage(trip: items[0]);
} else { } else {
return OnboardingPage(); return const OnboardingPage();
} }
} }
); );