Compare commits

..

No commits in common. "c4fddc1a575b1dd22c5f0ba82173a5a5b2a8ea7e" and "d31ca9f81fb9cd85cf9a2f5399be6b6299bd90b9" have entirely different histories.

24 changed files with 435 additions and 576 deletions

5
.vscode/launch.json vendored
View File

@ -36,10 +36,7 @@
"type": "dart", "type": "dart",
"request": "launch", "request": "launch",
"program": "lib/main.dart", "program": "lib/main.dart",
"cwd": "${workspaceFolder}/frontend", "cwd": "${workspaceFolder}/frontend"
"env": {
"GOOGLE_MAPS_API_KEY": "testing"
}
}, },
{ {
"name": "Frontend - profile", "name": "Frontend - profile",

View File

@ -19,7 +19,7 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "2.0.20" apply false id "org.jetbrains.kotlin.android" version "2.0.20" apply false
} }

View File

@ -1,27 +1,24 @@
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) => MaterialApp( Widget build(BuildContext context) {
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,18 +1,20 @@
import 'dart:developer'; 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
List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector}) { List<Widget> landmarksList(Trip trip) {
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
List<Widget> children = []; List<Widget> children = [];
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == typeStart ) { if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == typeStart ) {
children.add( children.add(
const Text("No landmarks in this trip"), const Text("No landmarks in this trip"),
@ -21,24 +23,17 @@ List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector
} }
for (Landmark landmark in trip.landmarks) { for (Landmark landmark in trip.landmarks) {
if (selector(landmark)) {
children.add( children.add(
LandmarkCard(landmark, trip), LandmarkCard(landmark, trip),
); );
if (!landmark.visited) { if (landmark.next != null) {
Landmark? nextLandmark = landmark.next;
while (nextLandmark != null && nextLandmark.visited) {
nextLandmark = nextLandmark.next;
}
if (nextLandmark != null) {
children.add( children.add(
StepBetweenLandmarks(current: landmark, next: nextLandmark!) StepBetweenLandmarks(current: landmark, next: landmark.next!)
); );
} }
} }
}
}
return children; return children;
} }

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'package:anyway/constants.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
@ -36,32 +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
const Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Padding( child: statusText(),
padding: EdgeInsets.only(bottom: 12),
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(const Duration(seconds: 5), () { Future.delayed(Duration(seconds: 5), () {
setState(() { setState(() {
statusIndex = (statusIndex + 1) % statusTexts.length; statusIndex = (statusIndex + 1) % statusTexts.length;
}); });
@ -85,19 +81,19 @@ Widget loadingText(Trip trip) => FutureBuilder(
Widget greeter; Widget greeter;
if (snapshot.hasData) { if (snapshot.hasData) {
greeter = AnimatedDotsText( greeter = AnimatedGradientText(
baseText: 'Creating your trip to ${snapshot.data}', text: 'Creating your trip to ${snapshot.data}...',
style: greeterStyle, style: greeterStyle,
); );
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
// the exact error is shown in the central part of the trip overview. No need to show it here // the exact error is shown in the central part of the trip overview. No need to show it here
greeter = Text( greeter = AnimatedGradientText(
'Error while loading trip.', text: 'Error while loading trip.',
style: greeterStyle, style: greeterStyle,
); );
} else { } else {
greeter = AnimatedDotsText( greeter = AnimatedGradientText(
baseText: 'Creating your trip', text: 'Creating your trip...',
style: greeterStyle, style: greeterStyle,
); );
} }
@ -105,44 +101,62 @@ Widget loadingText(Trip trip) => FutureBuilder(
} }
); );
class AnimatedDotsText extends StatefulWidget { class AnimatedGradientText extends StatefulWidget {
final String baseText; final String text;
final TextStyle style; final TextStyle style;
const AnimatedDotsText({ const AnimatedGradientText({
Key? key, Key? key,
required this.baseText, required this.text,
required this.style, required this.style,
}) : super(key: key); }) : super(key: key);
@override @override
_AnimatedDotsTextState createState() => _AnimatedDotsTextState(); _AnimatedGradientTextState createState() => _AnimatedGradientTextState();
} }
class _AnimatedDotsTextState extends State<AnimatedDotsText> { class _AnimatedGradientTextState extends State<AnimatedGradientText> with SingleTickerProviderStateMixin {
int dotCount = 0; late AnimationController _controller;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Timer.periodic(const Duration(seconds: 1), (timer) { _controller = AnimationController(
if (mounted) { duration: const Duration(seconds: 1),
setState(() { vsync: this,
dotCount = (dotCount + 1) % 4; )..repeat();
// show up to 3 dots
});
} else {
timer.cancel();
} }
});
@override
void dispose() {
_controller.dispose();
super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String dots = '.' * dotCount; return AnimatedBuilder(
return Text( animation: _controller,
'${widget.baseText}$dots', builder: (context, child) {
return ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
colors: [GRADIENT_START, GRADIENT_END, GRADIENT_START],
stops: [
_controller.value - 1.0,
_controller.value,
_controller.value + 1.0,
],
tileMode: TileMode.mirror,
).createShader(bounds);
},
child: Text(
widget.text,
style: widget.style, style: widget.style,
),
);
},
); );
} }
} }

