From ed60fcba064378ee116a1d1b522455cf4500103b Mon Sep 17 00:00:00 2001
From: Remy Moll <me@moll.re>
Date: Tue, 24 Sep 2024 22:58:28 +0200
Subject: [PATCH 1/4] revamp new trip flow

---
 .../android/app/src/main/AndroidManifest.xml  |   3 +
 frontend/android/gradlew.bat                  |  90 +++++++++
 frontend/devtools_options.yaml                |   3 +
 frontend/lib/constants.dart                   |  58 ++++++
 frontend/lib/layout.dart                      |  26 +--
 frontend/lib/main.dart                        |   2 +-
 .../modules/current_trip_landmarks_list.dart  |   1 -
 frontend/lib/modules/current_trip_map.dart    |   2 +-
 frontend/lib/modules/landmark_card.dart       |  12 +-
 frontend/lib/modules/new_trip_button.dart     |  68 +++----
 .../lib/modules/new_trip_location_search.dart |  54 +++++-
 frontend/lib/modules/new_trip_map.dart        |   6 +-
 .../lib/modules/new_trip_options_button.dart  |  41 ++++
 frontend/lib/modules/onboarding_card.dart     |   8 +-
 frontend/lib/modules/themed_marker.dart       |   5 +-
 .../{new_trip.dart => new_trip_location.dart} |   9 +-
 frontend/lib/pages/new_trip_preferences.dart  | 114 +++++++++++
 frontend/lib/pages/onboarding.dart            |   2 +-
 frontend/lib/pages/profile.dart               | 177 ------------------
 frontend/lib/pages/settings.dart              | 177 ++++++++++++++++++
 frontend/lib/structs/preferences.dart         |  36 +---
 frontend/lib/utils/fetch_trip.dart            |  18 +-
 frontend/pubspec.lock                         |  48 +++++
 frontend/pubspec.yaml                         |   1 +
 .../flutter/generated_plugin_registrant.cc    |   3 +
 .../windows/flutter/generated_plugins.cmake   |   1 +
 26 files changed, 663 insertions(+), 302 deletions(-)
 create mode 100755 frontend/android/gradlew.bat
 create mode 100644 frontend/devtools_options.yaml
 create mode 100644 frontend/lib/modules/new_trip_options_button.dart
 rename frontend/lib/pages/{new_trip.dart => new_trip_location.dart} (75%)
 create mode 100644 frontend/lib/pages/new_trip_preferences.dart
 delete mode 100644 frontend/lib/pages/profile.dart
 create mode 100644 frontend/lib/pages/settings.dart

diff --git a/frontend/android/app/src/main/AndroidManifest.xml b/frontend/android/app/src/main/AndroidManifest.xml
index 35d3386..2d36363 100644
--- a/frontend/android/app/src/main/AndroidManifest.xml
+++ b/frontend/android/app/src/main/AndroidManifest.xml
@@ -1,6 +1,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Required to fetch data from the internet. -->
     <uses-permission android:name="android.permission.INTERNET"/>
+    <!-- Required to show user location -->
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
     <application
         android:label="anyway"
diff --git a/frontend/android/gradlew.bat b/frontend/android/gradlew.bat
new file mode 100755
index 0000000..aec9973
--- /dev/null
+++ b/frontend/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/frontend/devtools_options.yaml b/frontend/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/frontend/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/frontend/lib/constants.dart b/frontend/lib/constants.dart
index a063cdd..3320062 100644
--- a/frontend/lib/constants.dart
+++ b/frontend/lib/constants.dart
@@ -1,6 +1,64 @@
+import 'package:flutter/material.dart';
+
 const String APP_NAME = 'AnyWay';
 
 String API_URL_BASE = 'https://anyway.anydev.info';
 String PRIVACY_URL = 'https://anydev.info/privacy';
 
 const String MAP_ID = '41c21ac9b81dbfd8';
+
+
+const Color GRADIENT_START = Color(0xFFF9B208);
+const Color GRADIENT_END = Color(0xFFE72E77);
+
+const Color PRIMARY_COLOR = Color(0xFFF38F1A);
+
+ThemeData APP_THEME = ThemeData(
+  primaryColor: PRIMARY_COLOR,
+
+  scaffoldBackgroundColor: Colors.white,
+  cardColor: Colors.white,
+  useMaterial3: true,
+
+  colorScheme: ColorScheme.light(
+    primary: PRIMARY_COLOR,
+    secondary: GRADIENT_END,
+    surface: Colors.white,
+    error: Colors.red,
+    onPrimary: Colors.white,
+    onSecondary: const Color.fromARGB(255, 30, 22, 22),
+    onSurface: Colors.black,
+    onError: Colors.white,
+    brightness: Brightness.light,
+  ),
+
+
+  textButtonTheme: TextButtonThemeData(
+    style: TextButton.styleFrom(
+      backgroundColor: PRIMARY_COLOR,
+      textStyle: TextStyle(
+        color: Colors.black,
+      ),
+    ),
+  ),
+
+  iconButtonTheme: IconButtonThemeData(
+    style: ButtonStyle(
+      backgroundColor: MaterialStateProperty.all(PRIMARY_COLOR),
+    )
+  ),
+  
+  buttonTheme: ButtonThemeData(
+    buttonColor: PRIMARY_COLOR,
+    textTheme: ButtonTextTheme.primary,
+  ),
+
+  
+);
+
+
+const Gradient APP_GRADIENT = LinearGradient(
+  begin: Alignment.topLeft,
+  end: Alignment.bottomRight,
+  colors: [GRADIENT_START, GRADIENT_END],
+);
\ No newline at end of file
diff --git a/frontend/lib/layout.dart b/frontend/lib/layout.dart
index b8f3f38..3441f6b 100644
--- a/frontend/lib/layout.dart
+++ b/frontend/lib/layout.dart
@@ -1,3 +1,4 @@
+import 'package:anyway/pages/settings.dart';
 import 'package:flutter/material.dart';
 
 import 'package:anyway/constants.dart';
@@ -6,10 +7,9 @@ import 'package:anyway/structs/trip.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/new_trip_location.dart';
 import 'package:anyway/pages/current_trip.dart';
 import 'package:anyway/pages/onboarding.dart';
-import 'package:anyway/pages/profile.dart';
 
 
 
@@ -62,7 +62,7 @@ class _BasePageState extends State<BasePage> {
                         )
                       );
                     },
-                    label: Text("Plan a trip now"),
+                    label: Text("Plan a trip"),
                   ),
                 );
               }
@@ -74,33 +74,33 @@ class _BasePageState extends State<BasePage> {
       }
     } else if (widget.mainScreen == "tutorial") {
       currentView = OnboardingPage();
-    } else if (widget.mainScreen == "profile") {
-      currentView = ProfilePage();
+    } else if (widget.mainScreen == "settings") {
+      currentView = SettingsPage();
     }
 
