Big overhaul of the UI and usability of the app #61
@@ -7,14 +7,11 @@ import 'package:anyway/structs/landmark.dart';
 | 
				
			|||||||
import 'package:anyway/structs/trip.dart';
 | 
					import 'package:anyway/structs/trip.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns a list of widgets that represent the landmarks matching the given selector
 | 
				
			||||||
List<Widget> landmarksList(Trip trip) {
 | 
					List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector}) {
 | 
				
			||||||
  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"),
 | 
				
			||||||
@@ -23,14 +20,16 @@ List<Widget> landmarksList(Trip trip) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (Landmark landmark in trip.landmarks) {
 | 
					  for (Landmark landmark in trip.landmarks) {
 | 
				
			||||||
    children.add(
 | 
					    if (selector(landmark)) {
 | 
				
			||||||
      LandmarkCard(landmark, trip),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (landmark.next != null) {
 | 
					 | 
				
			||||||
      children.add(
 | 
					      children.add(
 | 
				
			||||||
        StepBetweenLandmarks(current: landmark, next: landmark.next!)
 | 
					        LandmarkCard(landmark, trip),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!landmark.visited && landmark.next != null) {
 | 
				
			||||||
 | 
					        children.add(
 | 
				
			||||||
 | 
					          StepBetweenLandmarks(current: landmark, next: landmark.next!)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,14 +9,10 @@ import 'package:google_maps_flutter/google_maps_flutter.dart';
 | 
				
			|||||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
					import 'package:shared_preferences/shared_preferences.dart';
 | 
				
			||||||
import 'package:widget_to_marker/widget_to_marker.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({
 | 
					  CurrentTripMap({this.trip});
 | 
				
			||||||
    this.trip
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<CurrentTripMap> createState() => _CurrentTripMapState();
 | 
					  State<CurrentTripMap> createState() => _CurrentTripMapState();
 | 
				
			||||||
@@ -30,7 +26,23 @@ 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;
 | 
				
			||||||
@@ -40,16 +52,17 @@ 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 {
 | 
				
			||||||
    List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? [];
 | 
					    List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? [];
 | 
				
			||||||
    Set<Marker> newMarkers = <Marker>{};
 | 
					    Set<Marker> markers = <Marker>{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (int i = 0; i < landmarks.length; i++) {
 | 
					    for (int i = 0; i < landmarks.length; i++) {
 | 
				
			||||||
      Landmark landmark = landmarks[i];
 | 
					      Landmark landmark = landmarks[i];
 | 
				
			||||||
      List<double> location = landmark.location;
 | 
					      List<double> location = landmark.location;
 | 
				
			||||||
@@ -58,20 +71,47 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
 | 
				
			|||||||
        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);
 | 
					      markers.add(marker);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    setState(() {
 | 
					    setState(() {
 | 
				
			||||||
      mapMarkers = newMarkers;
 | 
					      mapMarkers = markers;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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 ? 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(
 | 
				
			||||||
@@ -84,7 +124,7 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return const CircularProgressIndicator();
 | 
					          return const CircularProgressIndicator();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -93,8 +133,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,
 | 
				
			||||||
@@ -102,5 +142,4 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
 | 
				
			|||||||
      myLocationButtonEnabled: false,
 | 
					      myLocationButtonEnabled: false,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import 'package:anyway/constants.dart';
 | 
					import 'package:anyway/constants.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:anyway/structs/landmark.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:anyway/structs/trip.dart';
 | 
					import 'package:anyway/structs/trip.dart';
 | 
				
			||||||
@@ -63,13 +64,40 @@ 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),
 | 
				
			||||||
 | 
					                      ExpansionTile(
 | 
				
			||||||
 | 
					                        leading: Icon(Icons.location_on),
 | 
				
			||||||
 | 
					                        title: Text('Visited Landmarks (tap to expand)'),
 | 
				
			||||||
 | 
					                        children: [
 | 
				
			||||||
 | 
					                          ...landmarksList(widget.trip, selector: (Landmark landmark) => landmark.visited),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        visualDensity: VisualDensity.compact,
 | 
				
			||||||
 | 
					                        collapsedShape: RoundedRectangleBorder(
 | 
				
			||||||
 | 
					                          borderRadius: BorderRadius.circular(10),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        shape: RoundedRectangleBorder(
 | 
				
			||||||
 | 
					                          borderRadius: BorderRadius.circular(10),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              const Padding(padding: EdgeInsets.only(top: 10)),
 | 
					              const Padding(padding: EdgeInsets.only(top: 10)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              // CurrentTripSummary(trip: widget.trip),
 | 
					              // upcoming landmarks
 | 
				
			||||||
 | 
					              ...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)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,17 +15,27 @@ 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 Column(
 | 
					    return Padding(
 | 
				
			||||||
      children: [
 | 
					      padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20),
 | 
				
			||||||
        Text('Summary'),
 | 
					      child: Row(
 | 
				
			||||||
        // Text('Start: ${widget.trip.start}'),
 | 
					        mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
				
			||||||
        // Text('End: ${widget.trip.end}'),
 | 
					        children: [
 | 
				
			||||||
        Text('Total duration: ${widget.trip.totalTime}'),
 | 
					          Row(
 | 
				
			||||||
        Text('Total distance: ${widget.trip.totalTime}'),
 | 
					            children: [
 | 
				
			||||||
        // Text('Fuel: ${widget.trip.fuel}'),
 | 
					              Icon(Icons.flag, size: 20),
 | 
				
			||||||
        // Text('Cost: ${widget.trip.cost}'),
 | 
					              Padding(padding: EdgeInsets.only(right: 10)),
 | 
				
			||||||
      ],
 | 
					              Text('Stops: ${widget.trip.landmarks.length}', style: Theme.of(context).textTheme.bodyLarge,),
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          Row(
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					              Icon(Icons.hourglass_bottom_rounded, size: 20),
 | 
				
			||||||
 | 
					              Padding(padding: EdgeInsets.only(right: 10)),
 | 
				
			||||||
 | 
					              Text('Duration: ${widget.trip.totalTime} minutes', style: Theme.of(context).textTheme.bodyLarge,),
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					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:flutter/material.dart';
 | 
				
			||||||
@@ -13,7 +14,7 @@ class LandmarkCard extends StatefulWidget {
 | 
				
			|||||||
  LandmarkCard(
 | 
					  LandmarkCard(
 | 
				
			||||||
    this.landmark,
 | 
					    this.landmark,
 | 
				
			||||||
    this.parentTrip,
 | 
					    this.parentTrip,
 | 
				
			||||||
    );
 | 
					  );
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  _LandmarkCardState createState() => _LandmarkCardState();
 | 
					  _LandmarkCardState createState() => _LandmarkCardState();
 | 
				
			||||||
@@ -31,142 +32,176 @@ class _LandmarkCardState extends State<LandmarkCard> {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // else:
 | 
					
 | 
				
			||||||
    return Container(
 | 
					    return Container(
 | 
				
			||||||
 | 
					      constraints: BoxConstraints(
 | 
				
			||||||
 | 
					        minHeight: 50,
 | 
				
			||||||
 | 
					        maxHeight: 200,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
      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: widget.landmark.imageURL != null ? splitLayout() : textLayout(),
 | 
					        child: Row(
 | 
				
			||||||
      ),
 | 
					          crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
    );
 | 
					          children: [
 | 
				
			||||||
  }
 | 
					            // Image and landmark "type" on the left
 | 
				
			||||||
 | 
					            AspectRatio(
 | 
				
			||||||
  Widget splitLayout() {
 | 
					              aspectRatio: 3 / 4,
 | 
				
			||||||
    // If an image is available, display it on the left side of the card
 | 
					              child: Column(
 | 
				
			||||||
    return Row(
 | 
					 | 
				
			||||||
      crossAxisAlignment: CrossAxisAlignment.start,
 | 
					 | 
				
			||||||
      children: [
 | 
					 | 
				
			||||||
        Container(
 | 
					 | 
				
			||||||
          // the image on the left
 | 
					 | 
				
			||||||
          width: 160,
 | 
					 | 
				
			||||||
          height: 160,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          child: CachedNetworkImage(
 | 
					 | 
				
			||||||
            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(
 | 
					 | 
				
			||||||
        children: [
 | 
					 | 
				
			||||||
          Row(
 | 
					 | 
				
			||||||
            children: [
 | 
					 | 
				
			||||||
              Flexible(
 | 
					 | 
				
			||||||
                child: Text(
 | 
					 | 
				
			||||||
                  widget.landmark.name,
 | 
					 | 
				
			||||||
                  style: const TextStyle(
 | 
					 | 
				
			||||||
                    fontSize: 18,
 | 
					 | 
				
			||||||
                    fontWeight: FontWeight.bold,
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                  maxLines: 2,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              )
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
          if (widget.landmark.nameEN != null)
 | 
					 | 
				
			||||||
            Row(
 | 
					 | 
				
			||||||
              children: [
 | 
					 | 
				
			||||||
                Flexible(
 | 
					 | 
				
			||||||
                  child: Text(
 | 
					 | 
				
			||||||
                    widget.landmark.nameEN!,
 | 
					 | 
				
			||||||
                    style: const TextStyle(
 | 
					 | 
				
			||||||
                      fontSize: 16,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    maxLines: 1,
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
              ],
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          Padding(padding: EdgeInsets.only(top: 10)),
 | 
					 | 
				
			||||||
          Align(
 | 
					 | 
				
			||||||
            alignment: Alignment.centerLeft,
 | 
					 | 
				
			||||||
            child: SingleChildScrollView(
 | 
					 | 
				
			||||||
              // allows the buttons to be scrolled
 | 
					 | 
				
			||||||
              scrollDirection: Axis.horizontal,
 | 
					 | 
				
			||||||
              child: Wrap(
 | 
					 | 
				
			||||||
                spacing: 10,
 | 
					 | 
				
			||||||
                // show the type, the website, and the wikipedia link as buttons/labels in a row
 | 
					 | 
				
			||||||
                children: [
 | 
					                children: [
 | 
				
			||||||
                  TextButton.icon(
 | 
					                  if (widget.landmark.imageURL != null && widget.landmark.imageURL!.isNotEmpty)
 | 
				
			||||||
                    onPressed: () {},
 | 
					                    Expanded(
 | 
				
			||||||
                    icon: widget.landmark.type.icon,
 | 
					                      child: CachedNetworkImage(
 | 
				
			||||||
                    label: Text(widget.landmark.type.name),
 | 
					                        imageUrl: widget.landmark.imageURL!,
 | 
				
			||||||
                  ),
 | 
					                        placeholder: (context, url) => Center(child: CircularProgressIndicator()),
 | 
				
			||||||
                  if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0)
 | 
					                        errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
 | 
				
			||||||
                    TextButton.icon(
 | 
					                        fit: BoxFit.cover,
 | 
				
			||||||
                      onPressed: () {},
 | 
					                      )
 | 
				
			||||||
                      icon: Icon(Icons.hourglass_bottom),
 | 
					                    )
 | 
				
			||||||
                      label: Text('${widget.landmark.duration!.inMinutes} minutes'),
 | 
					                  else
 | 
				
			||||||
                    ),
 | 
					                    Expanded(
 | 
				
			||||||
                  if (widget.landmark.websiteURL != null)
 | 
					                      child: 
 | 
				
			||||||
                    TextButton.icon(
 | 
					                      Container(
 | 
				
			||||||
                      onPressed: () async {
 | 
					                        decoration: BoxDecoration(
 | 
				
			||||||
                        // open a browser with the website link
 | 
					                          gradient: LinearGradient(
 | 
				
			||||||
                        await launchUrl(Uri.parse(widget.landmark.websiteURL!));
 | 
					                            begin: Alignment.topLeft,
 | 
				
			||||||
                      },
 | 
					                            end: Alignment.bottomRight,
 | 
				
			||||||
                      icon: Icon(Icons.link),
 | 
					                            colors: [GRADIENT_START, GRADIENT_END],
 | 
				
			||||||
                      label: Text('Website'),
 | 
					                          ),
 | 
				
			||||||
                    ),
 | 
					                        ),
 | 
				
			||||||
                  PopupMenuButton(
 | 
					                        child: Center(
 | 
				
			||||||
                    icon: Icon(Icons.settings),
 | 
					                          child: Icon(widget.landmark.type.icon.icon, size: 50),
 | 
				
			||||||
                    style: TextButtonTheme.of(context).style,
 | 
					 | 
				
			||||||
                    itemBuilder: (context) => [
 | 
					 | 
				
			||||||
                      PopupMenuItem(
 | 
					 | 
				
			||||||
                        child: ListTile(
 | 
					 | 
				
			||||||
                          leading: Icon(Icons.delete),
 | 
					 | 
				
			||||||
                          title: Text('Delete'),
 | 
					 | 
				
			||||||
                          onTap: () async {
 | 
					 | 
				
			||||||
                            widget.parentTrip.removeLandmark(widget.landmark);
 | 
					 | 
				
			||||||
                            rootScaffoldMessengerKey.currentState!.showSnackBar(
 | 
					 | 
				
			||||||
                              SnackBar(content: Text("We won't show ${widget.landmark.name} again"))
 | 
					 | 
				
			||||||
                            );
 | 
					 | 
				
			||||||
                          },
 | 
					 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                      PopupMenuItem(
 | 
					                    ),
 | 
				
			||||||
                        child: ListTile(
 | 
					 | 
				
			||||||
                          leading: Icon(Icons.star),
 | 
					 | 
				
			||||||
                          title: Text('Favorite'),
 | 
					 | 
				
			||||||
                          onTap: () async {
 | 
					 | 
				
			||||||
                            // delete the landmark
 | 
					 | 
				
			||||||
                            // await deleteLandmark(widget.landmark);
 | 
					 | 
				
			||||||
                          },
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                  )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  Container(
 | 
				
			||||||
 | 
					                    color: PRIMARY_COLOR,
 | 
				
			||||||
 | 
					                    child: Center(
 | 
				
			||||||
 | 
					                      child: Padding(
 | 
				
			||||||
 | 
					                        padding: EdgeInsets.all(5),
 | 
				
			||||||
 | 
					                        child: Row(
 | 
				
			||||||
 | 
					                          mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
 | 
					                          spacing: 5,
 | 
				
			||||||
 | 
					                          children: [
 | 
				
			||||||
 | 
					                            Icon(widget.landmark.type.icon.icon, size: 16),
 | 
				
			||||||
 | 
					                            Text(widget.landmark.type.name, style: TextStyle(fontWeight: FontWeight.bold)),
 | 
				
			||||||
 | 
					                          ],
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
              ),
 | 
					              )
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					
 | 
				
			||||||
        ],
 | 
					            // Main information, useful buttons on the right
 | 
				
			||||||
      ),
 | 
					            Expanded(
 | 
				
			||||||
 | 
					              child: Padding(
 | 
				
			||||||
 | 
					                padding: const EdgeInsets.all(10),
 | 
				
			||||||
 | 
					                child: Column(
 | 
				
			||||||
 | 
					                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    Text(
 | 
				
			||||||
 | 
					                      widget.landmark.name,
 | 
				
			||||||
 | 
					                      style: Theme.of(context).textTheme.titleMedium,
 | 
				
			||||||
 | 
					                      overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					                      maxLines: 2,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                      
 | 
				
			||||||
 | 
					                    if (widget.landmark.nameEN != null)
 | 
				
			||||||
 | 
					                      Text(
 | 
				
			||||||
 | 
					                        widget.landmark.nameEN!,
 | 
				
			||||||
 | 
					                        style: Theme.of(context).textTheme.bodyMedium,
 | 
				
			||||||
 | 
					                        maxLines: 1,
 | 
				
			||||||
 | 
					                        overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // fill the vspace
 | 
				
			||||||
 | 
					                    const Spacer(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    SingleChildScrollView(
 | 
				
			||||||
 | 
					                      // allows the buttons to be scrolled
 | 
				
			||||||
 | 
					                      scrollDirection: Axis.horizontal,
 | 
				
			||||||
 | 
					                      child: Wrap(
 | 
				
			||||||
 | 
					                        spacing: 10,
 | 
				
			||||||
 | 
					                        // show the type, the website, and the wikipedia link as buttons/labels in a row
 | 
				
			||||||
 | 
					                        children: [
 | 
				
			||||||
 | 
					                          doneToggleButton(),
 | 
				
			||||||
 | 
					                          // if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0)
 | 
				
			||||||
 | 
					                          //   TextButton.icon(
 | 
				
			||||||
 | 
					                          //     onPressed: () {},
 | 
				
			||||||
 | 
					                          //     icon: Icon(Icons.hourglass_bottom),
 | 
				
			||||||
 | 
					                          //     label: Text('${widget.landmark.duration!.inMinutes} minutes'),
 | 
				
			||||||
 | 
					                          //   ),
 | 
				
			||||||
 | 
					                          if (widget.landmark.websiteURL != null)
 | 
				
			||||||
 | 
					                            websiteButton(),
 | 
				
			||||||
 | 
					                          
 | 
				
			||||||
 | 
					                          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 {
 | 
				
			||||||
 | 
					      // open a browser with the website link
 | 
				
			||||||
 | 
					      await launchUrl(Uri.parse(widget.landmark.websiteURL!));
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    icon: Icon(Icons.link),
 | 
				
			||||||
 | 
					    label: Text('Website'),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget optionsButton () => PopupMenuButton(
 | 
				
			||||||
 | 
					    icon: Icon(Icons.settings),
 | 
				
			||||||
 | 
					    style: TextButtonTheme.of(context).style,
 | 
				
			||||||
 | 
					    itemBuilder: (context) => [
 | 
				
			||||||
 | 
					      PopupMenuItem(
 | 
				
			||||||
 | 
					        child: ListTile(
 | 
				
			||||||
 | 
					          leading: Icon(Icons.delete),
 | 
				
			||||||
 | 
					          title: Text('Delete'),
 | 
				
			||||||
 | 
					          onTap: () async {
 | 
				
			||||||
 | 
					            widget.parentTrip.removeLandmark(widget.landmark);
 | 
				
			||||||
 | 
					            rootScaffoldMessengerKey.currentState!.showSnackBar(
 | 
				
			||||||
 | 
					              SnackBar(content: Text("We won't show ${widget.landmark.name} again"))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      PopupMenuItem(
 | 
				
			||||||
 | 
					        child: ListTile(
 | 
				
			||||||
 | 
					          leading: Icon(Icons.star),
 | 
				
			||||||
 | 
					          title: Text('Favorite'),
 | 
				
			||||||
 | 
					          onTap: () async {
 | 
				
			||||||
 | 
					            // delete the landmark
 | 
				
			||||||
 | 
					            // await deleteLandmark(widget.landmark);
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,7 +40,7 @@ class ThemedMarker extends StatelessWidget {
 | 
				
			|||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          Container(
 | 
					          Container(
 | 
				
			||||||
            decoration: BoxDecoration(
 | 
					            decoration: BoxDecoration(
 | 
				
			||||||
              gradient: APP_GRADIENT,
 | 
					              gradient: landmark.visited ? LinearGradient(colors: [Colors.grey, Colors.white]) : APP_GRADIENT,
 | 
				
			||||||
              shape: BoxShape.circle,
 | 
					              shape: BoxShape.circle,
 | 
				
			||||||
              border: Border.all(color: Colors.black, width: 5),
 | 
					              border: Border.all(color: Colors.black, width: 5),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ 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;
 | 
				
			||||||
  final bool? visited;
 | 
					  bool visited;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Next node
 | 
					  // Next node
 | 
				
			||||||
  // final Landmark? next;
 | 
					  // final Landmark? next;
 | 
				
			||||||
@@ -46,7 +46,7 @@ final class Landmark extends LinkedListEntry<Landmark>{
 | 
				
			|||||||
    this.imageURL,
 | 
					    this.imageURL,
 | 
				
			||||||
    this.description,
 | 
					    this.description,
 | 
				
			||||||
    this.duration,
 | 
					    this.duration,
 | 
				
			||||||
    this.visited,
 | 
					    this.visited = false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // this.next,
 | 
					    // this.next,
 | 
				
			||||||
    this.tripTime,
 | 
					    this.tripTime,
 | 
				
			||||||
@@ -71,7 +71,7 @@ final class Landmark extends LinkedListEntry<Landmark>{
 | 
				
			|||||||
      final imageURL = json['image_url'] as String?;
 | 
					      final imageURL = json['image_url'] as String?;
 | 
				
			||||||
      final description = json['description'] as String?;
 | 
					      final description = json['description'] as String?;
 | 
				
			||||||
      var duration = Duration(minutes: json['duration'] ?? 0) as Duration?;
 | 
					      var duration = Duration(minutes: json['duration'] ?? 0) as Duration?;
 | 
				
			||||||
      final visited = json['visited'] as bool?;
 | 
					      final visited = json['visited'] ?? false 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(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,6 +52,7 @@ 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();
 | 
				
			||||||
@@ -72,6 +73,10 @@ 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!);
 | 
				
			||||||
@@ -80,7 +85,6 @@ 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;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user