View File

@ -1,19 +1,22 @@
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:anyway/modules/landmark_map_marker.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';
class CurrentTripMap extends StatefulWidget { class CurrentTripMap extends StatefulWidget {
final Trip? trip; final Trip? trip;
CurrentTripMap({this.trip}); CurrentTripMap({
this.trip
});
@override @override
State<CurrentTripMap> createState() => _CurrentTripMapState(); State<CurrentTripMap> createState() => _CurrentTripMapState();
@ -27,23 +30,7 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
zoom: 11.0, zoom: 11.0,
); );
Set<Marker> mapMarkers = <Marker>{}; 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 { void _onMapCreated(GoogleMapController controller) async {
mapController = controller; mapController = controller;
@ -53,70 +40,38 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
controller.moveCamera(update); controller.moveCamera(update);
} }
setMapMarkers(); setMapMarkers();
setMapRoute();
} }
void _onCameraIdle() { void _onCameraIdle() {
// print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0))); // print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0)));
} }
void setMapMarkers() async { void setMapMarkers() async {
Iterator<(int, Landmark)> it = (widget.trip?.landmarks.toList() ?? []).indexed.iterator; List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? [];
Set<Marker> newMarkers = <Marker>{};
while (it.moveNext()) { for (int i = 0; i < landmarks.length; i++) {
int i = it.current.$1; Landmark landmark = landmarks[i];
Landmark landmark = it.current.$2;
MarkerId markerId = MarkerId("${landmark.uuid} - ${landmark.visited}");
List<double> location = landmark.location; List<double> location = landmark.location;
// only create a new marker, if there is no marker for this landmark
if (!mapMarkers.any((Marker marker) => marker.markerId == markerId)) {
Marker marker = Marker( Marker marker = Marker(
markerId: markerId, markerId: MarkerId(landmark.uuid),
position: LatLng(location[0], location[1]), position: LatLng(location[0], location[1]),
icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor( icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor(
logicalSize: const Size(150, 150), logicalSize: const Size(150, 150),
imageSize: const Size(150, 150), imageSize: const Size(150, 150)
) ),
); );
newMarkers.add(marker);
}
setState(() { setState(() {
mapMarkers.add(marker); mapMarkers = newMarkers;
}); });
} }
}
}
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 || (landmark.next?.visited ?? false) ? Colors.grey : PRIMARY_COLOR,
width: 5
);
polyLines.add(stepLine);
}
}
setState(() {
mapPolylines = polyLines;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
widget.trip?.addListener(setMapMarkers);
Future<SharedPreferences> preferences = SharedPreferences.getInstance(); Future<SharedPreferences> preferences = SharedPreferences.getInstance();
return FutureBuilder( return FutureBuilder(
@ -129,7 +84,7 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
} else { } else {
return const CircularProgressIndicator(); return const CircularProgressIndicator();
} }
}, }
); );
} }
@ -138,8 +93,8 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
onMapCreated: _onMapCreated, onMapCreated: _onMapCreated,
initialCameraPosition: _cameraPosition, initialCameraPosition: _cameraPosition,
onCameraIdle: _onCameraIdle, onCameraIdle: _onCameraIdle,
// onLongPress: ,
markers: mapMarkers, markers: mapMarkers,
polylines: mapPolylines,
cloudMapId: MAP_ID, cloudMapId: MAP_ID,
mapToolbarEnabled: false, mapToolbarEnabled: false,
zoomControlsEnabled: false, zoomControlsEnabled: false,
@ -147,4 +102,5 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
myLocationButtonEnabled: false, myLocationButtonEnabled: false,
); );
} }
} }

