location picker and ui fixes
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build and release APK / Build APK (pull_request) Successful in 5m25s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build and release APK / Build APK (pull_request) Successful in 5m25s
				
			This commit is contained in:
		@@ -2,3 +2,4 @@ const String APP_NAME = 'AnyWay';
 | 
			
		||||
 | 
			
		||||
String API_URL_BASE = 'https://anyway.kluster.moll.re';
 | 
			
		||||
 | 
			
		||||
const String MAP_ID = '41c21ac9b81dbfd8';
 | 
			
		||||
 
 | 
			
		||||
@@ -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<BasePage> {
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    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") {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ import 'package:anyway/layout.dart';
 | 
			
		||||
 | 
			
		||||
void main() => runApp(const App());
 | 
			
		||||
 | 
			
		||||
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<LandmarkCard> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										64
									
								
								frontend/lib/modules/landmarks_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/lib/modules/landmarks_list.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<Widget> landmarksList(Trip trip) {
 | 
			
		||||
  log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
 | 
			
		||||
  
 | 
			
		||||
  List<Widget> 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<int>(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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -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<LandmarksOverview> createState() => _LandmarksOverviewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _LandmarksOverviewState extends State<LandmarksOverview> {
 | 
			
		||||
 | 
			
		||||
  @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<Widget> 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<Widget> children = [];
 | 
			
		||||
        for (Landmark landmark in widget.trip!.landmarks) {
 | 
			
		||||
          children.add(
 | 
			
		||||
            Dismissible(
 | 
			
		||||
              key: ValueKey<int>(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"),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -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<MapWidget> {
 | 
			
		||||
      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<MapWidget> {
 | 
			
		||||
      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(),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								frontend/lib/modules/new_trip_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								frontend/lib/modules/new_trip_button.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<NewTripButton> createState() => _NewTripButtonState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _NewTripButtonState extends State<NewTripButton> {
 | 
			
		||||
 | 
			
		||||
  @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<UserPreferences> 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,
 | 
			
		||||
                          );
 | 
			
		||||
                        }
 | 
			
		||||
                      },
 | 
			
		||||
                    )
 | 
			
		||||
                  )
 | 
			
		||||
                )
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      } 
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								frontend/lib/modules/new_trip_location_search.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/lib/modules/new_trip_location_search.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<NewTripLocationSearch> createState() => _NewTripLocationSearchState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
 | 
			
		||||
  final TextEditingController _controller = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  setTripLocation (String query) async {
 | 
			
		||||
    List<Location> 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'),
 | 
			
		||||
      ),]
 | 
			
		||||
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								frontend/lib/modules/new_trip_map.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								frontend/lib/modules/new_trip_map.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<NewTripMap> createState() => _NewTripMapState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _NewTripMapState extends State<NewTripMap> {
 | 
			
		||||
  final CameraPosition _cameraPosition = CameraPosition(
 | 
			
		||||
    target: LatLng(48.8566, 2.3522),
 | 
			
		||||
    zoom: 11.0,
 | 
			
		||||
  );
 | 
			
		||||
  late GoogleMapController _mapController;
 | 
			
		||||
  final Set<Marker> _markers = <Marker>{};
 | 
			
		||||
 | 
			
		||||
  _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,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								frontend/lib/modules/save_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								frontend/lib/modules/save_button.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    ),
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										60
									
								
								frontend/lib/modules/step_between_landmarks.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								frontend/lib/modules/step_between_landmarks.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<StepBetweenLandmarks> createState() => _StepBetweenLandmarksState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> {
 | 
			
		||||
  @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"),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								frontend/lib/modules/themed_marker.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								frontend/lib/modules/themed_marker.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<NewTripPage> {
 | 
			
		||||
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
 | 
			
		||||
  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: <Widget>[
 | 
			
		||||
              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<double> startPoint = [
 | 
			
		||||
                      double.parse(latController.text),
 | 
			
		||||
                      double.parse(lonController.text)
 | 
			
		||||
                    ];
 | 
			
		||||
                    Future<UserPreferences> 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)
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<NavigationOverview> createState() => _NavigationOverviewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _NavigationOverviewState extends State<NavigationOverview> {
 | 
			
		||||
 | 
			
		||||
  @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)
 | 
			
		||||
            ]
 | 
			
		||||
          )
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								frontend/lib/pages/trip.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								frontend/lib/pages/trip.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<TripPage> createState() => _TripPageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _TripPageState extends State<TripPage> {
 | 
			
		||||
 | 
			
		||||
  @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}'),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<String, dynamic> 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<Landmark> landmarks = readLandmarks(prefs, firstUUID);
 | 
			
		||||
    trip.landmarks = landmarks;
 | 
			
		||||
    // notifyListeners();
 | 
			
		||||
    return trip;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -90,6 +94,7 @@ class Trip with ChangeNotifier {
 | 
			
		||||
 | 
			
		||||
  void toPrefs(SharedPreferences prefs){
 | 
			
		||||
    Map<String, dynamic> 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<Landmark> landmarks, SharedPreferences prefs, String? firstUUID) {
 | 
			
		||||
LinkedList<Landmark> readLandmarks(SharedPreferences prefs, String? firstUUID) {
 | 
			
		||||
  LinkedList<Landmark> landmarks = LinkedList<Landmark>();
 | 
			
		||||
  while (firstUUID != null) {
 | 
			
		||||
    var (head, nextUUID) = getLandmarkFromPrefs(prefs, firstUUID);
 | 
			
		||||
    landmarks.add(head);
 | 
			
		||||
    firstUUID = nextUUID;
 | 
			
		||||
  }
 | 
			
		||||
  return landmarks;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user