overhaul using a trip struct that notifies its ui dependencies
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m55s
Build and release APK / Build APK (pull_request) Successful in 5m40s

This commit is contained in:
Remy Moll 2024-08-03 16:52:29 +02:00
parent 5748630b99
commit b1fd0f47fa
14 changed files with 323 additions and 163 deletions

View File

@ -1,3 +1,6 @@
import 'dart:collection';
import 'package:anyway/structs/landmark.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:anyway/constants.dart'; import 'package:anyway/constants.dart';
@ -15,12 +18,12 @@ import 'package:anyway/pages/profile.dart';
// A side drawer is used to switch between pages // A side drawer is used to switch between pages
class BasePage extends StatefulWidget { class BasePage extends StatefulWidget {
final String mainScreen; final String mainScreen;
final Future<Trip>? trip; final Trip? trip;
const BasePage({ const BasePage({
super.key, super.key,
required this.mainScreen, required this.mainScreen,
this.trip this.trip,
}); });
@override @override
@ -53,13 +56,13 @@ class _BasePageState extends State<BasePage> {
children: [ children: [
DrawerHeader( DrawerHeader(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient(colors: [Colors.cyan, theme.primaryColor]) gradient: LinearGradient(colors: [Colors.red, Colors.yellow])
), ),
child: Center( child: Center(
child: Text( child: Text(
APP_NAME, APP_NAME,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.grey[800],
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -129,9 +132,54 @@ class _BasePageState extends State<BasePage> {
} }
} }
// This function is used to get the first trip from a list of trips
// TODO: Implement this function
Future<Trip> getFirstTrip (Future<List<Trip>> trips) async { Trip getFirstTrip(Future<List<Trip>> trips) {
List<Trip> tripsf = await trips; Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>());
return tripsf[0]; t1.landmarks.add(
Landmark(
uuid: '1',
name: "Eiffel Tower",
location: [48.859, 2.295],
type: LandmarkType(name: "Tower"),
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg"
),
);
t1.landmarks.add(
Landmark(
uuid: "2",
name: "Notre Dame Cathedral",
location: [48.8530, 2.3498],
type: LandmarkType(name: "Monument"),
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Notre-Dame_de_Paris%2C_4_October_2017.jpg/440px-Notre-Dame_de_Paris%2C_4_October_2017.jpg"
),
);
t1.landmarks.add(
Landmark(
uuid: "3",
name: "Louvre palace",
location: [48.8606, 2.3376],
type: LandmarkType(name: "Museum"),
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Louvre_Museum_Wikimedia_Commons.jpg/540px-Louvre_Museum_Wikimedia_Commons.jpg"
),
);
t1.landmarks.add(
Landmark(
uuid: "4",
name: "Pont-des-arts",
location: [48.8585, 2.3376],
type: LandmarkType(name: "Bridge"),
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg/560px-Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg"
),
);
t1.landmarks.add(
Landmark(
uuid: "5",
name: "Panthéon",
location: [48.847, 2.347],
type: LandmarkType(name: "Monument"),
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Pantheon_of_Paris_007.JPG/1280px-Pantheon_of_Paris_007.JPG"
),
);
return t1;
} }

View File

@ -13,7 +13,7 @@ class App extends StatelessWidget {
return MaterialApp( return MaterialApp(
title: APP_NAME, title: APP_NAME,
home: BasePage(mainScreen: "map"), home: BasePage(mainScreen: "map"),
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.green), theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]),
); );
} }
} }

View File