View File

@ -1,12 +1,9 @@
import 'package:flutter/material.dart';
import 'package:anyway/constants.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_error_message.dart';
import 'package:anyway/modules/current_trip_loading_indicator.dart'; import 'package:anyway/modules/current_trip_loading_indicator.dart';
import 'package:flutter/material.dart';
import 'package:anyway/structs/trip.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';
@ -66,41 +63,13 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
child: CurrentTripGreeter(trip: widget.trip), 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),
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),
),
),
],
),
),
),
const Padding(padding: EdgeInsets.only(top: 10)), const Padding(padding: EdgeInsets.only(top: 10)),
// upcoming landmarks // CurrentTripSummary(trip: widget.trip),
...landmarksList(widget.trip, selector: (Landmark landmark) => landmark.visited == false),
// const Divider(),
...landmarksList(widget.trip),
const Padding(padding: EdgeInsets.only(top: 10)), const Padding(padding: EdgeInsets.only(top: 10)),

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,3 +52,4 @@ class _saveButtonState extends State<saveButton> {
); );
} }
} }

View File

@ -1,7 +1,6 @@
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,27 +15,17 @@ class CurrentTripSummary extends StatefulWidget {
class _CurrentTripSummaryState extends State<CurrentTripSummary> { class _CurrentTripSummaryState extends State<CurrentTripSummary> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Column(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Text('Summary'),
children: [ // Text('Start: ${widget.trip.start}'),
const Icon(Icons.flag, size: 20), // Text('End: ${widget.trip.end}'),
const Padding(padding: EdgeInsets.only(right: 10)), Text('Total duration: ${widget.trip.totalTime}'),
Text('Stops: ${widget.trip.landmarks.length}', style: Theme.of(context).textTheme.bodyLarge), Text('Total distance: ${widget.trip.totalTime}'),
] // Text('Fuel: ${widget.trip.fuel}'),
), // Text('Cost: ${widget.trip.cost}'),
Row(
children: [
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),
]
),
], ],
)
); );
} }
} }

View File