-    final ThemeData theme = Theme.of(context);
-    
     return Scaffold(
       appBar: AppBar(title: Text(APP_NAME)),
       body: Center(child: currentView),
       drawer: Drawer(
         child: Column(
           children: [
-            DrawerHeader(
+            Container(
               decoration: BoxDecoration(
-                gradient: LinearGradient(colors: [Colors.red, Colors.yellow])
+                gradient: APP_GRADIENT,
               ),
+              height: 150,
               child: Center(
                 child: Text(
                   APP_NAME,
                   style: TextStyle(
-                    color: Colors.grey[800],
+                    color: Colors.white,
                     fontSize: 24,
                     fontWeight: FontWeight.bold,
                   ),
                 ),
               ),
             ),
+
             ListTile(
               title: const Text('Your Trips'),
               leading: const Icon(Icons.map),
@@ -130,7 +130,7 @@ class _BasePageState extends State<BasePage> {
               },
               child: const Text('Clear trips'),
             ),
-            const Divider(),
+            const Divider(indent: 10, endIndent: 10),
             ListTile(
               title: const Text('How to use'),
               leading: Icon(Icons.help),
@@ -148,11 +148,11 @@ class _BasePageState extends State<BasePage> {
             ListTile(
               title: const Text('Settings'),
               leading: const Icon(Icons.settings),
-              selected: widget.mainScreen == "profile",
+              selected: widget.mainScreen == "settings",
               onTap: () {
                 Navigator.of(context).push(
                   MaterialPageRoute(
-                    builder: (context) => BasePage(mainScreen: "profile")
+                    builder: (context) => BasePage(mainScreen: "settings")
                   )
                 );
               },
diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart
index 9e2f631..77c7616 100644
--- a/frontend/lib/main.dart
+++ b/frontend/lib/main.dart
@@ -15,7 +15,7 @@ class App extends StatelessWidget {
     return MaterialApp(
       title: APP_NAME,
       home: BasePage(mainScreen: "map"),
-      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]),
+      theme: APP_THEME,
       scaffoldMessengerKey: rootScaffoldMessengerKey
     );
   }
diff --git a/frontend/lib/modules/current_trip_landmarks_list.dart b/frontend/lib/modules/current_trip_landmarks_list.dart
index 097b700..ada17f2 100644
--- a/frontend/lib/modules/current_trip_landmarks_list.dart
+++ b/frontend/lib/modules/current_trip_landmarks_list.dart
@@ -32,7 +32,6 @@ List<Widget> landmarksList(Trip trip) {
         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"))
diff --git a/frontend/lib/modules/current_trip_map.dart b/frontend/lib/modules/current_trip_map.dart
index dfd174a..6ce4b4e 100644
--- a/frontend/lib/modules/current_trip_map.dart
+++ b/frontend/lib/modules/current_trip_map.dart
@@ -79,7 +79,7 @@ class _MapWidgetState extends State<MapWidget> {
       cloudMapId: MAP_ID,
       mapToolbarEnabled: false,
       zoomControlsEnabled: false,
-
+      myLocationEnabled: true,
     );
   }
 }
diff --git a/frontend/lib/modules/landmark_card.dart b/frontend/lib/modules/landmark_card.dart
index 3c027e5..78b6a27 100644
--- a/frontend/lib/modules/landmark_card.dart
+++ b/frontend/lib/modules/landmark_card.dart
@@ -18,10 +18,6 @@ class _LandmarkCardState extends State<LandmarkCard> {
   @override
   Widget build(BuildContext context) {
     ThemeData theme = Theme.of(context);
-    ButtonStyle buttonStyle = TextButton.styleFrom(
-      backgroundColor: Colors.orange,
-      fixedSize: Size.fromHeight(20)
-    );
     return Container(
       height: 160,
       child: Card(
@@ -88,21 +84,21 @@ class _LandmarkCardState extends State<LandmarkCard> {
                         // show the type, the website, and the wikipedia link as buttons/labels in a row
                         children: [
                           TextButton.icon(
-                            style: buttonStyle,
+                            style: theme.iconButtonTheme.style,
                             onPressed: () {},
                             icon: widget.landmark.type.icon,
                             label: Text(widget.landmark.type.name),
                           ),
                           if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0)
                             TextButton.icon(
-                              style: buttonStyle,
+                              style: theme.iconButtonTheme.style,
                               onPressed: () {},
                               icon: Icon(Icons.hourglass_bottom),
                               label: Text('${widget.landmark.duration!.inMinutes} minutes'),
                             ),
                           if (widget.landmark.websiteURL != null)
                             TextButton.icon(
-                              style: buttonStyle,
+                              style: theme.iconButtonTheme.style,
                               onPressed: () async {
                                 // open a browser with the website link
                                 await launchUrl(Uri.parse(widget.landmark.websiteURL!));
@@ -112,7 +108,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
                             ),
                           if (widget.landmark.wikipediaURL != null)
                             TextButton.icon(
-                              style: buttonStyle,
+                              style: theme.iconButtonTheme.style,
                               onPressed: () async {
                                 // open a browser with the wikipedia link
                                 await launchUrl(Uri.parse(widget.landmark.wikipediaURL!));
diff --git a/frontend/lib/modules/new_trip_button.dart b/frontend/lib/modules/new_trip_button.dart
index 99491f2..d966abf 100644
--- a/frontend/lib/modules/new_trip_button.dart
+++ b/frontend/lib/modules/new_trip_button.dart
@@ -1,4 +1,5 @@
 import 'package:anyway/layout.dart';
+import 'package:anyway/main.dart';
 import 'package:anyway/structs/preferences.dart';
 import 'package:anyway/structs/trip.dart';
 import 'package:anyway/utils/fetch_trip.dart';
@@ -8,8 +9,12 @@ import 'package:flutter/material.dart';
 
 class NewTripButton extends StatefulWidget {
   final Trip trip;
+  final UserPreferences preferences;
 
-  const NewTripButton({required this.trip});
+  const NewTripButton({
+    required this.trip,
+    required this.preferences,
+    });
 
   @override
   State<NewTripButton> createState() => _NewTripButtonState();
@@ -23,42 +28,39 @@ class _NewTripButtonState extends State<NewTripButton> {
       listenable: widget.trip,
       builder: (BuildContext context, Widget? child) {
         if (widget.trip.landmarks.isEmpty){
+          // Fallback if the trip setup is lagging behind
+          // This should in theory never happen
           return Container();
         }
         return FloatingActionButton.extended(
-          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)
-              )
-            );
-          },
-          icon: Icon(Icons.add),
-          label: 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,
-                );
-              }
-            },
-          )
-        );
-        
-      } 
+          onPressed: onPressed,
+          icon: const Icon(Icons.add),
+          label: AutoSizeText('Start planning!'),
+        ); 
+      }
     );
   }
+
+  void onPressed() async {
+    // Check that the preferences are valid
+    UserPreferences preferences = widget.preferences;
+    if (preferences.nature.value == 0 && preferences.shopping.value == 0 && preferences.sightseeing.value == 0){
+      rootScaffoldMessengerKey.currentState!.showSnackBar(
+        SnackBar(content: Text("Please specify at least one preference"))
+      );
+    } else if (preferences.maxTime.value == 0){
+      rootScaffoldMessengerKey.currentState!.showSnackBar(
+        SnackBar(content: Text("Please choose a longer duration"))
+      );
+    } else {
+      Trip trip = widget.trip;
+      fetchTrip(trip, widget.preferences);
+      Navigator.of(context).push(
+        MaterialPageRoute(
+          builder: (context) => BasePage(mainScreen: "map", trip: trip)
+        )
+      );
+    }
+  }
 }
 
diff --git a/frontend/lib/modules/new_trip_location_search.dart b/frontend/lib/modules/new_trip_location_search.dart
index 088d08f..2aae71b 100644
--- a/frontend/lib/modules/new_trip_location_search.dart
+++ b/frontend/lib/modules/new_trip_location_search.dart
@@ -6,9 +6,12 @@ import 'dart:developer';
 
 import 'package:anyway/structs/trip.dart';
 import 'package:flutter/material.dart';
+import 'package:shared_preferences/shared_preferences.dart';
 
 class NewTripLocationSearch extends StatefulWidget {
+  Future<SharedPreferences> prefs = SharedPreferences.getInstance();
   Trip trip;
+  
   NewTripLocationSearch(
     this.trip,
   );
@@ -45,20 +48,51 @@ class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
     }
   }
 
-  @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(
+  late Widget locationSearchBar = 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'),
-      ),]
+      )
+    ]
+  );
 
+
+  late Widget useCurrentLocationButton = ElevatedButton(
+    onPressed: () {
+      // setTripLocation(location);
+      // TODO: get current location
+    },
+    child: Text('Use current location'),
+  );
+
+  @override
+  Widget build(BuildContext context) {
+    return FutureBuilder(
+      future: widget.prefs,
+      builder: (context, snapshot) {
+        if (snapshot.hasData) {
+          final useLocation = snapshot.data!.getBool('useLocation') ?? false;
+          if (useLocation) {
+            return Column(
+              children: [
+                locationSearchBar,
+                useCurrentLocationButton,
+              ],
+            );
+          } else {
+            return locationSearchBar;
+          }
+        } else {
+          return locationSearchBar;
+        }
+      },
     );
   }
-}
\ No newline at end of file
+}
diff --git a/frontend/lib/modules/new_trip_map.dart b/frontend/lib/modules/new_trip_map.dart
index fc0a8c2..2966454 100644
--- a/frontend/lib/modules/new_trip_map.dart
+++ b/frontend/lib/modules/new_trip_map.dart
@@ -1,4 +1,3 @@
-
 // A map that allows the user to select a location for a new trip.
 import 'dart:developer';
 
@@ -22,7 +21,7 @@ class NewTripMap extends StatefulWidget {
 }
 
 class _NewTripMapState extends State<NewTripMap> {
-  final CameraPosition _cameraPosition = CameraPosition(
+  final CameraPosition _cameraPosition = const CameraPosition(
     target: LatLng(48.8566, 2.3522),
     zoom: 11.0,
   );
@@ -70,7 +69,6 @@ class _NewTripMapState extends State<NewTripMap> {
   }
 
 
-
   @override
   Widget build(BuildContext context) {
     widget.trip.addListener(updateTripDetails);
@@ -82,6 +80,8 @@ class _NewTripMapState extends State<NewTripMap> {
       cloudMapId: MAP_ID,
       mapToolbarEnabled: false,
       zoomControlsEnabled: false,
+      // TODO: should be loaded from the sharedprefs
+      myLocationEnabled: true,
     );
   }
 }
\ No newline at end of file
diff --git a/frontend/lib/modules/new_trip_options_button.dart b/frontend/lib/modules/new_trip_options_button.dart
new file mode 100644
index 0000000..499c642
--- /dev/null
+++ b/frontend/lib/modules/new_trip_options_button.dart
@@ -0,0 +1,41 @@
+import 'package:anyway/pages/new_trip_preferences.dart';
+import 'package:anyway/structs/trip.dart';
+import 'package:auto_size_text/auto_size_text.dart';
+import 'package:flutter/material.dart';
+
+
+class NewTripOptionsButton extends StatefulWidget {
+  final Trip trip;
+
+  const NewTripOptionsButton({required this.trip});
+
+  @override
+  State<NewTripOptionsButton> createState() => _NewTripOptionsButtonState();
+}
+
+class _NewTripOptionsButtonState extends State<NewTripOptionsButton> {
+
+  @override
+  Widget build(BuildContext context) {
+    return ListenableBuilder(
+      listenable: widget.trip,
+      builder: (BuildContext context, Widget? child) {
+        if (widget.trip.landmarks.isEmpty){
+          return Container();
+        }
+        return FloatingActionButton.extended(
+          onPressed: () async {
+            Navigator.of(context).push(
+              MaterialPageRoute(
+                builder: (context) => NewTripPreferencesPage(trip: widget.trip)
+              )
+            );
+          },
+          icon: const Icon(Icons.add),
+          label: const AutoSizeText('Set preferences')
+        ); 
+      }
+    );
+  }
+}
+
diff --git a/frontend/lib/modules/onboarding_card.dart b/frontend/lib/modules/onboarding_card.dart
index 573c82d..4124754 100644
--- a/frontend/lib/modules/onboarding_card.dart
+++ b/frontend/lib/modules/onboarding_card.dart
@@ -16,20 +16,23 @@ class OnboardingCard extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    Color baseColor = Theme.of(context).primaryColor;
+    Color baseColor = Theme.of(context).colorScheme.secondary;
     // have a different color for each card, incrementing the hue
     Color currentColor = baseColor.withAlpha(baseColor.alpha - index * 30);
     return Container(
       color: currentColor,
+      alignment: Alignment.center,
       child: Padding(
         padding: EdgeInsets.all(20),
         child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
           children: [
             Text(
               title,
               style: TextStyle(
                 fontSize: 24,
                 fontWeight: FontWeight.bold,
+                color: Colors.white,
               ),
             ),
             Padding(padding: EdgeInsets.only(top: 20)),
@@ -44,7 +47,8 @@ class OnboardingCard extends StatelessWidget {
                 fontSize: 16,
               ),
             ),
-          ],
+
+          ]
         ),
       )
     );
diff --git a/frontend/lib/modules/themed_marker.dart b/frontend/lib/modules/themed_marker.dart
index c8b7ea2..de52739 100644
--- a/frontend/lib/modules/themed_marker.dart
+++ b/frontend/lib/modules/themed_marker.dart
@@ -1,3 +1,4 @@
+import 'package:anyway/constants.dart';
 import 'package:anyway/structs/landmark.dart';
 import 'package:flutter/material.dart';
 