@ -3,12 +3,10 @@ import 'package:anyway/structs/trip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class Greeter extends StatefulWidget { class Greeter extends StatefulWidget {
final Future<Trip> trip; final Trip trip;
final bool standalone;
Greeter({ Greeter({
required this.standalone, required this.trip,
required this.trip
}); });
@override @override
@ -18,56 +16,67 @@ class Greeter extends StatefulWidget {
class _GreeterState extends State<Greeter> { class _GreeterState extends State<Greeter> {
Widget greeterBuild (BuildContext context, AsyncSnapshot<Trip> snapshot) { Widget greeterBuilder (BuildContext context, Widget? child) {
ThemeData theme = Theme.of(context); ThemeData theme = Theme.of(context);
Widget topGreeter; Widget topGreeter;
if (widget.trip.landmarks.length > 1) {
topGreeter = FutureBuilder(
future: widget.trip.cityName,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
topGreeter = Padding( return Text(
padding: const EdgeInsets.only(top: 20, bottom: 20), 'Welcome to ${snapshot.data}!',
child: Text(
'Welcome to ${snapshot.data?.cityName}!',
style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24), style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
)
); );
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
topGreeter = const Padding( return const Text('Welcome to your trip!');
padding: EdgeInsets.only(top: 20, bottom: 20), } else {
child: Text('Error while fetching trip') return const Text('Welcome to ...');
}
}
); );
} else { } else {
// still awaiting the cityname // still awaiting the trip
// We can hopefully infer the city name from the cityName future
// Show a linear loader at the bottom and an info message above // Show a linear loader at the bottom and an info message above
topGreeter = Column( topGreeter = Column(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Padding( FutureBuilder(
padding: const EdgeInsets.only(top: 20, bottom: 20), future: widget.trip.cityName,
child: const Text('Generating your trip...', style: TextStyle(fontSize: 20),) builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(
'Generating your trip to ${snapshot.data}...',
style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
);
} else if (snapshot.hasError) {
return const Text('Error while fetching city name');
}
return const Text('Generating your trip...');
}
), ),
const LinearProgressIndicator() Padding(
padding: EdgeInsets.all(5),
child: const LinearProgressIndicator()
)
] ]
); );
} }
if (widget.standalone) {
return Center(
child: topGreeter,
);
} else {
return Center( return Center(
child: Column( child: Column(
children: [ children: [
Padding(padding: EdgeInsets.only(top: 24.0)), // Padding(padding: EdgeInsets.only(top: 20)),
topGreeter, topGreeter,
bottomGreeter, Padding(
Padding(padding: EdgeInsets.only(bottom: 24.0)), padding: EdgeInsets.all(20),
child: bottomGreeter
),
], ],
) )
); );
} }
}
Widget bottomGreeter = const Text( Widget bottomGreeter = const Text(
"Busy day ahead? Here is how to make the most of it!", "Busy day ahead? Here is how to make the most of it!",
@ -79,9 +88,9 @@ class _GreeterState extends State<Greeter> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return ListenableBuilder(
future: widget.trip, listenable: widget.trip,
builder: greeterBuild, builder: greeterBuilder,
); );
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:anyway/structs/landmark.dart'; import 'package:anyway/structs/landmark.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -31,9 +32,10 @@ class _LandmarkCardState extends State<LandmarkCard> {
height: double.infinity, height: double.infinity,
// force a fixed width // force a fixed width
width: 160, width: 160,
child: Image.network( child: CachedNetworkImage(
widget.landmark.imageURL ?? '', imageUrl: widget.landmark.imageURL ?? '',
errorBuilder: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
// TODO: make this a switch statement to load a placeholder if null // TODO: make this a switch statement to load a placeholder if null
// cover the whole container meaning the image will be cropped // cover the whole container meaning the image will be cropped
fit: BoxFit.cover, fit: BoxFit.cover,

View File

@ -1,17 +1,17 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:anyway/modules/landmark_card.dart'; import 'package:anyway/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:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LandmarksOverview extends StatefulWidget { class LandmarksOverview extends StatefulWidget {
final Future<Trip>? trip; final Trip? trip;
const LandmarksOverview({super.key, this.trip}); const LandmarksOverview({super.key, this.trip});
@override @override
@ -23,18 +23,17 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Future<LinkedList<Landmark>> _landmarks = getLandmarks(widget.trip); return ListenableBuilder(//<LinkedList<Landmark>>
return DefaultTextStyle( listenable: widget.trip!,
style: Theme.of(context).textTheme.displayMedium!, builder: (BuildContext context, Widget? child) {
textAlign: TextAlign.center, Trip trip = widget.trip!;
child: FutureBuilder<LinkedList<Landmark>>( log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
future: _landmarks,
builder: (BuildContext context, AsyncSnapshot<LinkedList<Landmark>> snapshot) {
List<Widget> children; List<Widget> children;
if (snapshot.hasData) { if (trip.uuid == 'pending') {
children = [landmarksWithSteps(snapshot.data!), saveButton()]; // the trip is still being fetched from the api
} else if (snapshot.hasError) { children = [Center(child: CircularProgressIndicator())];
children = <Widget>[ } else if (trip.uuid == 'error') {
children = [
const Icon( const Icon(
Icons.error_outline, Icons.error_outline,
color: Colors.red, color: Colors.red,
@ -42,20 +41,25 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 16), padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}', style: TextStyle(fontSize: 12)), child: Text('Error: ${trip.cityName}'),
), ),
]; ];
} else { } else {
children = [Center(child: CircularProgressIndicator())]; if (trip.landmarks.length <= 1) {
children = [
const Text("No landmarks in this trip"),
];
} else {
children = [
landmarksWithSteps(trip.landmarks),
saveButton(),
];
} }
return Center( }
child: Column( return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children, children: children,
),
); );
}, },
),
); );
} }
Widget saveButton() => ElevatedButton( Widget saveButton() => ElevatedButton(
@ -100,7 +104,7 @@ Widget landmarksWithSteps(LinkedList<Landmark> landmarks) {
); );
lkey++; lkey++;
if (landmark.next != null) { if (landmark.next != null) {
Widget step = stepBetweenLandmarks(landmark, landmark.next!); Widget step = stepBetweenLandmarks(landmark);
children.add(step); children.add(step);
} }
} }
@ -111,7 +115,7 @@ Widget landmarksWithSteps(LinkedList<Landmark> landmarks) {
} }
Widget stepBetweenLandmarks(Landmark before, Landmark after) { Widget stepBetweenLandmarks(Landmark landmark) {
// This is a simple widget that draws a line between landmark-cards // This is a simple widget that draws a line between landmark-cards
// It's a vertical dotted line // It's a vertical dotted line
// Next to the line is the icon for the mode of transport (walking for now) and the estimated time // Next to the line is the icon for the mode of transport (walking for now) and the estimated time
@ -134,7 +138,7 @@ Widget stepBetweenLandmarks(Landmark before, Landmark after) {
Column( Column(
children: [ children: [
Icon(Icons.directions_walk), Icon(Icons.directions_walk),
Text("5 min", style: TextStyle(fontSize: 10)), Text("${landmark.tripTime} min", style: TextStyle(fontSize: 10)),
], ],
), ),
Spacer(), Spacer(),
@ -149,8 +153,5 @@ Widget stepBetweenLandmarks(Landmark before, Landmark after) {
); );
} }
Future<LinkedList<Landmark>> getLandmarks (Future<Trip>? trip) async {
Trip tripf = await trip!;
return tripf.landmarks;
}