@ -1,5 +1,5 @@
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>(

View File

@ -1,15 +1,11 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:anyway/constants.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:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:url_launcher/url_launcher.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;
@ -27,184 +23,150 @@ 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),
);
}
// else:
return Container( return Container(
constraints: BoxConstraints(
// express the max height in terms text lines
maxHeight: 7 * (Theme.of(context).textTheme.titleMedium!.fontSize! + 10),
),
child: Card( child: Card(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(15.0),
), ),
elevation: 5, elevation: 5,
clipBehavior: Clip.antiAliasWithSaveLayer, clipBehavior: Clip.antiAliasWithSaveLayer,
// if the image is available, display it on the left side of the card, otherwise only display the text // if the image is available, display it on the left side of the card, otherwise only display the text
child: Row( 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(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Image and landmark "type" on the left
AspectRatio(
aspectRatio: 3 / 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (widget.landmark.imageURL != null && widget.landmark.imageURL!.isNotEmpty)
Expanded(
child: CachedNetworkImage(
imageUrl: widget.landmark.imageURL!,
placeholder: (context, url) => const Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => imagePlaceholder(widget.landmark),
fit: BoxFit.cover
)
)
else
imagePlaceholder(widget.landmark),
if (widget.landmark.type != typeStart && widget.landmark.type != typeFinish)
Container( Container(
color: PRIMARY_COLOR, // the image on the left
child: Center( width: 160,
child: Padding( height: 160,
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 5,
children: [
Icon(Icons.timer_outlined, size: 16),
Text("${widget.landmark.duration?.inMinutes} minutes"),
],
)
)
),
)
],
)
),
// Main information, useful buttons on the right child: CachedNetworkImage(
Expanded( imageUrl: widget.landmark.imageURL ?? '',
placeholder: (context, url) => Center(child: CircularProgressIndicator()),
errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
fit: BoxFit.cover,
),
),
Flexible(
child: textLayout(),
),
],
);
}
Widget textLayout() {
return Padding(
padding: EdgeInsets.all(10),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Row(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Flexible(
child: Text(
widget.landmark.name, widget.landmark.name,
style: Theme.of(context).textTheme.titleMedium, style: const TextStyle(
overflow: TextOverflow.ellipsis, fontSize: 18,
fontWeight: FontWeight.bold,
),
maxLines: 2, maxLines: 2,
), ),
)
],
),
if (widget.landmark.nameEN != null) if (widget.landmark.nameEN != null)
Text( Row(
children: [
Flexible(
child: Text(
widget.landmark.nameEN!, widget.landmark.nameEN!,
style: Theme.of(context).textTheme.bodyMedium, style: const TextStyle(
fontSize: 16,
),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
] )
],
), ),
), Padding(padding: EdgeInsets.only(top: 10)),
Align(
// fill the vspace alignment: Alignment.centerLeft,
const Spacer(), child: SingleChildScrollView(
// allows the buttons to be scrolled
SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(left: 5, right: 5, bottom: 10),
// the scroll view should be flush once the buttons are scrolled to the left
// but initially there should be some padding
child: Wrap( child: Wrap(
spacing: 10, spacing: 10,
// 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(), 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'),
),
if (widget.landmark.websiteURL != null) if (widget.landmark.websiteURL != null)
websiteButton(), TextButton.icon(
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 { onPressed: () async {
// 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: const Icon(Icons.link), icon: Icon(Icons.link),
label: const Text('Website'), label: Text('Website'),
); ),
PopupMenuButton(
icon: Icon(Icons.settings),
Widget optionsButton () => PopupMenuButton(
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: const Icon(Icons.delete), leading: Icon(Icons.delete),
title: const Text('Delete'), title: 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("${widget.landmark.name} won't be shown again")) SnackBar(content: Text("We won't show ${widget.landmark.name} again"))
); );
}, },
), ),
), ),
PopupMenuItem( PopupMenuItem(
child: ListTile( child: ListTile(
leading: const Icon(Icons.star), leading: Icon(Icons.star),
title: const Text('Favorite'), title: Text('Favorite'),
onTap: () async { onTap: () async {
rootScaffoldMessengerKey.currentState!.showSnackBar( // delete the landmark
SnackBar(content: Text("Not implemented yet")) // await deleteLandmark(widget.landmark);
);
}, },
), ),
), ),
], ],
)
],
),
),
),
],
),
); );
} }
}
Widget imagePlaceholder (Landmark landmark) => Expanded(
child:
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [GRADIENT_START, GRADIENT_END],
),
),
child: Center(
child: Icon(landmark.type.icon.icon, size: 50),
),
),
);

View File