@@ -51,9 +52,7 @@ class ThemedMarker extends StatelessWidget {
         children: [
           Container(
             decoration: BoxDecoration(
-              gradient: LinearGradient(
-                colors: [Colors.red, Colors.yellow]
-              ),
+              gradient: APP_GRADIENT,
               shape: BoxShape.circle,
               border: Border.all(color: Colors.black, width: 5),
             ),
diff --git a/frontend/lib/pages/new_trip.dart b/frontend/lib/pages/new_trip_location.dart
similarity index 75%
rename from frontend/lib/pages/new_trip.dart
rename to frontend/lib/pages/new_trip_location.dart
index 741b4dc..7fb3b4b 100644
--- a/frontend/lib/pages/new_trip.dart
+++ b/frontend/lib/pages/new_trip_location.dart
@@ -1,11 +1,7 @@
 import 'package:anyway/modules/new_trip_button.dart';
-import 'package:anyway/structs/landmark.dart';
+import 'package:anyway/modules/new_trip_options_button.dart';
 import 'package:flutter/material.dart';
-import 'package:geocoding/geocoding.dart';
 
-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';
@@ -19,7 +15,6 @@ class NewTripPage extends StatefulWidget {
 }
 
 class _NewTripPageState extends State<NewTripPage> {
-  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
   final TextEditingController latController = TextEditingController();
   final TextEditingController lonController = TextEditingController();
   Trip trip = Trip();
@@ -40,7 +35,7 @@ class _NewTripPageState extends State<NewTripPage> {
           ),
         ],
       ),
-      floatingActionButton: NewTripButton(trip: trip),
+      floatingActionButton: NewTripOptionsButton(trip: trip),
     );
   }
 }
diff --git a/frontend/lib/pages/new_trip_preferences.dart b/frontend/lib/pages/new_trip_preferences.dart
new file mode 100644
index 0000000..a41b140
--- /dev/null
+++ b/frontend/lib/pages/new_trip_preferences.dart
@@ -0,0 +1,114 @@
+import 'package:anyway/modules/new_trip_button.dart';
+import 'package:anyway/structs/preferences.dart';
+import 'package:anyway/structs/trip.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+
+
+class NewTripPreferencesPage extends StatefulWidget {
+  final Trip trip;
+  const NewTripPreferencesPage({required this.trip});
+
+  @override
+  _NewTripPreferencesPageState createState() => _NewTripPreferencesPageState();
+}
+
+class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
+  UserPreferences preferences = UserPreferences();
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      body: ListView(
+        children: [
+          // Center(
+          //   child: CircleAvatar(
+          //     radius: 100,
+          //     child: Icon(Icons.person, size: 100),
+          //   )
+          // ),
+          Center(
+            child: FutureBuilder(
+              future: widget.trip.cityName,
+              builder: (context, snapshot) => Text(
+                'New trip to ${snapshot.hasData ? snapshot.data! : "..."}',
+                style: TextStyle(fontSize: 24)
+              )
+            )
+          ),
+
+          Divider(indent: 25, endIndent: 25, height: 50),
+
+          Center(
+            child: Padding(
+            padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10),
+              child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18))
+            ),
+          ),
+
+          durationPicker(preferences.maxTime),
+
+          preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]),
+        ]
+      ),
+      floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences),
+    );
+  }
+
+
+  Widget durationPicker(SinglePreference maxTime) {
+    return Card(
+      margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
+      shadowColor: Colors.grey,
+      child: ListTile(
+        leading: Icon(Icons.timer),
+        title: Text('How long should the trip be?'),
+        subtitle: CupertinoTimerPicker(
+          mode: CupertinoTimerPickerMode.hm,
+          initialTimerDuration: Duration(minutes: 90),
+          minuteInterval: 15,
+          onTimerDurationChanged: (Duration newDuration) {
+            setState(() {
+              preferences.maxTime.value = newDuration.inMinutes;
+            });
+          },
+        )
+      ),
+    );
+  }
+
+
+  Widget preferenceSliders(List<SinglePreference> prefs) {
+    List<Card> sliders = [];
+      for (SinglePreference pref in prefs) {
+      sliders.add(
+        Card(
+          child: ListTile(
+            leading: pref.icon,
+            title: Text(pref.name),
+            subtitle: Slider(
+              value: pref.value.toDouble(),
+              min: pref.minVal.toDouble(),
+              max: pref.maxVal.toDouble(),
+              divisions: pref.maxVal - pref.minVal,
+              label: pref.value.toString(),
+              onChanged: (double newValue) {
+                setState(() {
+                  pref.value = newValue.toInt();
+                });
+              },
+            )
+          ),
+          margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
+          shadowColor: Colors.grey,
+        )
+      );
+    }
+
+    return Column(
+      children: sliders
+    );
+  }
+}
+
diff --git a/frontend/lib/pages/onboarding.dart b/frontend/lib/pages/onboarding.dart
index 6412273..e23a016 100644
--- a/frontend/lib/pages/onboarding.dart
+++ b/frontend/lib/pages/onboarding.dart
@@ -1,5 +1,5 @@
 import 'package:anyway/modules/onboarding_card.dart';
-import 'package:anyway/pages/new_trip.dart';
+import 'package:anyway/pages/new_trip_location.dart';
 import 'package:flutter/material.dart';
 
 class OnboardingPage extends StatefulWidget {
diff --git a/frontend/lib/pages/profile.dart b/frontend/lib/pages/profile.dart
deleted file mode 100644
index 5ee1687..0000000
--- a/frontend/lib/pages/profile.dart
+++ /dev/null
@@ -1,177 +0,0 @@
-import 'package:anyway/constants.dart';
-import 'package:anyway/structs/preferences.dart';
-import 'package:flutter/material.dart';
-
-
-bool debugMode = false;
-
-class ProfilePage extends StatefulWidget {
-  @override
-  _ProfilePageState createState() => _ProfilePageState();
-}
-
-class _ProfilePageState extends State<ProfilePage> {
-  Future<UserPreferences> _prefs = loadUserPreferences();
-
-  @override
-  Widget build(BuildContext context) {
-    return ListView(
-      children: [
-        // First a round, centered image
-        Center(
-          child: CircleAvatar(
-            radius: 100,
-            child: Icon(Icons.person, size: 100),
-          )
-        ),
-        Center(
-          child: Text('Curious traveler', style: TextStyle(fontSize: 24))
-        ),
-
-        Divider(indent: 25, endIndent: 25, height: 50),
-
-        Center(
-          child: Padding(
-           padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10),
-            child: Text('For a tailored experience, please rate your discovery preferences.', style: TextStyle(fontSize: 18))
-          ),
-        ),
-
-        FutureBuilder(future: _prefs, builder: futureSliders),
-
-        Divider(indent: 25, endIndent: 25, height: 50),
-
-        privacyInfo(),
-
-        debugButton(),
-      ]
-    );
-  }
-
-  Widget debugButton() {
-    return Padding(
-      padding: EdgeInsets.only(top: 20),
-      child: Row(
-        children: [
-          Text('Debug mode'),
-          Switch(
-            value: debugMode,
-            onChanged: (bool? newValue) {
-              setState(() {
-                debugMode = newValue!;
-                showDialog(
-                  context: context,
-                  builder: (BuildContext context) {
-                    return AlertDialog(
-                      title: Text('Debug mode - custom API'),
-                      content: TextField(
-                        decoration: InputDecoration(
-                          hintText: 'http://localhost:8000'
-                        ),
-                      onChanged: (value) {
-                        setState(() {
-                          API_URL_BASE = value;
-                        });
-                      },
-                    ),
-                    actions: [
-                      TextButton(
-                        child: Text('OK'),
-                        onPressed: () {
-                          Navigator.of(context).pop();
-                        },
-                      ),
-                    ],
-                    );
-                  }
-                );
-              });
-            }
-          )
-        ],
-      )
-    );
-  }
-
-
-  Widget futureSliders(BuildContext context, AsyncSnapshot<UserPreferences> snapshot) {
-    if (snapshot.connectionState == ConnectionState.done) {
-      UserPreferences prefs = snapshot.data!;
-
-      return Column(
-        children: [
-          PreferenceSliders(prefs: [prefs.maxTime, prefs.maxDetour]),
-          Divider(indent: 25, endIndent: 25, height: 50),
-          PreferenceSliders(prefs: [prefs.sightseeing, prefs.shopping, prefs.nature])
-        ]
-      );
-    } else {
-      return CircularProgressIndicator();
-    }
-  }
-
-  Widget privacyInfo() {
-    return Padding(
-      padding: EdgeInsets.only(top: 20),
-      child: Row(
-        children: [
-          Text('Privacy policy is available at '),
-          TextButton.icon(
-            icon: Icon(Icons.info),
-            label: Text(PRIVACY_URL),
-            onPressed: () {
-            }
-          )
-        ],
-      )
-    );
-  }
-}
-
-
-class PreferenceSliders extends StatefulWidget {
-  final List<SinglePreference> prefs;
-
-  PreferenceSliders({required this.prefs});
-
-  @override
-  State<PreferenceSliders> createState() => _PreferenceSlidersState();
-}
-
-
-class _PreferenceSlidersState extends State<PreferenceSliders> {
-
-  @override
-  Widget build(BuildContext context) {
-    List<Card> sliders = [];
-      for (SinglePreference pref in widget.prefs) {
-      sliders.add(
-        Card(
-          child: ListTile(
-            leading: pref.icon,
-            title: Text(pref.name),
-            subtitle: Slider(
-              value: pref.value.toDouble(),
-              min: pref.minVal.toDouble(),
-              max: pref.maxVal.toDouble(),
-              divisions: pref.maxVal - pref.minVal,
-              label: pref.value.toString(),
-              onChanged: (double newValue) {
-                setState(() {
-                  pref.value = newValue.toInt();
-                  pref.save();
-                });
-              },
-            )
-          ),
-          margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
-          shadowColor: Colors.grey,
-        )
-      );
-    }
-
-    return Column(
-      children: sliders);
-  }
-}
-
diff --git a/frontend/lib/pages/settings.dart b/frontend/lib/pages/settings.dart
new file mode 100644
index 0000000..ba4297d
--- /dev/null
+++ b/frontend/lib/pages/settings.dart
@@ -0,0 +1,177 @@
+import 'package:anyway/constants.dart';
+import 'package:anyway/main.dart';
+import 'package:flutter/material.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+
+bool debugMode = false;
+bool useLocation = false;
+
+class SettingsPage extends StatefulWidget {
+  @override
+  _SettingsPageState createState() => _SettingsPageState();
+}
+
+class _SettingsPageState extends State<SettingsPage> {
+
+  @override
+  Widget build(BuildContext context) {
+    return ListView(
+      padding: EdgeInsets.all(15),
+      children: [
+        // First a round, centered image
+        Center(
+          child: CircleAvatar(
+            radius: 75,
+            child: Icon(Icons.settings, size: 100),
+          )
+        ),
+        Center(
+          child: Text('Global settings', style: TextStyle(fontSize: 24))
+        ),
+
+        Divider(indent: 25, endIndent: 25, height: 50),
+
+        darkMode(),
+        setLocationUsage(),
+        setDebugMode(),
+
+        Divider(indent: 25, endIndent: 25, height: 50),
+
+        privacyInfo(),
+      ]
+    );
+  }
+
+  Widget setDebugMode() {
+    return Row(
+      children: [
+        Text('Debugging: use a custom API URL'),
+        // white space
+        Spacer(),
+        Switch(
+          value: debugMode,
+          onChanged: (bool? newValue) {
+            setState(() {
+              debugMode = newValue!;
+              if (debugMode) {
+                showDialog(
+                  context: context,
+                  builder: (BuildContext context) {
+                    return AlertDialog(
+                      title: Text('Debug mode - use a custom API endpoint'),
+                      content: TextField(
+                        decoration: InputDecoration(
+                          hintText: 'https://anyway-stg.anydev.info'
+                        ),
+                        onChanged: (value) {
+                          setState(() {
+                            API_URL_BASE = value;
+                          });
+                        },
+                      ),
+                      actions: [
+                        TextButton(
+                          child: Text('OK'),
+                          onPressed: () {
+                            Navigator.of(context).pop();
+                          },
+                        ),
+                      ],
+                    );
+                  }
+                );
+              }
+            });
+          }
+        )
+      ],
+    );
+  }
+  Widget darkMode() {
+    return Row(
+      children: [
+        Text('Dark mode'),
+        Spacer(),
+        Switch(
+          value: Theme.of(context).brightness == Brightness.dark,
+          onChanged: (bool? newValue) {
+            setState(() {
+              rootScaffoldMessengerKey.currentState!.showSnackBar(
+                SnackBar(content: Text('Dark mode is not implemented yet'))
+              );
+              // if (newValue!) {
+              //   // Dark mode
+              //   Theme.of(context).brightness = Brightness.dark;
+              // } else {
+              //   // Light mode
+              //   Theme.of(context).brightness = Brightness.light;
+              // }
+            });
+          }
+        )
+      ],
+    );
+  }
+  Widget setLocationUsage() {
+    return Row(
+      children: [
+        Text('Use location services'),
+        // white space
+        Spacer(),
+        Switch(
+          value: useLocation,
+          onChanged: (bool? newValue) async {
+            await Permission.locationWhenInUse
+              .onDeniedCallback(() {
+                rootScaffoldMessengerKey.currentState!.showSnackBar(
+                  SnackBar(content: Text('Location services are required for this feature'))
+                );
+              })
+              .onGrantedCallback(() {
+                rootScaffoldMessengerKey.currentState!.showSnackBar(
+                  SnackBar(content: Text('Location services are now enabled'))
+                );
+                setState(() {
+                  useLocation = newValue!;
+                });
+                SharedPreferences.getInstance().then(
+                  (SharedPreferences prefs) {
+                    prefs.setBool('useLocation', useLocation);
+                  }
+                );
+              })
+              .onPermanentlyDeniedCallback(() {
+                rootScaffoldMessengerKey.currentState!.showSnackBar(
+                  SnackBar(content: Text('Location services are required for this feature'))
+                );
+              })  
+            .request();
+          }
+        )
+      ],
+    );
+  }
+
+
+  Widget privacyInfo() {
+    return Center(
+      child: Column(
+        children: [
+          Text('Our privacy policy is available under:'),
+          
+          TextButton.icon(
+            icon: Icon(Icons.info),
+            label: Text(PRIVACY_URL),
+            onPressed: () async{
+              await launchUrl(Uri.parse(PRIVACY_URL));
+            }
+          )
+        ],
+      )
+    );
+  }
+
+}
diff --git a/frontend/lib/structs/preferences.dart b/frontend/lib/structs/preferences.dart
index 074bbde..315b5c0 100644
--- a/frontend/lib/structs/preferences.dart
+++ b/frontend/lib/structs/preferences.dart
@@ -1,5 +1,4 @@
 import 'package:flutter/material.dart';