View File

@ -8,7 +8,7 @@ import 'package:the_widget_marker/the_widget_marker.dart';
class MapWidget extends StatefulWidget { class MapWidget extends StatefulWidget {
final Future<Trip>? trip; final Trip? trip;
MapWidget({ MapWidget({
this.trip this.trip
@ -31,8 +31,7 @@ class _MapWidgetState extends State<MapWidget> {
void _onMapCreated(GoogleMapController controller) async { void _onMapCreated(GoogleMapController controller) async {
mapController = controller; mapController = controller;
Trip? trip = await widget.trip; List<double>? newLocation = widget.trip?.landmarks.first.location;
List<double>? newLocation = trip?.landmarks.first.location;
if (newLocation != null) { if (newLocation != null) {
CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1])); CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1]));
controller.moveCamera(update); controller.moveCamera(update);
@ -48,8 +47,7 @@ class _MapWidgetState extends State<MapWidget> {
void drawLandmarks() async { void drawLandmarks() async {
// (re)draws landmarks on the map // (re)draws landmarks on the map
Trip? trip = await widget.trip; LinkedList<Landmark>? landmarks = widget.trip?.landmarks;
LinkedList<Landmark>? landmarks = trip?.landmarks;
if (landmarks != null){ if (landmarks != null){
for (Landmark landmark in landmarks) { for (Landmark landmark in landmarks) {
markers.add(Marker( markers.add(Marker(

View File

@ -30,7 +30,7 @@ class _TripsOverviewState extends State<TripsOverview> {
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BasePage(mainScreen: "map", trip: Future.value(trip)) builder: (context) => BasePage(mainScreen: "map", trip: trip)
) )
); );
}, },

View File

@ -1,8 +1,10 @@
import 'package:anyway/structs/landmark.dart';
import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:anyway/layout.dart'; import 'package:anyway/layout.dart';
import 'package:anyway/structs/preferences.dart';
import 'package:anyway/utils/fetch_trip.dart'; import 'package:anyway/utils/fetch_trip.dart';
import 'package:flutter/material.dart'; import 'package:anyway/structs/preferences.dart';
import "package:anyway/structs/trip.dart"; import "package:anyway/structs/trip.dart";
@ -64,7 +66,16 @@ class _NewTripPageState extends State<NewTripPage> {
double.parse(lonController.text) double.parse(lonController.text)
]; ];
Future<UserPreferences> preferences = loadUserPreferences(); Future<UserPreferences> preferences = loadUserPreferences();
Future<Trip>? trip = fetchTrip(startPoint, preferences); Trip trip = Trip();
trip.landmarks.add(
Landmark(
location: startPoint,
name: "start",
type: LandmarkType(name: 'start'),
uuid: "pending"
)
);
fetchTrip(trip, preferences);
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BasePage(mainScreen: "map", trip: trip) builder: (context) => BasePage(mainScreen: "map", trip: trip)

View File

@ -10,10 +10,10 @@ import 'package:anyway/modules/greeter.dart';
class NavigationOverview extends StatefulWidget { class NavigationOverview extends StatefulWidget {
final Future<Trip> trip; final Trip trip;
NavigationOverview({ NavigationOverview({
required this.trip required this.trip,
}); });
@override @override
@ -27,53 +27,56 @@ class _NavigationOverviewState extends State<NavigationOverview> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SlidingUpPanel( return SlidingUpPanel(
renderPanelSheet: false,
panel: _floatingPanel(), panel: _floatingPanel(),
collapsed: _floatingCollapsed(), // collapsed: _floatingCollapsed(),
body: MapWidget(trip: widget.trip) body: MapWidget(trip: widget.trip),
// renderPanelSheet: false,
// backdropEnabled: true,
maxHeight: MediaQuery.of(context).size.height * 0.8,
padding: EdgeInsets.all(10),
// panelSnapping: false,
borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
boxShadow: [
BoxShadow(
blurRadius: 20.0,
color: Colors.black,
)
],
); );
} }
Widget _floatingCollapsed(){ Widget _floatingCollapsed(){
final ThemeData theme = Theme.of(context); return Greeter(
return Container( trip: widget.trip
decoration: BoxDecoration(
color: theme.canvasColor,
borderRadius: BorderRadius.only(topLeft: Radius.circular(24.0), topRight: Radius.circular(24.0)),
boxShadow: []
),
child: Greeter(standalone: true, trip: widget.trip)
); );
} }
Widget _floatingPanel(){ Widget _floatingPanel(){
final ThemeData theme = Theme.of(context); return Column(
return Container( children: [
Padding(
padding: const EdgeInsets.all(15),
child:
Center(
child: Container(
width: 40,
height: 5,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.grey[300],
borderRadius: BorderRadius.all(Radius.circular(24.0)), borderRadius: BorderRadius.all(Radius.circular(12.0)),
boxShadow: [
BoxShadow(
blurRadius: 20.0,
color: theme.shadowColor,
), ),
),
),
),
Expanded(
child: ListView(
children: [
Greeter(trip: widget.trip),
LandmarksOverview(trip: widget.trip)
] ]
), )
child: Center( )
child: Padding(
padding: EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Greeter(standalone: false, trip: widget.trip),
LandmarksOverview(trip: widget.trip),
], ],
),
),
),
),
); );
} }
} }

View File

@ -5,35 +5,56 @@ import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'package:anyway/structs/landmark.dart'; import 'package:anyway/structs/landmark.dart';
import 'package:flutter/foundation.dart';
import 'package:geocoding/geocoding.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
class Trip { class Trip with ChangeNotifier {
final String uuid; String uuid;
final String cityName; int totalTime;
// TODO: cityName should be inferred from coordinates of the Landmarks LinkedList<Landmark> landmarks;
final int totalTime;
final LinkedList<Landmark> landmarks;
// could be empty as well // could be empty as well
Future<String> get cityName async {
if (GeocodingPlatform.instance == null) {
return '${landmarks.first.location[0]}, ${landmarks.first.location[1]}';
}
List<Placemark> placemarks = await placemarkFromCoordinates(landmarks.first.location[0], landmarks.first.location[1]);
return placemarks.first.locality ?? 'Unknown';
}
Trip({ Trip({
required this.uuid, this.uuid = 'pending',
required this.cityName, this.totalTime = 0,
required this.landmarks, LinkedList<Landmark>? landmarks
this.totalTime = 0 // a trip can be created with no landmarks, but the list should be initialized anyway
}); }) : landmarks = landmarks ?? LinkedList<Landmark>();
factory Trip.fromJson(Map<String, dynamic> json) { factory Trip.fromJson(Map<String, dynamic> json) {
Trip trip = Trip( Trip trip = Trip(
uuid: json['uuid'], uuid: json['uuid'],
cityName: json['city_name'] ?? 'Not communicated', totalTime: json['total_time'],
landmarks: LinkedList()
); );
return trip; return trip;
} }
void loadFromJson(Map<String, dynamic> json) {
uuid = json['uuid'];
totalTime = json['total_time'];
notifyListeners();
}
void addLandmark(Landmark landmark) {
landmarks.add(landmark);
notifyListeners();
}
void updateUUID(String newUUID) {
uuid = newUUID;
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');
@ -47,7 +68,7 @@ class Trip {
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'uuid': uuid, 'uuid': uuid,
'city_name': cityName, 'total_time': totalTime,
'first_landmark_uuid': landmarks.first.uuid 'first_landmark_uuid': landmarks.first.uuid
}; };

View File

@ -1,7 +1,7 @@
import "dart:convert"; import "dart:convert";
import "dart:developer"; import "dart:developer";
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:anyway/constants.dart'; import 'package:anyway/constants.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";
@ -25,14 +25,14 @@ Dio dio = Dio(
), ),
); );
Future<Trip>? fetchTrip( fetchTrip(
List<double> startPoint, Trip trip,
Future<UserPreferences> preferences, Future<UserPreferences> preferences,
) async { ) async {
UserPreferences prefs = await preferences; UserPreferences prefs = await preferences;
Map<String, dynamic> data = { Map<String, dynamic> data = {
"preferences": prefs.toJson(), "preferences": prefs.toJson(),
"start": startPoint "start": trip.landmarks!.first.location,
}; };
String dataString = jsonEncode(data); String dataString = jsonEncode(data);
log(dataString); log(dataString);
@ -44,24 +44,25 @@ Future<Trip>? fetchTrip(
// handle errors // handle errors
if (response.statusCode != 200) { if (response.statusCode != 200) {
trip.updateUUID("error");
throw Exception('Failed to load trip'); throw Exception('Failed to load trip');
} }
if (response.data["error"] != null) { if (response.data["error"] != null) {
trip.updateUUID("error");
throw Exception(response.data["error"]); throw Exception(response.data["error"]);
} }
log(response.data.toString()); log(response.data.toString());
Map<String, dynamic> json = response.data; Map<String, dynamic> json = response.data;
// only fetch the trip "meta" data for now // only fill in the trip "meta" data for now
Trip trip = Trip.fromJson(json); trip.loadFromJson(json);
String? nextUUID = json["first_landmark_uuid"]; String? nextUUID = json["first_landmark_uuid"];
while (nextUUID != null) { while (nextUUID != null) {
var (landmark, newUUID) = await fetchLandmark(nextUUID); var (landmark, newUUID) = await fetchLandmark(nextUUID);
trip.landmarks.add(landmark); trip.addLandmark(landmark);
nextUUID = newUUID; nextUUID = newUUID;
} }
return trip;
} }

View File

@ -17,7 +17,7 @@ Future<List<Trip>> loadTrips() async {
} }
if (trips.isEmpty) { if (trips.isEmpty) {
Trip t1 = Trip(uuid: '1', cityName: 'Paris', landmarks: LinkedList<Landmark>()); Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>());
t1.landmarks.add( t1.landmarks.add(
Landmark( Landmark(
uuid: '1', uuid: '1',
@ -66,7 +66,7 @@ Future<List<Trip>> loadTrips() async {
trips.add(t1); trips.add(t1);
Trip t2 = Trip(uuid: '2', cityName: 'Vienna', landmarks: LinkedList<Landmark>()); Trip t2 = Trip(uuid: '2', landmarks: LinkedList<Landmark>());
t2.landmarks.add( t2.landmarks.add(
Landmark( Landmark(

View File

@ -25,6 +25,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819"
url: "https://pub.dev"
source: hosted
version: "3.4.0"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: ff0c949e323d2a1b52be73acce5b4a7b04063e61414c8ca542dbba47281630a7
url: "https://pub.dev"
source: hosted
version: "4.1.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -168,6 +192,38 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
geocoding:
dependency: "direct main"
description:
name: geocoding
sha256: d580c801cba9386b4fac5047c4c785a4e19554f46be42f4f5e5b7deacd088a66
url: "https://pub.dev"
source: hosted
version: "3.0.0"
geocoding_android:
dependency: transitive
description:
name: geocoding_android
sha256: "1b13eca79b11c497c434678fed109c2be020b158cec7512c848c102bc7232603"
url: "https://pub.dev"
source: hosted
version: "3.3.1"
geocoding_ios:
dependency: transitive
description:
name: geocoding_ios
sha256: "94ddba60387501bd1c11e18dca7c5a9e8c645d6e3da9c38b9762434941870c24"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
geocoding_platform_interface:
dependency: transitive
description:
name: geocoding_platform_interface
sha256: "8c2c8226e5c276594c2e18bfe88b19110ed770aeb7c1ab50ede570be8b92229b"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
google_maps: google_maps:
dependency: transitive dependency: transitive
description: description:
@ -296,6 +352,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.12.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
path: path:
dependency: transitive dependency: transitive
description: description:

View File

@ -41,6 +41,8 @@ dependencies:
dio: ^5.5.0+1 dio: ^5.5.0+1
google_maps_flutter: ^2.7.0 google_maps_flutter: ^2.7.0
the_widget_marker: ^1.0.0 the_widget_marker: ^1.0.0
cached_network_image: ^3.4.0
geocoding: ^3.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: