From 311b1c2218b08a6b1bd94cd58340e94e46723d1e Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Fri, 9 Aug 2024 00:48:45 +0200 Subject: [PATCH] location picker and ui fixes --- frontend/lib/constants.dart | 1 + frontend/lib/layout.dart | 9 +- frontend/lib/main.dart | 3 + frontend/lib/modules/landmark_card.dart | 12 +- frontend/lib/modules/landmarks_list.dart | 64 +++++++ frontend/lib/modules/landmarks_overview.dart | 168 ------------------ frontend/lib/modules/map.dart | 77 +------- frontend/lib/modules/new_trip_button.dart | 78 ++++++++ .../lib/modules/new_trip_location_search.dart | 64 +++++++ frontend/lib/modules/new_trip_map.dart | 86 +++++++++ frontend/lib/modules/save_button.dart | 33 ++++ .../lib/modules/step_between_landmarks.dart | 60 +++++++ frontend/lib/modules/themed_marker.dart | 68 +++++++ ...ps_overview.dart => trips_saved_list.dart} | 0 frontend/lib/pages/new_trip.dart | 81 +++------ frontend/lib/pages/overview.dart | 82 --------- frontend/lib/pages/trip.dart | 84 +++++++++ frontend/lib/structs/trip.dart | 13 +- 18 files changed, 588 insertions(+), 395 deletions(-) create mode 100644 frontend/lib/modules/landmarks_list.dart delete mode 100644 frontend/lib/modules/landmarks_overview.dart create mode 100644 frontend/lib/modules/new_trip_button.dart create mode 100644 frontend/lib/modules/new_trip_location_search.dart create mode 100644 frontend/lib/modules/new_trip_map.dart create mode 100644 frontend/lib/modules/save_button.dart create mode 100644 frontend/lib/modules/step_between_landmarks.dart create mode 100644 frontend/lib/modules/themed_marker.dart rename frontend/lib/modules/{trips_overview.dart => trips_saved_list.dart} (100%) delete mode 100644 frontend/lib/pages/overview.dart create mode 100644 frontend/lib/pages/trip.dart diff --git a/frontend/lib/constants.dart b/frontend/lib/constants.dart index efa0a1f..973aec9 100644 --- a/frontend/lib/constants.dart +++ b/frontend/lib/constants.dart @@ -2,3 +2,4 @@ const String APP_NAME = 'AnyWay'; String API_URL_BASE = 'https://anyway.kluster.moll.re'; +const String MAP_ID = '41c21ac9b81dbfd8'; diff --git a/frontend/lib/layout.dart b/frontend/lib/layout.dart index 7a214d3..be398c0 100644 --- a/frontend/lib/layout.dart +++ b/frontend/lib/layout.dart @@ -6,14 +6,17 @@ import 'package:flutter/material.dart'; import 'package:anyway/constants.dart'; import 'package:anyway/structs/trip.dart'; -import 'package:anyway/modules/trips_overview.dart'; +import 'package:anyway/modules/trips_saved_list.dart'; import 'package:anyway/utils/load_trips.dart'; import 'package:anyway/pages/new_trip.dart'; import 'package:anyway/pages/tutorial.dart'; -import 'package:anyway/pages/overview.dart'; +import 'package:anyway/pages/trip.dart'; import 'package:anyway/pages/profile.dart'; + + + // BasePage is the scaffold that holds all other pages // A side drawer is used to switch between pages class BasePage extends StatefulWidget { @@ -39,7 +42,7 @@ class _BasePageState extends State { if (widget.mainScreen == "map") { - currentView = NavigationOverview(trip: widget.trip ?? getFirstTrip(trips)); + currentView = TripPage(trip: widget.trip ?? getFirstTrip(trips)); } else if (widget.mainScreen == "tutorial") { currentView = TutorialPage(); } else if (widget.mainScreen == "profile") { diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 44c3922..9e2f631 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -4,6 +4,8 @@ import 'package:anyway/layout.dart'; void main() => runApp(const App()); +final GlobalKey rootScaffoldMessengerKey = GlobalKey(); + class App extends StatelessWidget { const App({super.key}); @@ -14,6 +16,7 @@ class App extends StatelessWidget { title: APP_NAME, home: BasePage(mainScreen: "map"), theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]), + scaffoldMessengerKey: rootScaffoldMessengerKey ); } } diff --git a/frontend/lib/modules/landmark_card.dart b/frontend/lib/modules/landmark_card.dart index 579ebca..636153c 100644 --- a/frontend/lib/modules/landmark_card.dart +++ b/frontend/lib/modules/landmark_card.dart @@ -1,17 +1,19 @@ -import 'package:anyway/structs/landmark.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:cached_network_image/cached_network_image.dart'; + +import 'package:anyway/structs/landmark.dart'; class LandmarkCard extends StatefulWidget { final Landmark landmark; + + LandmarkCard(this.landmark); + @override _LandmarkCardState createState() => _LandmarkCardState(); - - LandmarkCard(this.landmark); - } + class _LandmarkCardState extends State { @override Widget build(BuildContext context) { diff --git a/frontend/lib/modules/landmarks_list.dart b/frontend/lib/modules/landmarks_list.dart new file mode 100644 index 0000000..097b700 --- /dev/null +++ b/frontend/lib/modules/landmarks_list.dart @@ -0,0 +1,64 @@ +import 'dart:developer'; +import 'package:anyway/modules/step_between_landmarks.dart'; +import 'package:flutter/material.dart'; + +import 'package:anyway/modules/landmark_card.dart'; +import 'package:anyway/structs/landmark.dart'; +import 'package:anyway/structs/trip.dart'; +import 'package:anyway/main.dart'; + + + +List landmarksList(Trip trip) { + log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); + + List children = []; + + log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); + + if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == start ) { + children.add( + const Text("No landmarks in this trip"), + ); + return children; + } + + for (Landmark landmark in trip.landmarks) { + children.add( + Dismissible( + key: ValueKey(landmark.hashCode), + child: LandmarkCard(landmark), + dismissThresholds: {DismissDirection.endToStart: 0.95, DismissDirection.startToEnd: 0.95}, + onDismissed: (direction) { + log('Removing ${landmark.name}'); + trip.removeLandmark(landmark); + // Then show a snackbar + + rootScaffoldMessengerKey.currentState!.showSnackBar( + SnackBar(content: Text("We won't show ${landmark.name} again")) + ); + }, + + background: Container(color: Colors.red), + secondaryBackground: Container( + color: Colors.red, + child: Icon( + Icons.delete, + color: Colors.white, + ), + padding: EdgeInsets.all(15), + alignment: Alignment.centerRight, + ), + ) + ); + + if (landmark.next != null) { + children.add( + StepBetweenLandmarks(current: landmark, next: landmark.next!) + ); + } + } + + return children; +} + diff --git a/frontend/lib/modules/landmarks_overview.dart b/frontend/lib/modules/landmarks_overview.dart deleted file mode 100644 index a368e3d..0000000 --- a/frontend/lib/modules/landmarks_overview.dart +++ /dev/null @@ -1,168 +0,0 @@ -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/structs/landmark.dart'; -import 'package:anyway/structs/trip.dart'; - - - - -class LandmarksOverview extends StatefulWidget { - final Trip? trip; - const LandmarksOverview({super.key, this.trip}); - - @override - State createState() => _LandmarksOverviewState(); -} - -class _LandmarksOverviewState extends State { - - @override - Widget build(BuildContext context) { - return ListenableBuilder( - listenable: widget.trip!, - builder: (BuildContext context, Widget? child) { - Trip trip = widget.trip!; - log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); - - List children; - - if (trip.uuid != 'pending' && trip.uuid != 'error') { - log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); - if (trip.landmarks.length <= 1) { - children = [ - const Text("No landmarks in this trip"), - ]; - } else { - children = [ - landmarksWithSteps(), - saveButton(), - ]; - } - } else if(trip.uuid == 'pending') { - // the trip is still being fetched from the api - children = [Center(child: CircularProgressIndicator())]; - } else { - // trip.uuid == 'error' - // show the error raised by the api - // String error = - children = [ - const Icon( - Icons.error_outline, - color: Colors.red, - size: 60, - ), - Padding( - padding: const EdgeInsets.only(top: 16), - child: Text('Error: ${trip.errorDescription}'), - ), - ]; - } - - return Column( - children: children, - ); - }, - ); - } - Widget saveButton() => ElevatedButton( - onPressed: () async { - Trip? trip = await widget.trip; - SharedPreferences prefs = await SharedPreferences.getInstance(); - trip?.toPrefs(prefs); - }, - child: const Text('Save'), - ); - - Widget landmarksWithSteps() { - return ListenableBuilder( - listenable: widget.trip!, - builder: (BuildContext context, Widget? child) { - List children = []; - for (Landmark landmark in widget.trip!.landmarks) { - children.add( - Dismissible( - key: ValueKey(landmark.hashCode), - child: LandmarkCard(landmark), - dismissThresholds: {DismissDirection.endToStart: 0.6}, - onDismissed: (direction) { - // Remove the item from the data source. - log(landmark.name); - setState(() { - widget.trip!.removeLandmark(landmark); - }); - // Then show a snackbar. - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text("We won't show ${landmark.name} again"))); - }, - background: Container(color: Colors.red), - secondaryBackground: Container( - color: Colors.red, - child: Icon( - Icons.delete, - color: Colors.white, - ), - padding: EdgeInsets.all(15), - alignment: Alignment.centerRight, - ), - ) - ); - if (landmark.next != null) { - Widget step = stepBetweenLandmarks(landmark, landmark.next!); - children.add(step); - } - } - return Column( - children: children - ); - }, - ); - } -} - - -Widget stepBetweenLandmarks(Landmark current, Landmark next) { - int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5; - // ~/ is integer division (rounding) - return Container( - margin: EdgeInsets.all(10), - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - border: Border( - left: BorderSide(width: 3.0, color: Colors.black), - ), - // gradient: LinearGradient( - // begin: Alignment.topLeft, - // end: Alignment.bottomRight, - // colors: [Colors.grey, Colors.white, Colors.white], - // ), - ), - child: Row( - children: [ - Column( - children: [ - Icon(Icons.directions_walk), - Text("~$timeRounded min", style: TextStyle(fontSize: 10)), - ], - ), - Spacer(), - ElevatedButton( - onPressed: () { - // Open navigation instructions - }, - child: Row( - children: [ - Icon(Icons.directions), - Text("Directions"), - ], - ), - ) - ], - ), - ); -} - - - diff --git a/frontend/lib/modules/map.dart b/frontend/lib/modules/map.dart index d47f88f..205eabd 100644 --- a/frontend/lib/modules/map.dart +++ b/frontend/lib/modules/map.dart @@ -1,6 +1,7 @@ import 'dart:collection'; -import 'dart:developer'; +import 'package:anyway/constants.dart'; +import 'package:anyway/modules/themed_marker.dart'; import 'package:flutter/material.dart'; import 'package:anyway/structs/landmark.dart'; import 'package:anyway/structs/trip.dart'; @@ -54,7 +55,7 @@ class _MapWidgetState extends State { Marker marker = Marker( markerId: MarkerId(landmark.uuid), position: LatLng(location[0], location[1]), - icon: await CustomMarker(landmark: landmark, position: i).toBitmapDescriptor( + icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor( logicalSize: const Size(150, 150), imageSize: const Size(150, 150) ), @@ -75,77 +76,7 @@ class _MapWidgetState extends State { onCameraIdle: _onCameraIdle, // onLongPress: , markers: mapMarkers, - cloudMapId: '41c21ac9b81dbfd8', + cloudMapId: MAP_ID, ); } } - - -class CustomMarker extends StatelessWidget { - final Landmark landmark; - final int position; - - CustomMarker({ - super.key, - required this.landmark, - required this.position - }); - - @override - Widget build(BuildContext context) { - // This returns an outlined circle, with an icon corresponding to the landmark type - // As a small dot, the number of the landmark is displayed in the top right - Icon icon; - if (landmark.type == sightseeing) { - icon = Icon(Icons.church, color: Colors.black, size: 50); - } else if (landmark.type == nature) { - icon = Icon(Icons.park, color: Colors.black, size: 50); - } else if (landmark.type == shopping) { - icon = Icon(Icons.shopping_cart, color: Colors.black, size: 50); - } else if (landmark.type == start || landmark.type == finish) { - icon = Icon(Icons.flag, color: Colors.black, size: 50); - } else { - icon = Icon(Icons.location_on, color: Colors.black, size: 50); - } - - Widget? positionIndicator; - if (landmark.type != start && landmark.type != finish) { - positionIndicator = Positioned( - top: 0, - right: 0, - child: Container( - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - shape: BoxShape.circle, - ), - child: Text('$position', style: TextStyle(color: Colors.white, fontSize: 20)), - ), - ); - } - - return RepaintBoundary( - child: Stack( - children: [ - Container( - // these are not the final sizes, since the final size is set in the toBitmapDescriptor method - // they are useful nevertheless to ensure the scale of the components are correct - width: 75, - height: 75, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.red, Colors.yellow] - ), - shape: BoxShape.circle, - border: Border.all(color: Colors.black, width: 5), - ), - child: icon, - ), - positionIndicator ?? Container(), - ], - ), - ); - } -} \ No newline at end of file diff --git a/frontend/lib/modules/new_trip_button.dart b/frontend/lib/modules/new_trip_button.dart new file mode 100644 index 0000000..0d1a479 --- /dev/null +++ b/frontend/lib/modules/new_trip_button.dart @@ -0,0 +1,78 @@ +import 'package:anyway/layout.dart'; +import 'package:anyway/structs/preferences.dart'; +import 'package:anyway/structs/trip.dart'; +import 'package:anyway/utils/fetch_trip.dart'; +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; + + +class NewTripButton extends StatefulWidget { + final Trip trip; + + const NewTripButton({required this.trip}); + + @override + State createState() => _NewTripButtonState(); +} + +class _NewTripButtonState extends State { + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: widget.trip, + builder: (BuildContext context, Widget? child) { + if (widget.trip.landmarks.isEmpty){ + return Container(); + } + return SizedBox( + width: 200, + child: ElevatedButton( + onPressed: () async { + Future preferences = loadUserPreferences(); + Trip trip = widget.trip; + fetchTrip(trip, preferences); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => BasePage(mainScreen: "map", trip: trip) + ) + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add, + ), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: 10, top: 5, bottom: 5, right: 5), + child: FutureBuilder( + future: widget.trip.cityName, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return AutoSizeText( + 'New trip to ${snapshot.data.toString()}', + style: TextStyle(fontSize: 18), + maxLines: 2, + ); + } else { + return AutoSizeText( + 'New trip to ...', + style: TextStyle(fontSize: 18), + maxLines: 2, + ); + } + }, + ) + ) + ) + ], + ), + ), + ); + } + ); + } +} + diff --git a/frontend/lib/modules/new_trip_location_search.dart b/frontend/lib/modules/new_trip_location_search.dart new file mode 100644 index 0000000..088d08f --- /dev/null +++ b/frontend/lib/modules/new_trip_location_search.dart @@ -0,0 +1,64 @@ + +// A search bar that allow the user to enter a city name +import 'package:anyway/structs/landmark.dart'; +import 'package:geocoding/geocoding.dart'; +import 'dart:developer'; + +import 'package:anyway/structs/trip.dart'; +import 'package:flutter/material.dart'; + +class NewTripLocationSearch extends StatefulWidget { + Trip trip; + NewTripLocationSearch( + this.trip, + ); + + + @override + State createState() => _NewTripLocationSearchState(); +} + +class _NewTripLocationSearchState extends State { + final TextEditingController _controller = TextEditingController(); + + setTripLocation (String query) async { + List locations = []; + log('Searching for: $query'); + + try{ + locations = await locationFromAddress(query); + } catch (e) { + log('No results found for: $query : $e'); + } + + if (locations.isNotEmpty) { + Location location = locations.first; + widget.trip.landmarks.clear(); + widget.trip.addLandmark( + Landmark( + uuid: 'pending', + name: query, + location: [location.latitude, location.longitude], + type: start + ) + ); + } + } + + @override + Widget build(BuildContext context) { + return SearchBar( + hintText: 'Enter a city name or long press on the map.', + onSubmitted: setTripLocation, + controller: _controller, + leading: Icon(Icons.search), + trailing: [ElevatedButton( + onPressed: () { + setTripLocation(_controller.text); + }, + child: Text('Search'), + ),] + + ); + } +} \ No newline at end of file diff --git a/frontend/lib/modules/new_trip_map.dart b/frontend/lib/modules/new_trip_map.dart new file mode 100644 index 0000000..d8232e8 --- /dev/null +++ b/frontend/lib/modules/new_trip_map.dart @@ -0,0 +1,86 @@ + +// A map that allows the user to select a location for a new trip. +import 'dart:developer'; + +import 'package:anyway/constants.dart'; +import 'package:anyway/modules/themed_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:widget_to_marker/widget_to_marker.dart'; + + +class NewTripMap extends StatefulWidget { + Trip trip; + NewTripMap( + this.trip, + ); + + @override + State createState() => _NewTripMapState(); +} + +class _NewTripMapState extends State { + final CameraPosition _cameraPosition = CameraPosition( + target: LatLng(48.8566, 2.3522), + zoom: 11.0, + ); + late GoogleMapController _mapController; + final Set _markers = {}; + + _onLongPress(LatLng location) { + log('Long press: $location'); + widget.trip.landmarks.clear(); + widget.trip.addLandmark( + Landmark( + uuid: 'pending', + name: 'start', + location: [location.latitude, location.longitude], + type: start + ) + ); + } + + updateTripDetails() async { + _markers.clear(); + if (widget.trip.landmarks.isNotEmpty) { + Landmark landmark = widget.trip.landmarks.first; + _markers.add( + Marker( + markerId: MarkerId(landmark.uuid), + position: LatLng(landmark.location[0], landmark.location[1]), + icon: await ThemedMarker(landmark: landmark, position: 0).toBitmapDescriptor( + logicalSize: const Size(150, 150), + imageSize: const Size(150, 150) + ), + ) + ); + _mapController.moveCamera( + CameraUpdate.newLatLng( + LatLng(landmark.location[0], landmark.location[1]) + ) + ); + setState(() {}); + } + } + + void _onMapCreated(GoogleMapController controller) async { + _mapController = controller; + } + + + + @override + Widget build(BuildContext context) { + widget.trip.addListener(updateTripDetails); + return GoogleMap( + onMapCreated: _onMapCreated, + initialCameraPosition: _cameraPosition, + onLongPress: _onLongPress, + markers: _markers, + cloudMapId: MAP_ID, + mapToolbarEnabled: false, + ); + } +} \ No newline at end of file diff --git a/frontend/lib/modules/save_button.dart b/frontend/lib/modules/save_button.dart new file mode 100644 index 0000000..d95c580 --- /dev/null +++ b/frontend/lib/modules/save_button.dart @@ -0,0 +1,33 @@ + +import 'package:anyway/structs/trip.dart'; +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Widget saveButton(Trip trip) => ElevatedButton( + onPressed: () async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + trip.toPrefs(prefs); + }, + child: SizedBox( + width: 100, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.save, + ), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: 10, top: 5, bottom: 5, right: 5), + child: AutoSizeText( + 'Save trip', + maxLines: 2, + ), + ), + ), + ], + ), + ) +); + diff --git a/frontend/lib/modules/step_between_landmarks.dart b/frontend/lib/modules/step_between_landmarks.dart new file mode 100644 index 0000000..4d0f992 --- /dev/null +++ b/frontend/lib/modules/step_between_landmarks.dart @@ -0,0 +1,60 @@ +import 'package:anyway/structs/landmark.dart'; +import 'package:flutter/material.dart'; + +class StepBetweenLandmarks extends StatefulWidget { + final Landmark current; + final Landmark next; + + const StepBetweenLandmarks({ + super.key, + required this.current, + required this.next + }); + + @override + State createState() => _StepBetweenLandmarksState(); +} + +class _StepBetweenLandmarksState extends State { + @override + Widget build(BuildContext context) { + int timeRounded = 5 * (widget.current.tripTime?.inMinutes ?? 0) ~/ 5; + // ~/ is integer division (rounding) + return Container( + margin: EdgeInsets.all(10), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + border: Border( + left: BorderSide(width: 3.0, color: Colors.black), + ), + // gradient: LinearGradient( + // begin: Alignment.topLeft, + // end: Alignment.bottomRight, + // colors: [Colors.grey, Colors.white, Colors.white], + // ), + ), + child: Row( + children: [ + Column( + children: [ + Icon(Icons.directions_walk), + Text("~$timeRounded min", style: TextStyle(fontSize: 10)), + ], + ), + Spacer(), + ElevatedButton( + onPressed: () { + // Open navigation instructions + }, + child: Row( + children: [ + Icon(Icons.directions), + Text("Directions"), + ], + ), + ) + ], + ), + ); + } +} diff --git a/frontend/lib/modules/themed_marker.dart b/frontend/lib/modules/themed_marker.dart new file mode 100644 index 0000000..c8b7ea2 --- /dev/null +++ b/frontend/lib/modules/themed_marker.dart @@ -0,0 +1,68 @@ +import 'package:anyway/structs/landmark.dart'; +import 'package:flutter/material.dart'; + + +class ThemedMarker extends StatelessWidget { + final Landmark landmark; + final int position; + + ThemedMarker({ + super.key, + required this.landmark, + required this.position + }); + + @override + Widget build(BuildContext context) { + // This returns an outlined circle, with an icon corresponding to the landmark type + // As a small dot, the number of the landmark is displayed in the top right + Icon icon; + if (landmark.type == sightseeing) { + icon = Icon(Icons.church, color: Colors.black, size: 50); + } else if (landmark.type == nature) { + icon = Icon(Icons.park, color: Colors.black, size: 50); + } else if (landmark.type == shopping) { + icon = Icon(Icons.shopping_cart, color: Colors.black, size: 50); + } else if (landmark.type == start || landmark.type == finish) { + icon = Icon(Icons.flag, color: Colors.black, size: 50); + } else { + icon = Icon(Icons.location_on, color: Colors.black, size: 50); + } + + Widget? positionIndicator; + if (landmark.type != start && landmark.type != finish) { + positionIndicator = Positioned( + top: 0, + right: 0, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + color: Colors.grey[100], + shape: BoxShape.circle, + ), + child: Text('$position', style: TextStyle(color: Colors.black, fontSize: 25)), + ), + ); + } + + return RepaintBoundary( + child: Stack( + alignment: Alignment.topRight, + children: [ + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.red, Colors.yellow] + ), + shape: BoxShape.circle, + border: Border.all(color: Colors.black, width: 5), + ), + padding: EdgeInsets.all(5), + child: icon + ), + if (positionIndicator != null) positionIndicator, + ], + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/modules/trips_overview.dart b/frontend/lib/modules/trips_saved_list.dart similarity index 100% rename from frontend/lib/modules/trips_overview.dart rename to frontend/lib/modules/trips_saved_list.dart diff --git a/frontend/lib/pages/new_trip.dart b/frontend/lib/pages/new_trip.dart index f3e97d3..78d6cef 100644 --- a/frontend/lib/pages/new_trip.dart +++ b/frontend/lib/pages/new_trip.dart @@ -1,3 +1,4 @@ +import 'package:anyway/modules/new_trip_button.dart'; import 'package:anyway/structs/landmark.dart'; import 'package:flutter/material.dart'; import 'package:geocoding/geocoding.dart'; @@ -6,7 +7,8 @@ import 'package:anyway/layout.dart'; import 'package:anyway/utils/fetch_trip.dart'; import 'package:anyway/structs/preferences.dart'; import "package:anyway/structs/trip.dart"; - +import 'package:anyway/modules/new_trip_location_search.dart'; +import 'package:anyway/modules/new_trip_map.dart'; class NewTripPage extends StatefulWidget { @@ -20,74 +22,31 @@ class _NewTripPageState extends State { final GlobalKey _formKey = GlobalKey(); final TextEditingController latController = TextEditingController(); final TextEditingController lonController = TextEditingController(); + Trip trip = Trip(); @override Widget build(BuildContext context) { + // floating search bar and map as a background return Scaffold( appBar: AppBar( title: const Text('New Trip'), ), - body: Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.all(15.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - - children: [ - TextFormField( - decoration: const InputDecoration(hintText: 'Lat'), - controller: latController, - validator: (String? value) { - if (value == null || value.isEmpty || double.tryParse(value) == null){ - return 'Please enter a floating point number'; - } - return null; - }, - ), - TextFormField( - decoration: const InputDecoration(hintText: 'Lon'), - controller: lonController, - - validator: (String? value) { - if (value == null || value.isEmpty || double.tryParse(value) == null){ - return 'Please enter a floating point number'; - } - return null; - }, - ), - Divider(height: 15, color: Colors.transparent), - ElevatedButton( - child: const Text('Create trip'), - onPressed: () { - if (_formKey.currentState!.validate()) { - List startPoint = [ - double.parse(latController.text), - double.parse(lonController.text) - ]; - Future preferences = loadUserPreferences(); - Trip trip = Trip(); - trip.landmarks.add( - Landmark( - location: startPoint, - name: "Start", - type: start, - uuid: "pending" - ) - ); - fetchTrip(trip, preferences); - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => BasePage(mainScreen: "map", trip: trip) - ) - ); - } - }, - ), - ], + body: Stack( + children: [ + NewTripMap(trip), + Padding( + padding: EdgeInsets.all(15), + child: NewTripLocationSearch(trip), ), - ) - ) + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.all(15), + child: NewTripButton(trip: trip) + ), + ), + ], + ), ); } } diff --git a/frontend/lib/pages/overview.dart b/frontend/lib/pages/overview.dart deleted file mode 100644 index e98d91b..0000000 --- a/frontend/lib/pages/overview.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sliding_up_panel/sliding_up_panel.dart'; - -import 'package:anyway/structs/trip.dart'; - -import 'package:anyway/modules/landmarks_overview.dart'; -import 'package:anyway/modules/map.dart'; -import 'package:anyway/modules/greeter.dart'; - - - -class NavigationOverview extends StatefulWidget { - final Trip trip; - - NavigationOverview({ - required this.trip, - }); - - @override - State createState() => _NavigationOverviewState(); -} - - - -class _NavigationOverviewState extends State { - - @override - Widget build(BuildContext context) { - return SlidingUpPanel( - panel: _floatingPanel(), - // collapsed: _floatingCollapsed(), - 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(){ - return Greeter( - trip: widget.trip - ); - } - - Widget _floatingPanel(){ - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(15), - child: - Center( - child: Container( - width: 40, - height: 5, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.all(Radius.circular(12.0)), - ), - ), - ), - ), - Expanded( - child: ListView( - children: [ - Greeter(trip: widget.trip), - LandmarksOverview(trip: widget.trip) - ] - ) - ) - ], - ); - } -} diff --git a/frontend/lib/pages/trip.dart b/frontend/lib/pages/trip.dart new file mode 100644 index 0000000..2d6e58e --- /dev/null +++ b/frontend/lib/pages/trip.dart @@ -0,0 +1,84 @@ +import 'package:anyway/modules/save_button.dart'; +import 'package:flutter/material.dart'; +import 'package:sliding_up_panel/sliding_up_panel.dart'; + +import 'package:anyway/structs/trip.dart'; +import 'package:anyway/modules/landmarks_list.dart'; +import 'package:anyway/modules/greeter.dart'; +import 'package:anyway/modules/map.dart'; + + + +class TripPage extends StatefulWidget { + final Trip trip; + + TripPage({ + required this.trip, + }); + + @override + State createState() => _TripPageState(); +} + + + +class _TripPageState extends State { + + @override + Widget build(BuildContext context) { + return SlidingUpPanel( + panelBuilder: (sc) => _panelFull(sc), + // collapsed: _floatingCollapsed(), + body: MapWidget(trip: widget.trip), + // renderPanelSheet: false, + // backdropEnabled: true, + maxHeight: MediaQuery.of(context).size.height * 0.8, + padding: EdgeInsets.only(left: 10, right: 10, top: 25, bottom: 10), + // panelSnapping: false, + borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), + boxShadow: [ + BoxShadow( + blurRadius: 20.0, + color: Colors.black, + ) + ], + ); + } + + + Widget _panelFull(ScrollController sc) { + return ListenableBuilder( + listenable: widget.trip, + builder: (context, child) { + if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') { + return ListView( + controller: sc, + padding: EdgeInsets.only(bottom: 35), + children: [ + Greeter(trip: widget.trip), + ...landmarksList(widget.trip), + Padding(padding: EdgeInsets.only(top: 10)), + Center(child: saveButton(widget.trip)), + ], + ); + } else if(widget.trip.uuid == 'pending') { + return Greeter(trip: widget.trip); + } else { + return Column( + children: [ + const Icon( + Icons.error_outline, + color: Colors.red, + size: 60, + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: Text('Error: ${widget.trip.errorDescription}'), + ), + ], + ); + } + } + ); + } +} diff --git a/frontend/lib/structs/trip.dart b/frontend/lib/structs/trip.dart index ec0f228..296ba25 100644 --- a/frontend/lib/structs/trip.dart +++ b/frontend/lib/structs/trip.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'dart:convert'; +import 'dart:developer'; import 'package:anyway/structs/landmark.dart'; import 'package:flutter/foundation.dart'; @@ -75,8 +76,11 @@ class Trip with ChangeNotifier { String? content = prefs.getString('trip_$uuid'); Map json = jsonDecode(content!); Trip trip = Trip.fromJson(json); - String? firstUUID = json['entry_uuid']; - readLandmarks(trip.landmarks, prefs, firstUUID); + String? firstUUID = json['first_landmark_uuid']; + log('Loading trip $uuid with first landmark $firstUUID'); + LinkedList landmarks = readLandmarks(prefs, firstUUID); + trip.landmarks = landmarks; + // notifyListeners(); return trip; } @@ -90,6 +94,7 @@ class Trip with ChangeNotifier { void toPrefs(SharedPreferences prefs){ Map json = toJson(); + log('Saving trip $uuid : $json'); prefs.setString('trip_$uuid', jsonEncode(json)); for (Landmark landmark in landmarks) { landmarkToPrefs(prefs, landmark, landmark.next); @@ -99,12 +104,14 @@ class Trip with ChangeNotifier { // Helper -readLandmarks(LinkedList landmarks, SharedPreferences prefs, String? firstUUID) { +LinkedList readLandmarks(SharedPreferences prefs, String? firstUUID) { + LinkedList landmarks = LinkedList(); while (firstUUID != null) { var (head, nextUUID) = getLandmarkFromPrefs(prefs, firstUUID); landmarks.add(head); firstUUID = nextUUID; } + return landmarks; }