-import 'package:shared_preferences/shared_preferences.dart';
 
 
 class SinglePreference {
@@ -20,16 +19,6 @@ class SinglePreference {
     this.minVal = 0,
     this.maxVal = 5,
   });
-
-  void save() async {
-    SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
-      sharedPrefs.setInt('pref_$slug', value);
-  }
-
-  void load() async {
-    SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
-      value = sharedPrefs.getInt('pref_$slug') ?? minVal;
-  }
 }
 
 
@@ -65,38 +54,15 @@ class UserPreferences {
     maxVal: 720,
     icon: Icon(Icons.timer),
   );
-  SinglePreference maxDetour = SinglePreference(
-    name: "Trip detours",
-    slug: "detours",
-    description: "Are you okay with roaming even if makes the trip longer?",
-    value: 0,
-    maxVal: 30,
-    icon: Icon(Icons.loupe_sharp),
-  );
 
 
-
-  Future<void> load() async {
-    for (SinglePreference pref in [sightseeing, shopping, nature, maxTime, maxDetour]) {
-      pref.load();
-    }
-  }
-
   Map<String, dynamic> toJson() {
       // This is "opinionated" JSON, corresponding to the backend's expectations
       return {
         "sightseeing": {"type": "sightseeing", "score": sightseeing.value},
         "shopping": {"type": "shopping", "score": shopping.value},
         "nature": {"type": "nature", "score": nature.value},
-        "max_time_minute": maxTime.value,
-        "detour_tolerance_minute": maxDetour.value
+        "max_time_minute": maxTime.value
       };
     }
-}
-
-
-Future<UserPreferences> loadUserPreferences() async {
-  UserPreferences prefs = UserPreferences();
-  await prefs.load();
-  return prefs;
 }