@ -40,7 +40,7 @@ class ThemedMarker extends StatelessWidget {
children: [ children: [
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: landmark.visited ? LinearGradient(colors: [Colors.grey, Colors.white]) : APP_GRADIENT, gradient: APP_GRADIENT,
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(color: Colors.black, width: 5), border: Border.all(color: Colors.black, width: 5),
), ),

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(
const SnackBar(content: Text("Please specify at least one preference")) 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(
const SnackBar(content: Text("Please choose a longer duration")) SnackBar(content: Text("Please choose a longer duration"))
); );
} else { } else {
Trip trip = widget.trip; Trip trip = widget.trip;
@ -63,3 +63,4 @@ 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 'package:flutter/material.dart'; import 'dart:developer';
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/structs/trip.dart';
import 'package:anyway/structs/landmark.dart';
import 'package:anyway/modules/landmark_map_marker.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';
class NewTripMap extends StatefulWidget { class NewTripMap extends StatefulWidget {
@ -30,6 +30,7 @@ class _NewTripMapState extends State<NewTripMap> {
final Set<Marker> _markers = <Marker>{}; final Set<Marker> _markers = <Marker>{};
_onLongPress(LatLng location) { _onLongPress(LatLng location) {
log('Long press: $location');
widget.trip.landmarks.clear(); widget.trip.landmarks.clear();
widget.trip.addLandmark( widget.trip.addLandmark(
Landmark( Landmark(

View File

@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:anyway/structs/landmark.dart'; import 'package:anyway/structs/landmark.dart';
import 'package:flutter/material.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;
@ -21,23 +19,12 @@ 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; int timeRounded = 5 * ((widget.current.tripTime?.inMinutes ?? 0) ~/ 5);
// ~/ is integer division (rounding)
// since landmarks might have been marked as visited, the next landmark might not be the immediate next in the list
// => the precomputed trip time is not valid anymore
if (widget.current.next != widget.next) {
time = null;
}
// round 0 travel time to 1 minute
if (time != null && time < 1) {
time = 1;
}
return Container( return Container(
margin: const EdgeInsets.all(10), margin: EdgeInsets.all(10),
padding: const EdgeInsets.all(10), padding: EdgeInsets.all(10),
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
left: BorderSide(width: 3.0, color: Colors.black), left: BorderSide(width: 3.0, color: Colors.black),
), ),
@ -46,22 +33,21 @@ class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> {
children: [ children: [
Column( Column(
children: [ children: [
const Icon(Icons.directions_walk), Icon(Icons.directions_walk),
Text( Text("~$timeRounded min", style: TextStyle(fontSize: 10)),
time == null ? "" : "About $time min",
style: const TextStyle(fontSize: 10)
),
], ],
), ),
Spacer(),
const Spacer(), ElevatedButton(
ElevatedButton.icon(
onPressed: () async { onPressed: () async {
showMapChooser(context, widget.current, widget.next); showMapChooser(context, widget.current, widget.next);
}, },
icon: const Icon(Icons.directions), child: Row(
label: const Text("Directions"), children: [
Icon(Icons.directions),
Text("Directions"),
],
),
) )
], ],
), ),

View File

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

View File

@ -1,5 +1,5 @@
import 'package:anyway/constants.dart'; import 'package:anyway/constants.dart';
import 'package:anyway/layouts/scaffold.dart'; import 'package:anyway/pages/base_page.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,13 +28,12 @@ class TripPage extends StatefulWidget {
class _TripPageState extends State<TripPage> with ScaffoldLayout{ class _TripPageState extends State<TripPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return mainScaffold( return BasePage(
context, mainScreen: SlidingUpPanel(
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
@ -59,13 +58,9 @@ class _TripPageState extends State<TripPage> with ScaffoldLayout{
title: FutureBuilder( title: FutureBuilder(
future: widget.trip.cityName, future: widget.trip.cityName,
builder: (context, snapshot) => Text( builder: (context, snapshot) => Text(
'Trip to ${snapshot.hasData ? snapshot.data! : "..."}', 'Your 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> with ScaffoldLayout { class _NewTripPageState extends State<NewTripPage> {
final TextEditingController latController = TextEditingController(); final TextEditingController latController = TextEditingController();
final TextEditingController lonController = TextEditingController(); final TextEditingController lonController = TextEditingController();
Trip trip = Trip(); Trip trip = Trip();
@ -23,9 +23,8 @@ class _NewTripPageState extends State<NewTripPage> with ScaffoldLayout {
@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 mainScaffold( return BasePage(
context, mainScreen: Scaffold(
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,14 +15,13 @@ class NewTripPreferencesPage extends StatefulWidget {
_NewTripPreferencesPageState createState() => _NewTripPreferencesPageState(); _NewTripPreferencesPageState createState() => _NewTripPreferencesPageState();
} }
class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> with ScaffoldLayout { class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
UserPreferences preferences = UserPreferences(); UserPreferences preferences = UserPreferences();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return mainScaffold( return BasePage(
context, mainScreen: Scaffold(
child: Scaffold(
body: ListView( body: ListView(
children: [ children: [
// Center( // Center(
@ -58,6 +57,7 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> with Sc
), ),
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,11 +14,11 @@ class SettingsPage extends StatefulWidget {
_SettingsPageState createState() => _SettingsPageState(); _SettingsPageState createState() => _SettingsPageState();
} }
class _SettingsPageState extends State<SettingsPage> with ScaffoldLayout { class _SettingsPageState extends State<SettingsPage> {
@override @override
Widget build (BuildContext context) => mainScaffold( Widget build(BuildContext context) {
context, return BasePage(
child: ListView( mainScreen: ListView(
padding: EdgeInsets.all(15), padding: EdgeInsets.all(15),
children: [ children: [
// First a round, centered image // First a round, centered image
@ -49,6 +49,7 @@ class _SettingsPageState extends State<SettingsPage> with ScaffoldLayout {
'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

@ -27,12 +27,11 @@ final class Landmark extends LinkedListEntry<Landmark>{
String? imageURL; // not final because it can be patched String? imageURL; // not final because it can be patched
final String? description; final String? description;
final Duration? duration; final Duration? duration;
bool visited; final bool? visited;
// Next node is implicitly available through the LinkedListEntry mixin // Next node
// final Landmark? next; // final Landmark? next;
Duration? tripTime; final Duration? tripTime;
// the trip time depends on the next landmark, so it is not final
Landmark({ Landmark({
@ -47,7 +46,7 @@ final class Landmark extends LinkedListEntry<Landmark>{
this.imageURL, this.imageURL,
this.description, this.description,
this.duration, this.duration,
this.visited = false, this.visited,
// this.next, // this.next,
this.tripTime, this.tripTime,
@ -71,8 +70,8 @@ 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']); var duration = Duration(minutes: json['duration'] ?? 0) as Duration?;
final visited = json['visited'] ?? false; final visited = json['visited'] as bool?;
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(

View File

@ -29,18 +29,6 @@ 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',
@ -64,7 +52,6 @@ class Trip with ChangeNotifier {
totalTime = json['total_time']; totalTime = json['total_time'];
notifyListeners(); notifyListeners();
} }
void addLandmark(Landmark landmark) { void addLandmark(Landmark landmark) {
landmarks.add(landmark); landmarks.add(landmark);
notifyListeners(); notifyListeners();
@ -75,16 +62,8 @@ class Trip with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void removeLandmark (Landmark landmark) async { void removeLandmark(Landmark landmark) {
Landmark? previous = landmark.previous;
Landmark? next = landmark.next;
landmarks.remove(landmark); landmarks.remove(landmark);
// removing the landmark means we need to recompute the time between the two adjoined landmarks
if (previous != null && next != null) {
// previous.next = next happens automatically since we are using a LinkedList
previous.tripTime = null;
// TODO
}
notifyListeners(); notifyListeners();
} }
@ -93,10 +72,6 @@ class Trip with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void notifyUpdate(){
notifyListeners();
}
factory Trip.fromPrefs(SharedPreferences prefs, String uuid) { factory Trip.fromPrefs(SharedPreferences prefs, String uuid) {
String? content = prefs.getString('trip_$uuid'); String? content = prefs.getString('trip_$uuid');
Map<String, dynamic> json = jsonDecode(content!); Map<String, dynamic> json = jsonDecode(content!);
@ -105,6 +80,7 @@ class Trip with ChangeNotifier {
log('Loading trip $uuid with first landmark $firstUUID'); log('Loading trip $uuid with first landmark $firstUUID');
LinkedList<Landmark> landmarks = readLandmarks(prefs, firstUUID); LinkedList<Landmark> landmarks = readLandmarks(prefs, firstUUID);
trip.landmarks = landmarks; trip.landmarks = landmarks;
// notifyListeners();
return trip; return trip;
} }

View File

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