\ No newline at end of file
diff --git a/frontend/lib/utils/fetch_trip.dart b/frontend/lib/utils/fetch_trip.dart
index 8ed04d3..a497f2e 100644
--- a/frontend/lib/utils/fetch_trip.dart
+++ b/frontend/lib/utils/fetch_trip.dart
@@ -29,11 +29,10 @@ Dio dio = Dio(
 
 fetchTrip(
   Trip trip,
-  Future<UserPreferences> preferences,
+  UserPreferences preferences,
 ) async {
-  UserPreferences prefs = await preferences;
   Map<String, dynamic> data = {
-    "preferences": prefs.toJson(),
+    "preferences": preferences.toJson(),
     "start": trip.landmarks!.first.location,
   };
   String dataString = jsonEncode(data);
@@ -47,11 +46,16 @@ fetchTrip(
   // handle errors
   if (response.statusCode != 200) {
     trip.updateUUID("error");
-    if (response.data["detail"] != null) {
-      trip.updateError(response.data["detail"]);
-      log(response.data["detail"]);
-      // throw Exception(response.data["detail"]);
+    String errorDetail;
+    if (response.data.runtimeType == String) {
+      errorDetail = response.data;
+    } else {
+      errorDetail = response.data["detail"] ?? "Unknown error";
     }
+    trip.updateError(errorDetail);
+    log(errorDetail);
+    // Actualy no need to throw an exception, we can just log the error and let the user retry
+    // throw Exception(errorDetail);
   } else {
     Map<String, dynamic> json = response.data;
 
diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock
index e53d9d5..d6c1b56 100644
--- a/frontend/pubspec.lock
+++ b/frontend/pubspec.lock
@@ -496,6 +496,54 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.3.0"
+  permission_handler:
+    dependency: "direct main"
+    description:
+      name: permission_handler
+      sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
+      url: "https://pub.dev"
+    source: hosted
+    version: "11.3.1"
+  permission_handler_android:
+    dependency: transitive
+    description:
+      name: permission_handler_android
+      sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
+      url: "https://pub.dev"
+    source: hosted
+    version: "12.0.12"
+  permission_handler_apple:
+    dependency: transitive
+    description:
+      name: permission_handler_apple
+      sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
+      url: "https://pub.dev"
+    source: hosted
+    version: "9.4.5"
+  permission_handler_html:
+    dependency: transitive
+    description:
+      name: permission_handler_html
+      sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.1.3+2"
+  permission_handler_platform_interface:
+    dependency: transitive
+    description:
+      name: permission_handler_platform_interface
+      sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.2.3"
+  permission_handler_windows:
+    dependency: transitive
+    description:
+      name: permission_handler_windows
+      sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.2.1"
   petitparser:
     dependency: transitive
     description:
diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml
index ffcce37..ad2961f 100644
--- a/frontend/pubspec.yaml
+++ b/frontend/pubspec.yaml
@@ -49,6 +49,7 @@ dependencies:
   flutter_svg: ^2.0.10+1
   url_launcher: ^6.3.0
   flutter_launcher_icons: ^0.13.1
+  permission_handler: ^11.3.1
 
 dev_dependencies:
   flutter_test:
diff --git a/frontend/windows/flutter/generated_plugin_registrant.cc b/frontend/windows/flutter/generated_plugin_registrant.cc
index 4f78848..a0d0bbe 100644
--- a/frontend/windows/flutter/generated_plugin_registrant.cc
+++ b/frontend/windows/flutter/generated_plugin_registrant.cc
@@ -6,9 +6,12 @@
 
 #include "generated_plugin_registrant.h"
 
+#include <permission_handler_windows/permission_handler_windows_plugin.h>
 #include <url_launcher_windows/url_launcher_windows.h>
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
+  PermissionHandlerWindowsPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
   UrlLauncherWindowsRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("UrlLauncherWindows"));
 }
diff --git a/frontend/windows/flutter/generated_plugins.cmake b/frontend/windows/flutter/generated_plugins.cmake
index 88b22e5..c20a586 100644
--- a/frontend/windows/flutter/generated_plugins.cmake
+++ b/frontend/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
 #
 
 list(APPEND FLUTTER_PLUGIN_LIST
+  permission_handler_windows
   url_launcher_windows
 )
 

From d323194ea74d1135d955dcfe3f5ead15038d649c Mon Sep 17 00:00:00 2001
From: Remy Moll <me@moll.re>
Date: Tue, 24 Sep 2024 23:48:07 +0200
Subject: [PATCH 2/4] Better location handling on map

---
 frontend/lib/constants.dart                  | 13 +---
 frontend/lib/modules/current_trip_map.dart   | 31 ++++++--
 frontend/lib/modules/new_trip_map.dart       | 22 +++++-
 frontend/lib/pages/current_trip.dart         |  4 +-
 frontend/lib/pages/new_trip_preferences.dart |  4 +-
 frontend/lib/pages/settings.dart             | 75 +++++++++++---------
 6 files changed, 96 insertions(+), 53 deletions(-)

diff --git a/frontend/lib/constants.dart b/frontend/lib/constants.dart
index 3320062..7856578 100644
--- a/frontend/lib/constants.dart
+++ b/frontend/lib/constants.dart
@@ -34,26 +34,19 @@ ThemeData APP_THEME = ThemeData(
 
 
   textButtonTheme: TextButtonThemeData(
+    
     style: TextButton.styleFrom(
       backgroundColor: PRIMARY_COLOR,
       textStyle: TextStyle(
-        color: Colors.black,
-      ),
+        color: Colors.red
+      )
     ),
   ),
-
-  iconButtonTheme: IconButtonThemeData(
-    style: ButtonStyle(
-      backgroundColor: MaterialStateProperty.all(PRIMARY_COLOR),
-    )
-  ),
-  
   buttonTheme: ButtonThemeData(
     buttonColor: PRIMARY_COLOR,
     textTheme: ButtonTextTheme.primary,
   ),
 
-  
 );
 
 
diff --git a/frontend/lib/modules/current_trip_map.dart b/frontend/lib/modules/current_trip_map.dart
index 6ce4b4e..3c770f3 100644
--- a/frontend/lib/modules/current_trip_map.dart
+++ b/frontend/lib/modules/current_trip_map.dart
@@ -6,22 +6,23 @@ import 'package:flutter/material.dart';
 import 'package:anyway/structs/landmark.dart';
 import 'package:anyway/structs/trip.dart';
 import 'package:google_maps_flutter/google_maps_flutter.dart';
+import 'package:shared_preferences/shared_preferences.dart';
 import 'package:widget_to_marker/widget_to_marker.dart';
 
 
-class MapWidget extends StatefulWidget {
+class CurrentTripMap extends StatefulWidget {
 
   final Trip? trip;
 
-  MapWidget({
+  CurrentTripMap({
     this.trip
   });
 
   @override
-  State<MapWidget> createState() => _MapWidgetState();
+  State<CurrentTripMap> createState() => _CurrentTripMapState();
 }
 
-class _MapWidgetState extends State<MapWidget> {
+class _CurrentTripMapState extends State<CurrentTripMap> {
   late GoogleMapController mapController;
 
   CameraPosition _cameraPosition = CameraPosition(
@@ -67,9 +68,27 @@ class _MapWidgetState extends State<MapWidget> {
     });
   }
 
+
   @override
   Widget build(BuildContext context) {
     widget.trip?.addListener(setMapMarkers);
+    Future<SharedPreferences> preferences = SharedPreferences.getInstance();
+
+    return FutureBuilder(
+      future: preferences,
+      builder: (context, snapshot) {
+        if (snapshot.hasData) {
+          SharedPreferences prefs = snapshot.data as SharedPreferences;
+          bool useLocation = prefs.getBool('useLocation') ?? true;
+          return _buildMap(useLocation);
+        } else {
+          return const CircularProgressIndicator();
+        }
+      }
+    );
+  }
+
+  Widget _buildMap(bool useLocation) {
     return GoogleMap(
       onMapCreated: _onMapCreated,
       initialCameraPosition: _cameraPosition,
@@ -79,7 +98,9 @@ class _MapWidgetState extends State<MapWidget> {
       cloudMapId: MAP_ID,
       mapToolbarEnabled: false,
       zoomControlsEnabled: false,
-      myLocationEnabled: true,
+      myLocationEnabled: useLocation,
+      myLocationButtonEnabled: false,
     );
   }
+
 }
diff --git a/frontend/lib/modules/new_trip_map.dart b/frontend/lib/modules/new_trip_map.dart
index 2966454..9c6c2cd 100644
--- a/frontend/lib/modules/new_trip_map.dart
+++ b/frontend/lib/modules/new_trip_map.dart
@@ -7,6 +7,7 @@ import 'package:anyway/structs/landmark.dart';
 import 'package:anyway/structs/trip.dart';
 import 'package:flutter/material.dart';
 import 'package:google_maps_flutter/google_maps_flutter.dart';
+import 'package:shared_preferences/shared_preferences.dart';
 import 'package:widget_to_marker/widget_to_marker.dart';
 
 
@@ -72,6 +73,23 @@ class _NewTripMapState extends State<NewTripMap> {
   @override
   Widget build(BuildContext context) {
     widget.trip.addListener(updateTripDetails);
+    Future<SharedPreferences> preferences = SharedPreferences.getInstance();
+
+    return FutureBuilder(
+      future: preferences,
+      builder: (context, snapshot) {
+        if (snapshot.hasData) {
+          SharedPreferences prefs = snapshot.data as SharedPreferences;
+          bool useLocation = prefs.getBool('useLocation') ?? true;
+          return _buildMap(useLocation);
+        } else {
+          return const CircularProgressIndicator();
+        }
+      }
+    );
+  }
+
+  Widget _buildMap(bool useLocation) {
     return GoogleMap(
       onMapCreated: _onMapCreated,
       initialCameraPosition: _cameraPosition,
@@ -80,8 +98,8 @@ class _NewTripMapState extends State<NewTripMap> {
       cloudMapId: MAP_ID,
       mapToolbarEnabled: false,
       zoomControlsEnabled: false,
-      // TODO: should be loaded from the sharedprefs
-      myLocationEnabled: true,
+      myLocationButtonEnabled: false,
+      myLocationEnabled: useLocation,
     );
   }
 }
\ No newline at end of file
diff --git a/frontend/lib/pages/current_trip.dart b/frontend/lib/pages/current_trip.dart
index 793f22f..3e3c57e 100644
--- a/frontend/lib/pages/current_trip.dart
+++ b/frontend/lib/pages/current_trip.dart
@@ -29,14 +29,14 @@ class _TripPageState extends State<TripPage> {
     return SlidingUpPanel(
         panelBuilder: (sc) => _panelFull(sc),
         // collapsed: _floatingCollapsed(),
-        body: MapWidget(trip: widget.trip),
+        body: CurrentTripMap(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: const [
           BoxShadow(
             blurRadius: 20.0,
             color: Colors.black,
diff --git a/frontend/lib/pages/new_trip_preferences.dart b/frontend/lib/pages/new_trip_preferences.dart
index a41b140..5fb7640 100644
--- a/frontend/lib/pages/new_trip_preferences.dart
+++ b/frontend/lib/pages/new_trip_preferences.dart
@@ -84,6 +84,8 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
       for (SinglePreference pref in prefs) {
       sliders.add(
         Card(
+          margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
+          shadowColor: Colors.grey,
           child: ListTile(
             leading: pref.icon,
             title: Text(pref.name),
@@ -100,8 +102,6 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
               },
             )
           ),
-          margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
-          shadowColor: Colors.grey,
         )
       );
     }
diff --git a/frontend/lib/pages/settings.dart b/frontend/lib/pages/settings.dart
index ba4297d..4cb0248 100644
--- a/frontend/lib/pages/settings.dart
+++ b/frontend/lib/pages/settings.dart
@@ -7,7 +7,6 @@ import 'package:url_launcher/url_launcher.dart';
 
 
 bool debugMode = false;
-bool useLocation = false;
 
 class SettingsPage extends StatefulWidget {
   @override
@@ -15,7 +14,6 @@ class SettingsPage extends StatefulWidget {
 }
 
 class _SettingsPageState extends State<SettingsPage> {
-
   @override
   Widget build(BuildContext context) {
     return ListView(
@@ -90,6 +88,7 @@ class _SettingsPageState extends State<SettingsPage> {
       ],
     );
   }
+
   Widget darkMode() {
     return Row(
       children: [
@@ -115,46 +114,58 @@ class _SettingsPageState extends State<SettingsPage> {
       ],
     );
   }
+
   Widget setLocationUsage() {
+    Future<SharedPreferences> preferences = SharedPreferences.getInstance();
     return Row(
       children: [
         Text('Use location services'),
         // white space
         Spacer(),
-        Switch(
-          value: useLocation,
-          onChanged: (bool? newValue) async {
-            await Permission.locationWhenInUse
-              .onDeniedCallback(() {
-                rootScaffoldMessengerKey.currentState!.showSnackBar(
-                  SnackBar(content: Text('Location services are required for this feature'))
-                );
-              })
-              .onGrantedCallback(() {
-                rootScaffoldMessengerKey.currentState!.showSnackBar(
-                  SnackBar(content: Text('Location services are now enabled'))
-                );
-                setState(() {
-                  useLocation = newValue!;
-                });
-                SharedPreferences.getInstance().then(
-                  (SharedPreferences prefs) {
-                    prefs.setBool('useLocation', useLocation);
-                  }
-                );
-              })
-              .onPermanentlyDeniedCallback(() {
-                rootScaffoldMessengerKey.currentState!.showSnackBar(
-                  SnackBar(content: Text('Location services are required for this feature'))
-                );
-              })  
-            .request();
+        FutureBuilder(
+          future: preferences,
+          builder: (context, snapshot) {
+            if (snapshot.hasData) {
+              bool useLocation = snapshot.data!.getBool('useLocation') ?? false;
+              return Switch(
+                value: useLocation,
+                onChanged: setUseLocation,
+              );
+            } else {
+              return CircularProgressIndicator();
+            }
           }
         )
       ],
     );
   }
 
+  void setUseLocation(bool newValue) async {
+    await Permission.locationWhenInUse
+      .onDeniedCallback(() {
+        rootScaffoldMessengerKey.currentState!.showSnackBar(
+          SnackBar(content: Text('Location services are required for this feature'))
+        );
+      })
+      .onGrantedCallback(() {
+        rootScaffoldMessengerKey.currentState!.showSnackBar(
+          SnackBar(content: Text('Location services are now enabled'))
+        );
+        SharedPreferences.getInstance().then(
+          (SharedPreferences prefs) {
+            setState(() {
+              prefs.setBool('useLocation', newValue);
+            });
+          }
+        );
+      })
+      .onPermanentlyDeniedCallback(() {
+        rootScaffoldMessengerKey.currentState!.showSnackBar(
+          SnackBar(content: Text('Location services are required for this feature'))
+        );
+      })  
+    .request();
+  }
 
   Widget privacyInfo() {
     return Center(
@@ -163,8 +174,8 @@ class _SettingsPageState extends State<SettingsPage> {
           Text('Our privacy policy is available under:'),
           
           TextButton.icon(
-            icon: Icon(Icons.info),
-            label: Text(PRIVACY_URL),
+            icon: Icon(Icons.info, color: Colors.white),
+            label: Text(PRIVACY_URL, style: TextStyle(color: Colors.white)),
             onPressed: () async{
               await launchUrl(Uri.parse(PRIVACY_URL));
             }

From 88b825ea31e70a57497ae8fc087e44699f64de2f Mon Sep 17 00:00:00 2001
From: Remy Moll <me@moll.re>
Date: Wed, 25 Sep 2024 14:47:13 +0200
Subject: [PATCH 3/4] better visual coherence

---
 frontend/lib/constants.dart                   | 50 ++++++++---
 .../lib/modules/current_trip_greeter.dart     | 27 ++----
 .../modules/current_trip_landmarks_list.dart  |  2 +-
 frontend/lib/modules/current_trip_map.dart    |  2 +-
 frontend/lib/modules/current_trip_panel.dart  | 82 +++++++++++++++++++
 .../lib/modules/current_trip_save_button.dart |  8 ++
 .../lib/modules/current_trip_summary.dart     | 31 +++++++
 frontend/lib/modules/landmark_card.dart       |  6 +-
 ...d_marker.dart => landmark_map_marker.dart} | 20 ++---
 .../lib/modules/new_trip_location_search.dart |  7 +-
 frontend/lib/modules/new_trip_map.dart        |  4 +-
 frontend/lib/pages/current_trip.dart          | 63 ++++----------
 frontend/lib/pages/new_trip_preferences.dart  | 15 ++--
 frontend/lib/pages/settings.dart              |  4 +-
 frontend/lib/structs/landmark.dart            | 22 ++---
 frontend/lib/structs/preferences.dart         |  9 +-
 16 files changed, 221 insertions(+), 131 deletions(-)
 create mode 100644 frontend/lib/modules/current_trip_panel.dart
 create mode 100644 frontend/lib/modules/current_trip_summary.dart
 rename frontend/lib/modules/{themed_marker.dart => landmark_map_marker.dart} (66%)

diff --git a/frontend/lib/constants.dart b/frontend/lib/constants.dart
index 7856578..d4e756c 100644
--- a/frontend/lib/constants.dart
+++ b/frontend/lib/constants.dart
@@ -13,6 +13,11 @@ const Color GRADIENT_END = Color(0xFFE72E77);
 
 const Color PRIMARY_COLOR = Color(0xFFF38F1A);
 
+
+
+const double TRIP_PANEL_MAX_HEIGHT = 0.8;
+const double TRIP_PANEL_MIN_HEIGHT = 0.12;
+
 ThemeData APP_THEME = ThemeData(
   primaryColor: PRIMARY_COLOR,
 
@@ -33,20 +38,43 @@ ThemeData APP_THEME = ThemeData(
   ),
 
 
-  textButtonTheme: TextButtonThemeData(
-    
-    style: TextButton.styleFrom(
-      backgroundColor: PRIMARY_COLOR,
-      textStyle: TextStyle(
-        color: Colors.red
-      )
+  textButtonTheme: const TextButtonThemeData(
+    style: ButtonStyle(
+      foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
+      side: WidgetStatePropertyAll(
+        BorderSide(
+          color: PRIMARY_COLOR,
+          width: 1,
+        ),
+      ),
+    )
     ),
-  ),
-  buttonTheme: ButtonThemeData(
-    buttonColor: PRIMARY_COLOR,
-    textTheme: ButtonTextTheme.primary,
+
+  elevatedButtonTheme: const ElevatedButtonThemeData(
+    style: ButtonStyle(
+      foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
+    )
   ),
 
+  outlinedButtonTheme: const OutlinedButtonThemeData(
+    style: ButtonStyle(
+      foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
+    )
+  ),
+
+
+  cardTheme: const CardTheme(
+    shadowColor: Colors.grey,
+    elevation: 2,
+    margin: EdgeInsets.all(10),
+  ),
+
+  sliderTheme: const SliderThemeData(
+    trackHeight: 15,
+    inactiveTrackColor: Colors.grey,
+    thumbColor: PRIMARY_COLOR,
+    activeTrackColor: GRADIENT_END
+  )
 );
 
 
diff --git a/frontend/lib/modules/current_trip_greeter.dart b/frontend/lib/modules/current_trip_greeter.dart
index b0771d5..6a2118a 100644
--- a/frontend/lib/modules/current_trip_greeter.dart
+++ b/frontend/lib/modules/current_trip_greeter.dart
@@ -1,5 +1,6 @@
 import 'dart:developer';
 
+import 'package:anyway/constants.dart';
 import 'package:anyway/structs/trip.dart';
 import 'package:auto_size_text/auto_size_text.dart';
 
@@ -20,8 +21,12 @@ class Greeter extends StatefulWidget {
 class _GreeterState extends State<Greeter> {
   
   Widget greeterBuilder (BuildContext context, Widget? child) {
-    ThemeData theme = Theme.of(context);
-    TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24);
+    final Shader textGradient = APP_GRADIENT.createShader(Rect.fromLTWH(0.0, 0.0, 200.0, 70.0));
+    TextStyle greeterStyle = TextStyle(
+      foreground: Paint()..shader = textGradient,
+      fontWeight: FontWeight.bold,
+      fontSize: 26
+    );
 
     Widget topGreeter;
 
@@ -91,26 +96,10 @@ class _GreeterState extends State<Greeter> {
     }
 
     return Center(
-      child: Column(
-        children: [
-          // Padding(padding: EdgeInsets.only(top: 20)),
-          topGreeter,
-          Padding(
-            padding: EdgeInsets.all(20),
-            child: bottomGreeter
-          ),
-        ],
-      )
+      child: topGreeter,
     );
   }
 
-  Widget bottomGreeter = const Text(
-    "Busy day ahead? Here is how to make the most of it!",
-    style: TextStyle(color: Colors.black, fontSize: 18),
-    textAlign: TextAlign.center,
-  );
-
-
 
   @override
   Widget build(BuildContext context) {
diff --git a/frontend/lib/modules/current_trip_landmarks_list.dart b/frontend/lib/modules/current_trip_landmarks_list.dart
index ada17f2..b486a97 100644
--- a/frontend/lib/modules/current_trip_landmarks_list.dart
+++ b/frontend/lib/modules/current_trip_landmarks_list.dart
@@ -16,7 +16,7 @@ List<Widget> landmarksList(Trip trip) {
 
   log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
 
-  if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == start ) {
+  if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == typeStart ) {
     children.add(
       const Text("No landmarks in this trip"),
     );
diff --git a/frontend/lib/modules/current_trip_map.dart b/frontend/lib/modules/current_trip_map.dart
index 3c770f3..9efdc4c 100644
--- a/frontend/lib/modules/current_trip_map.dart
+++ b/frontend/lib/modules/current_trip_map.dart
@@ -1,7 +1,7 @@
 import 'dart:collection';
 
 import 'package:anyway/constants.dart';
-import 'package:anyway/modules/themed_marker.dart';
+import 'package:anyway/modules/landmark_map_marker.dart';
 import 'package:flutter/material.dart';
 import 'package:anyway/structs/landmark.dart';
 import 'package:anyway/structs/trip.dart';
diff --git a/frontend/lib/modules/current_trip_panel.dart b/frontend/lib/modules/current_trip_panel.dart
new file mode 100644
index 0000000..cd73f28
--- /dev/null
+++ b/frontend/lib/modules/current_trip_panel.dart
@@ -0,0 +1,82 @@
+import 'package:anyway/constants.dart';
+import 'package:flutter/material.dart';
+
+import 'package:anyway/structs/trip.dart';
+import 'package:anyway/modules/current_trip_summary.dart';
+import 'package:anyway/modules/current_trip_save_button.dart';
+import 'package:anyway/modules/current_trip_landmarks_list.dart';
+import 'package:anyway/modules/current_trip_greeter.dart';
+
+
+class CurrentTripPanel extends StatefulWidget {
+  final ScrollController controller;
+  final Trip trip;
+
+  const CurrentTripPanel({
+    super.key,
+    required this.controller,
+    required this.trip,
+    });
+
+  @override
+  State<CurrentTripPanel> createState() => _CurrentTripPanelState();
+}
+
+class _CurrentTripPanelState extends State<CurrentTripPanel> {
+  @override
+  Widget build(BuildContext context) {
+    return ListenableBuilder(
+      listenable: widget.trip,
+      builder: (context, child) {
+        if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') {
+          return ListView(
+            controller: widget.controller,
+            padding: const EdgeInsets.only(bottom: 30, left: 5, right: 5),
+            children: [
+              SizedBox(
+                // reuse the exact same height as the panel has when collapsed
+                // this way the greeter will be centered when the panel is collapsed
+                height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20,
+                child: Greeter(trip: widget.trip),
+              ),
+
+              const Padding(padding: EdgeInsets.only(top: 10)),
+
+              // CurrentTripSummary(trip: widget.trip),
+
+              // const Divider(),
+
+              ...landmarksList(widget.trip),
+
+              const Padding(padding: EdgeInsets.only(top: 10)),
+
+              Center(child: saveButton(widget.trip)),
+            ],
+          );
+        } else if(widget.trip.uuid == 'pending') {
+          return SizedBox(
+            // reuse the exact same height as the panel has when collapsed
+            // this way the greeter will be centered when the panel is collapsed
+            height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20,
+            child: Greeter(trip: widget.trip)
+          );
+        } else {
+          return Row(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              const Icon(
+                Icons.error_outline,
+                color: Colors.red,
+                size: 50,
+              ),
+              Padding(
+                padding: const EdgeInsets.only(left: 10),
+                child: Text('Error: ${widget.trip.errorDescription}'),
+              ),
+            ],
+          );
+        }
+      }
+    );
+  }
+}  
diff --git a/frontend/lib/modules/current_trip_save_button.dart b/frontend/lib/modules/current_trip_save_button.dart
index d95c580..f4587cf 100644
--- a/frontend/lib/modules/current_trip_save_button.dart
+++ b/frontend/lib/modules/current_trip_save_button.dart
@@ -1,4 +1,5 @@
 
+import 'package:anyway/main.dart';
 import 'package:anyway/structs/trip.dart';
 import 'package:auto_size_text/auto_size_text.dart';
 import 'package:flutter/material.dart';
@@ -8,6 +9,13 @@ Widget saveButton(Trip trip) => ElevatedButton(
   onPressed: () async {
     SharedPreferences prefs = await SharedPreferences.getInstance();
     trip.toPrefs(prefs);
+    rootScaffoldMessengerKey.currentState!.showSnackBar(
+      SnackBar(
+        content: Text('Trip saved'),
+        duration: Duration(seconds: 2),
+        dismissDirection: DismissDirection.horizontal
+      )
+    );
   },
   child: SizedBox(
     width: 100,
diff --git a/frontend/lib/modules/current_trip_summary.dart b/frontend/lib/modules/current_trip_summary.dart
new file mode 100644
index 0000000..77c9461
--- /dev/null
+++ b/frontend/lib/modules/current_trip_summary.dart
@@ -0,0 +1,31 @@
+import 'package:anyway/structs/trip.dart';
+import 'package:flutter/material.dart';
+
+class CurrentTripSummary extends StatefulWidget {
+  final Trip trip;
+  const CurrentTripSummary({
+    super.key,
+    required this.trip,
+    });
+
+  @override
+  State<CurrentTripSummary> createState() => _CurrentTripSummaryState();
+}
+
+class _CurrentTripSummaryState extends State<CurrentTripSummary> {
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      children: [
+        Text('Summary'),
+        // Text('Start: ${widget.trip.start}'),
+        // Text('End: ${widget.trip.end}'),
+        Text('Total duration: ${widget.trip.totalTime}'),
+        Text('Total distance: ${widget.trip.totalTime}'),
+        // Text('Fuel: ${widget.trip.fuel}'),
+        // Text('Cost: ${widget.trip.cost}'),
+      ],
+    );
+    
+  }
+}
\ No newline at end of file
diff --git a/frontend/lib/modules/landmark_card.dart b/frontend/lib/modules/landmark_card.dart
index 78b6a27..43d42ef 100644
--- a/frontend/lib/modules/landmark_card.dart
+++ b/frontend/lib/modules/landmark_card.dart
@@ -36,7 +36,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
               width: 160,
               child: CachedNetworkImage(
                 imageUrl: widget.landmark.imageURL ?? '',
-                placeholder: (context, url) => CircularProgressIndicator(),
+                placeholder: (context, url) => Center(child: CircularProgressIndicator()),
                 errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
                 // TODO: make this a switch statement to load a placeholder if null
                 // cover the whole container meaning the image will be cropped
@@ -84,21 +84,18 @@ class _LandmarkCardState extends State<LandmarkCard> {
                         // show the type, the website, and the wikipedia link as buttons/labels in a row
                         children: [
                           TextButton.icon(
-                            style: theme.iconButtonTheme.style,
                             onPressed: () {},
                             icon: widget.landmark.type.icon,
                             label: Text(widget.landmark.type.name),
                           ),
                           if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0)
                             TextButton.icon(
-                              style: theme.iconButtonTheme.style,
                               onPressed: () {},
                               icon: Icon(Icons.hourglass_bottom),
                               label: Text('${widget.landmark.duration!.inMinutes} minutes'),
                             ),
                           if (widget.landmark.websiteURL != null)
                             TextButton.icon(
-                              style: theme.iconButtonTheme.style,
                               onPressed: () async {
                                 // open a browser with the website link
                                 await launchUrl(Uri.parse(widget.landmark.websiteURL!));
@@ -108,7 +105,6 @@ class _LandmarkCardState extends State<LandmarkCard> {
                             ),
                           if (widget.landmark.wikipediaURL != null)
                             TextButton.icon(
-                              style: theme.iconButtonTheme.style,
                               onPressed: () async {
                                 // open a browser with the wikipedia link
                                 await launchUrl(Uri.parse(widget.landmark.wikipediaURL!));
diff --git a/frontend/lib/modules/themed_marker.dart b/frontend/lib/modules/landmark_map_marker.dart
similarity index 66%
rename from frontend/lib/modules/themed_marker.dart
rename to frontend/lib/modules/landmark_map_marker.dart
index de52739..2e8f5d7 100644
--- a/frontend/lib/modules/themed_marker.dart
+++ b/frontend/lib/modules/landmark_map_marker.dart
@@ -17,21 +17,9 @@ class ThemedMarker extends StatelessWidget {
   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) {
+    if (landmark.type != typeStart && landmark.type != typeFinish) {
       positionIndicator = Positioned(
         top: 0,
         right: 0,
@@ -56,8 +44,10 @@ class ThemedMarker extends StatelessWidget {
               shape: BoxShape.circle,
               border: Border.all(color: Colors.black, width: 5),
             ),
-            padding: EdgeInsets.all(5),
-            child: icon
+            width: 70,
+            height: 70,
+            padding: const EdgeInsets.all(5),
+            child: Icon(landmark.type.icon.icon, size: 50),
           ),
           if (positionIndicator != null) positionIndicator,
         ],
diff --git a/frontend/lib/modules/new_trip_location_search.dart b/frontend/lib/modules/new_trip_location_search.dart
index 2aae71b..c737a75 100644
--- a/frontend/lib/modules/new_trip_location_search.dart
+++ b/frontend/lib/modules/new_trip_location_search.dart
@@ -42,7 +42,7 @@ class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
           uuid: 'pending',
           name: query,
           location: [location.latitude, location.longitude],
-          type: start
+          type: typeStart
         )
       );
     }
@@ -65,9 +65,8 @@ class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
 
 
   late Widget useCurrentLocationButton = ElevatedButton(
-    onPressed: () {
-      // setTripLocation(location);
-      // TODO: get current location
+    onPressed: () async {
+      
     },
     child: Text('Use current location'),
   );
diff --git a/frontend/lib/modules/new_trip_map.dart b/frontend/lib/modules/new_trip_map.dart
index 9c6c2cd..49445fc 100644
--- a/frontend/lib/modules/new_trip_map.dart
+++ b/frontend/lib/modules/new_trip_map.dart
@@ -2,7 +2,7 @@
 import 'dart:developer';
 
 import 'package:anyway/constants.dart';
-import 'package:anyway/modules/themed_marker.dart';
+import 'package:anyway/modules/landmark_map_marker.dart';
 import 'package:anyway/structs/landmark.dart';
 import 'package:anyway/structs/trip.dart';
 import 'package:flutter/material.dart';
@@ -37,7 +37,7 @@ class _NewTripMapState extends State<NewTripMap> {
         uuid: 'pending',
         name: 'start',
         location: [location.latitude, location.longitude],
-        type: start
+        type: typeStart
       )
     );
   }
diff --git a/frontend/lib/pages/current_trip.dart b/frontend/lib/pages/current_trip.dart
index 3e3c57e..834b3f6 100644
--- a/frontend/lib/pages/current_trip.dart
+++ b/frontend/lib/pages/current_trip.dart
@@ -1,11 +1,10 @@
-import 'package:anyway/modules/current_trip_save_button.dart';
+import 'package:anyway/constants.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/current_trip_landmarks_list.dart';
-import 'package:anyway/modules/current_trip_greeter.dart';
 import 'package:anyway/modules/current_trip_map.dart';
+import 'package:anyway/modules/current_trip_panel.dart';
 
 
 
@@ -27,15 +26,20 @@ class _TripPageState extends State<TripPage> {
   @override
   Widget build(BuildContext context) {
     return SlidingUpPanel(
-        panelBuilder: (sc) => _panelFull(sc),
-        // collapsed: _floatingCollapsed(),
+        // use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview
+        panelBuilder: (scrollcontroller) => CurrentTripPanel(controller: scrollcontroller, trip: widget.trip),
+        // using collapsed and panelBuilder seems to show both at the same time, so we include the greeter in the panelBuilder
+        // collapsed: Greeter(trip: widget.trip),
         body: CurrentTripMap(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)),
+        minHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT,
+        maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT,
+        // padding in this context is annoying: it offsets the notion of vertical alignment.
+        // children that want to be centered vertically need to have their size adjusted by 2x the padding
+        padding: const EdgeInsets.only(top: 10),
+        // Panel snapping should not be disabled because it significantly improves the user experience
+        // panelSnapping: false
+        borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
+        parallaxEnabled: true,
         boxShadow: const [
           BoxShadow(
             blurRadius: 20.0,
@@ -44,41 +48,4 @@ class _TripPageState extends State<TripPage> {
         ],
     );
   }
-
-  
-  Widget _panelFull(ScrollController sc) {
-    return ListenableBuilder(
-      listenable: widget.trip,
-      builder: (context, child) {
-        if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') {
-          return ListView(
-            controller: sc,
-            padding: EdgeInsets.only(bottom: 35),
-            children: [
-              Greeter(trip: widget.trip),
-              ...landmarksList(widget.trip),
-              Padding(padding: EdgeInsets.only(top: 10)),
-              Center(child: saveButton(widget.trip)),
-            ],
-          );
-        } else if(widget.trip.uuid == 'pending') {
-          return Greeter(trip: widget.trip);
-        } else {
-          return Column(
-            children: [
-              const Icon(
-                Icons.error_outline,
-                color: Colors.red,
-                size: 60,
-              ),
-              Padding(
-                padding: const EdgeInsets.only(top: 16),
-                child: Text('Error: ${widget.trip.errorDescription}'),
-              ),
-            ],
-          );
-        }
-      }
-    );
-  }
 }
diff --git a/frontend/lib/pages/new_trip_preferences.dart b/frontend/lib/pages/new_trip_preferences.dart
index 5fb7640..9f60055 100644
--- a/frontend/lib/pages/new_trip_preferences.dart
+++ b/frontend/lib/pages/new_trip_preferences.dart
@@ -28,25 +28,26 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
           //     child: Icon(Icons.person, size: 100),
           //   )
           // ),
+          Padding(padding: EdgeInsets.only(top: 30)),
           Center(
             child: FutureBuilder(
               future: widget.trip.cityName,
               builder: (context, snapshot) => Text(
-                'New trip to ${snapshot.hasData ? snapshot.data! : "..."}',
-                style: TextStyle(fontSize: 24)
+                'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}',
+                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)
               )
             )
           ),
 
-          Divider(indent: 25, endIndent: 25, height: 50),
-
           Center(
             child: Padding(
-            padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10),
+            padding: EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 0),
               child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18))
             ),
           ),
 
+          Divider(indent: 25, endIndent: 25, height: 50),
+
           durationPicker(preferences.maxTime),
 
           preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]),
@@ -63,7 +64,7 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
       shadowColor: Colors.grey,
       child: ListTile(
         leading: Icon(Icons.timer),
-        title: Text('How long should the trip be?'),
+        title: Text(preferences.maxTime.description),
         subtitle: CupertinoTimerPicker(
           mode: CupertinoTimerPickerMode.hm,
           initialTimerDuration: Duration(minutes: 90),
@@ -84,8 +85,6 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
       for (SinglePreference pref in prefs) {
       sliders.add(
         Card(
-          margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
-          shadowColor: Colors.grey,
           child: ListTile(
             leading: pref.icon,
             title: Text(pref.name),
diff --git a/frontend/lib/pages/settings.dart b/frontend/lib/pages/settings.dart
index 4cb0248..8a97c90 100644
--- a/frontend/lib/pages/settings.dart
+++ b/frontend/lib/pages/settings.dart
@@ -174,8 +174,8 @@ class _SettingsPageState extends State<SettingsPage> {
           Text('Our privacy policy is available under:'),
           
           TextButton.icon(
-            icon: Icon(Icons.info, color: Colors.white),
-            label: Text(PRIVACY_URL, style: TextStyle(color: Colors.white)),
+            icon: Icon(Icons.info),
+            label: Text(PRIVACY_URL),
             onPressed: () async{
               await launchUrl(Uri.parse(PRIVACY_URL));
             }
diff --git a/frontend/lib/structs/landmark.dart b/frontend/lib/structs/landmark.dart
index e6d3734..6729a65 100644
--- a/frontend/lib/structs/landmark.dart
+++ b/frontend/lib/structs/landmark.dart
@@ -4,13 +4,13 @@ import 'dart:convert';
 import 'package:flutter/material.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 
-LandmarkType sightseeing = LandmarkType(name: 'sightseeing');
-LandmarkType nature = LandmarkType(name: 'nature');
-LandmarkType shopping = LandmarkType(name: 'shopping');
+LandmarkType typeSightseeing = LandmarkType(name: 'sightseeing');
+LandmarkType typeNature = LandmarkType(name: 'nature');
+LandmarkType typeShopping = LandmarkType(name: 'shopping');
 // LandmarkType museum = LandmarkType(name: 'Museum');
 // LandmarkType restaurant = LandmarkType(name: 'Restaurant');
-LandmarkType start = LandmarkType(name: 'start');
-LandmarkType finish = LandmarkType(name: 'finish');
+LandmarkType typeStart = LandmarkType(name: 'start');
+LandmarkType typeFinish = LandmarkType(name: 'finish');
 
 
 final class Landmark extends LinkedListEntry<Landmark>{
@@ -130,22 +130,22 @@ class LandmarkType {
   LandmarkType({required this.name, this.icon = const Icon(Icons.location_on)}) {
     switch (name) {
       case 'sightseeing':
-        icon = Icon(Icons.church);
+        icon = const Icon(Icons.church);
         break;
       case 'nature':
-        icon = Icon(Icons.eco);
+        icon = const Icon(Icons.eco);
         break;
       case 'shopping':
-        icon = Icon(Icons.shopping_cart);
+        icon = const Icon(Icons.shopping_cart);
         break;
       case 'start':
-        icon = Icon(Icons.play_arrow);
+        icon = const Icon(Icons.play_arrow);
         break;
       case 'finish':
-        icon = Icon(Icons.flag);
+        icon = const Icon(Icons.flag);
         break;
       default:
-        icon = Icon(Icons.location_on);
+        icon = const Icon(Icons.location_on);
     }
   }
   @override
diff --git a/frontend/lib/structs/preferences.dart b/frontend/lib/structs/preferences.dart
index 315b5c0..e36f35d 100644
--- a/frontend/lib/structs/preferences.dart
+++ b/frontend/lib/structs/preferences.dart
@@ -1,3 +1,4 @@
+import 'package:anyway/structs/landmark.dart';
 import 'package:flutter/material.dart';
 
 
@@ -28,27 +29,27 @@ class UserPreferences {
     slug: "sightseeing",
     description: "How much do you like sightseeing?",
     value: 0,
-    icon: Icon(Icons.church),
+    icon: typeSightseeing.icon,
   );
   SinglePreference shopping = SinglePreference(
     name: "Shopping",
     slug: "shopping",
     description: "How much do you like shopping?",
     value: 0,
-    icon: Icon(Icons.shopping_bag),
+    icon: typeShopping.icon,
   );
   SinglePreference nature = SinglePreference(
     name: "Nature",
     slug: "nature",
     description: "How much do you like nature?",
     value: 0,
-    icon: Icon(Icons.landscape),
+    icon: typeNature.icon,
   );
 
   SinglePreference maxTime = SinglePreference(
     name: "Trip duration",
     slug: "duration",
-    description: "How long do you want your trip to be?",
+    description: "How long should your trip be?",
     value: 30,
     minVal: 30,
     maxVal: 720,

From d37fb09d629f6b4523b81a1103abfd58887fa5c7 Mon Sep 17 00:00:00 2001
From: Remy Moll <me@moll.re>
Date: Wed, 25 Sep 2024 15:10:49 +0200
Subject: [PATCH 4/4] trip destination from current location

---
 frontend/android/settings.gradle              |  2 +-
 .../lib/modules/new_trip_location_search.dart | 13 ++++-
 .../Flutter/GeneratedPluginRegistrant.swift   |  2 +
 frontend/pubspec.lock                         | 56 +++++++++++++++++--
 frontend/pubspec.yaml                         |  1 +
 .../flutter/generated_plugin_registrant.cc    |  3 +
 .../windows/flutter/generated_plugins.cmake   |  1 +
 7 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/frontend/android/settings.gradle b/frontend/android/settings.gradle
index 1d6d19b..c9fb5ba 100644
--- a/frontend/android/settings.gradle
+++ b/frontend/android/settings.gradle
@@ -20,7 +20,7 @@ pluginManagement {
 plugins {
     id "dev.flutter.flutter-plugin-loader" version "1.0.0"
     id "com.android.application" version "7.3.0" apply false
-    id "org.jetbrains.kotlin.android" version "1.7.10" apply false
+    id "org.jetbrains.kotlin.android" version "2.0.20" apply false
 }
 
 include ":app"
diff --git a/frontend/lib/modules/new_trip_location_search.dart b/frontend/lib/modules/new_trip_location_search.dart
index c737a75..2f285c1 100644
--- a/frontend/lib/modules/new_trip_location_search.dart
+++ b/frontend/lib/modules/new_trip_location_search.dart
@@ -6,6 +6,7 @@ import 'dart:developer';
 
 import 'package:anyway/structs/trip.dart';
 import 'package:flutter/material.dart';
+import 'package:geolocator/geolocator.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 
 class NewTripLocationSearch extends StatefulWidget {
@@ -66,7 +67,17 @@ class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
 
   late Widget useCurrentLocationButton = ElevatedButton(
     onPressed: () async {
-      
+      // this widget is only shown if the user has already granted location permissions
+      Position position = await Geolocator.getCurrentPosition();
+      widget.trip.landmarks.clear();
+      widget.trip.addLandmark(
+        Landmark(
+          uuid: 'pending',
+          name: 'start',
+          location: [position.latitude, position.longitude],
+          type: typeStart
+        )
+      );
     },
     child: Text('Use current location'),
   );
diff --git a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift
index 63ad0d1..c0164f1 100644
--- a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,12 +5,14 @@
 import FlutterMacOS
 import Foundation
 
+import geolocator_apple
 import path_provider_foundation
 import shared_preferences_foundation
 import sqflite
 import url_launcher_macos
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+  GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
   SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock
index d6c1b56..7532658 100644
--- a/frontend/pubspec.lock
+++ b/frontend/pubspec.lock
@@ -264,6 +264,54 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "3.2.0"
+  geolocator:
+    dependency: "direct main"
+    description:
+      name: geolocator
+      sha256: "0ec58b731776bc43097fcf751f79681b6a8f6d3bc737c94779fe9f1ad73c1a81"
+      url: "https://pub.dev"
+    source: hosted
+    version: "13.0.1"
+  geolocator_android:
+    dependency: transitive
+    description:
+      name: geolocator_android
+      sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.6.1"
+  geolocator_apple:
+    dependency: transitive
+    description:
+      name: geolocator_apple
+      sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.7"
+  geolocator_platform_interface:
+    dependency: transitive
+    description:
+      name: geolocator_platform_interface
+      sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.2.4"
+  geolocator_web:
+    dependency: transitive
+    description:
+      name: geolocator_web
+      sha256: "2ed69328e05cd94e7eb48bb0535f5fc0c0c44d1c4fa1e9737267484d05c29b5e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.1.1"
+  geolocator_windows:
+    dependency: transitive
+    description:
+      name: geolocator_windows
+      sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.2.3"
   google_maps:
     dependency: transitive
     description:
@@ -636,10 +684,10 @@ packages:
     dependency: transitive
     description:
       name: shared_preferences_web
-      sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936"
+      sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
       url: "https://pub.dev"
     source: hosted
-    version: "2.4.0"
+    version: "2.4.2"
   shared_preferences_windows:
     dependency: transitive
     description:
@@ -873,10 +921,10 @@ packages:
     dependency: transitive
     description:
       name: web
-      sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
+      sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
       url: "https://pub.dev"
     source: hosted
-    version: "0.5.1"
+    version: "1.1.0"
   widget_to_marker:
     dependency: "direct main"
     description:
diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml
index ad2961f..98c7fc2 100644
--- a/frontend/pubspec.yaml
+++ b/frontend/pubspec.yaml
@@ -50,6 +50,7 @@ dependencies:
   url_launcher: ^6.3.0
   flutter_launcher_icons: ^0.13.1
   permission_handler: ^11.3.1
+  geolocator: ^13.0.1
 
 dev_dependencies:
   flutter_test:
diff --git a/frontend/windows/flutter/generated_plugin_registrant.cc b/frontend/windows/flutter/generated_plugin_registrant.cc
index a0d0bbe..ce843bc 100644
--- a/frontend/windows/flutter/generated_plugin_registrant.cc
+++ b/frontend/windows/flutter/generated_plugin_registrant.cc
@@ -6,10 +6,13 @@
 
 #include "generated_plugin_registrant.h"
 
+#include <geolocator_windows/geolocator_windows.h>
 #include <permission_handler_windows/permission_handler_windows_plugin.h>
 #include <url_launcher_windows/url_launcher_windows.h>
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
+  GeolocatorWindowsRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("GeolocatorWindows"));
   PermissionHandlerWindowsPluginRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
   UrlLauncherWindowsRegisterWithRegistrar(
diff --git a/frontend/windows/flutter/generated_plugins.cmake b/frontend/windows/flutter/generated_plugins.cmake
index c20a586..b3ea692 100644
--- a/frontend/windows/flutter/generated_plugins.cmake
+++ b/frontend/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
 #
 
 list(APPEND FLUTTER_PLUGIN_LIST
+  geolocator_windows
   permission_handler_windows
   url_launcher_windows
 )