Usability and styling #24
@@ -1,6 +1,9 @@
 | 
				
			|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
					<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
				
			||||||
    <!-- Required to fetch data from the internet. -->
 | 
					    <!-- Required to fetch data from the internet. -->
 | 
				
			||||||
    <uses-permission android:name="android.permission.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
 | 
					    <application
 | 
				
			||||||
        android:label="anyway"
 | 
					        android:label="anyway"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								frontend/android/gradlew.bat
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										90
									
								
								frontend/android/gradlew.bat
									
									
									
									
										vendored
									
									
										Executable file
									
								
							@@ -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
 | 
				
			||||||
@@ -20,7 +20,7 @@ pluginManagement {
 | 
				
			|||||||
plugins {
 | 
					plugins {
 | 
				
			||||||
    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
 | 
					    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
 | 
				
			||||||
    id "com.android.application" version "7.3.0" apply false
 | 
					    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"
 | 
					include ":app"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								frontend/devtools_options.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								frontend/devtools_options.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -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:
 | 
				
			||||||
@@ -1,6 +1,85 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const String APP_NAME = 'AnyWay';
 | 
					const String APP_NAME = 'AnyWay';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
String API_URL_BASE = 'https://anyway.anydev.info';
 | 
					String API_URL_BASE = 'https://anyway.anydev.info';
 | 
				
			||||||
String PRIVACY_URL = 'https://anydev.info/privacy';
 | 
					String PRIVACY_URL = 'https://anydev.info/privacy';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const String MAP_ID = '41c21ac9b81dbfd8';
 | 
					const String MAP_ID = '41c21ac9b81dbfd8';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Color GRADIENT_START = Color(0xFFF9B208);
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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: const TextButtonThemeData(
 | 
				
			||||||
 | 
					    style: ButtonStyle(
 | 
				
			||||||
 | 
					      foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
 | 
				
			||||||
 | 
					      side: WidgetStatePropertyAll(
 | 
				
			||||||
 | 
					        BorderSide(
 | 
				
			||||||
 | 
					          color: PRIMARY_COLOR,
 | 
				
			||||||
 | 
					          width: 1,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Gradient APP_GRADIENT = LinearGradient(
 | 
				
			||||||
 | 
					  begin: Alignment.topLeft,
 | 
				
			||||||
 | 
					  end: Alignment.bottomRight,
 | 
				
			||||||
 | 
					  colors: [GRADIENT_START, GRADIENT_END],
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anyway/pages/settings.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:anyway/constants.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/modules/trips_saved_list.dart';
 | 
				
			||||||
import 'package:anyway/utils/load_trips.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/current_trip.dart';
 | 
				
			||||||
import 'package:anyway/pages/onboarding.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") {
 | 
					    } else if (widget.mainScreen == "tutorial") {
 | 
				
			||||||
      currentView = OnboardingPage();
 | 
					      currentView = OnboardingPage();
 | 
				
			||||||
    } else if (widget.mainScreen == "profile") {
 | 
					    } else if (widget.mainScreen == "settings") {
 | 
				
			||||||
      currentView = ProfilePage();
 | 
					      currentView = SettingsPage();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final ThemeData theme = Theme.of(context);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      appBar: AppBar(title: Text(APP_NAME)),
 | 
					      appBar: AppBar(title: Text(APP_NAME)),
 | 
				
			||||||
      body: Center(child: currentView),
 | 
					      body: Center(child: currentView),
 | 
				
			||||||
      drawer: Drawer(
 | 
					      drawer: Drawer(
 | 
				
			||||||
        child: Column(
 | 
					        child: Column(
 | 
				
			||||||
          children: [
 | 
					          children: [
 | 
				
			||||||
            DrawerHeader(
 | 
					            Container(
 | 
				
			||||||
              decoration: BoxDecoration(
 | 
					              decoration: BoxDecoration(
 | 
				
			||||||
                gradient: LinearGradient(colors: [Colors.red, Colors.yellow])
 | 
					                gradient: APP_GRADIENT,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
 | 
					              height: 150,
 | 
				
			||||||
              child: Center(
 | 
					              child: Center(
 | 
				
			||||||
                child: Text(
 | 
					                child: Text(
 | 
				
			||||||
                  APP_NAME,
 | 
					                  APP_NAME,
 | 
				
			||||||
                  style: TextStyle(
 | 
					                  style: TextStyle(
 | 
				
			||||||
                    color: Colors.grey[800],
 | 
					                    color: Colors.white,
 | 
				
			||||||
                    fontSize: 24,
 | 
					                    fontSize: 24,
 | 
				
			||||||
                    fontWeight: FontWeight.bold,
 | 
					                    fontWeight: FontWeight.bold,
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ListTile(
 | 
					            ListTile(
 | 
				
			||||||
              title: const Text('Your Trips'),
 | 
					              title: const Text('Your Trips'),
 | 
				
			||||||
              leading: const Icon(Icons.map),
 | 
					              leading: const Icon(Icons.map),
 | 
				
			||||||
@@ -130,7 +130,7 @@ class _BasePageState extends State<BasePage> {
 | 
				
			|||||||
              },
 | 
					              },
 | 
				
			||||||
              child: const Text('Clear trips'),
 | 
					              child: const Text('Clear trips'),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            const Divider(),
 | 
					            const Divider(indent: 10, endIndent: 10),
 | 
				
			||||||
            ListTile(
 | 
					            ListTile(
 | 
				
			||||||
              title: const Text('How to use'),
 | 
					              title: const Text('How to use'),
 | 
				
			||||||
              leading: Icon(Icons.help),
 | 
					              leading: Icon(Icons.help),
 | 
				
			||||||
@@ -148,11 +148,11 @@ class _BasePageState extends State<BasePage> {
 | 
				
			|||||||
            ListTile(
 | 
					            ListTile(
 | 
				
			||||||
              title: const Text('Settings'),
 | 
					              title: const Text('Settings'),
 | 
				
			||||||
              leading: const Icon(Icons.settings),
 | 
					              leading: const Icon(Icons.settings),
 | 
				
			||||||
              selected: widget.mainScreen == "profile",
 | 
					              selected: widget.mainScreen == "settings",
 | 
				
			||||||
              onTap: () {
 | 
					              onTap: () {
 | 
				
			||||||
                Navigator.of(context).push(
 | 
					                Navigator.of(context).push(
 | 
				
			||||||
                  MaterialPageRoute(
 | 
					                  MaterialPageRoute(
 | 
				
			||||||
                    builder: (context) => BasePage(mainScreen: "profile")
 | 
					                    builder: (context) => BasePage(mainScreen: "settings")
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ class App extends StatelessWidget {
 | 
				
			|||||||
    return MaterialApp(
 | 
					    return MaterialApp(
 | 
				
			||||||
      title: APP_NAME,
 | 
					      title: APP_NAME,
 | 
				
			||||||
      home: BasePage(mainScreen: "map"),
 | 
					      home: BasePage(mainScreen: "map"),
 | 
				
			||||||
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]),
 | 
					      theme: APP_THEME,
 | 
				
			||||||
      scaffoldMessengerKey: rootScaffoldMessengerKey
 | 
					      scaffoldMessengerKey: rootScaffoldMessengerKey
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import 'dart:developer';
 | 
					import 'dart:developer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:anyway/constants.dart';
 | 
				
			||||||
import 'package:anyway/structs/trip.dart';
 | 
					import 'package:anyway/structs/trip.dart';
 | 
				
			||||||
import 'package:auto_size_text/auto_size_text.dart';
 | 
					import 'package:auto_size_text/auto_size_text.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,8 +21,12 @@ class Greeter extends StatefulWidget {
 | 
				
			|||||||
class _GreeterState extends State<Greeter> {
 | 
					class _GreeterState extends State<Greeter> {
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  Widget greeterBuilder (BuildContext context, Widget? child) {
 | 
					  Widget greeterBuilder (BuildContext context, Widget? child) {
 | 
				
			||||||
    ThemeData theme = Theme.of(context);
 | 
					    final Shader textGradient = APP_GRADIENT.createShader(Rect.fromLTWH(0.0, 0.0, 200.0, 70.0));
 | 
				
			||||||
    TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24);
 | 
					    TextStyle greeterStyle = TextStyle(
 | 
				
			||||||
 | 
					      foreground: Paint()..shader = textGradient,
 | 
				
			||||||
 | 
					      fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					      fontSize: 26
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Widget topGreeter;
 | 
					    Widget topGreeter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -91,26 +96,10 @@ class _GreeterState extends State<Greeter> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Center(
 | 
					    return Center(
 | 
				
			||||||
      child: Column(
 | 
					      child: topGreeter,
 | 
				
			||||||
        children: [
 | 
					 | 
				
			||||||
          // Padding(padding: EdgeInsets.only(top: 20)),
 | 
					 | 
				
			||||||
          topGreeter,
 | 
					 | 
				
			||||||
          Padding(
 | 
					 | 
				
			||||||
            padding: EdgeInsets.all(20),
 | 
					 | 
				
			||||||
            child: bottomGreeter
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ List<Widget> landmarksList(Trip trip) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
 | 
					  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(
 | 
					    children.add(
 | 
				
			||||||
      const Text("No landmarks in this trip"),
 | 
					      const Text("No landmarks in this trip"),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@@ -32,7 +32,6 @@ List<Widget> landmarksList(Trip trip) {
 | 
				
			|||||||
        onDismissed: (direction) {
 | 
					        onDismissed: (direction) {
 | 
				
			||||||
          log('Removing ${landmark.name}');
 | 
					          log('Removing ${landmark.name}');
 | 
				
			||||||
          trip.removeLandmark(landmark);
 | 
					          trip.removeLandmark(landmark);
 | 
				
			||||||
          // Then show a snackbar
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          rootScaffoldMessengerKey.currentState!.showSnackBar(
 | 
					          rootScaffoldMessengerKey.currentState!.showSnackBar(
 | 
				
			||||||
            SnackBar(content: Text("We won't show ${landmark.name} again"))
 | 
					            SnackBar(content: Text("We won't show ${landmark.name} again"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,27 +1,28 @@
 | 
				
			|||||||
import 'dart:collection';
 | 
					import 'dart:collection';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:anyway/constants.dart';
 | 
					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:flutter/material.dart';
 | 
				
			||||||
import 'package:anyway/structs/landmark.dart';
 | 
					import 'package:anyway/structs/landmark.dart';
 | 
				
			||||||
import 'package:anyway/structs/trip.dart';
 | 
					import 'package:anyway/structs/trip.dart';
 | 
				
			||||||
import 'package:google_maps_flutter/google_maps_flutter.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';
 | 
					import 'package:widget_to_marker/widget_to_marker.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MapWidget extends StatefulWidget {
 | 
					class CurrentTripMap extends StatefulWidget {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final Trip? trip;
 | 
					  final Trip? trip;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MapWidget({
 | 
					  CurrentTripMap({
 | 
				
			||||||
    this.trip
 | 
					    this.trip
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<MapWidget> createState() => _MapWidgetState();
 | 
					  State<CurrentTripMap> createState() => _CurrentTripMapState();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _MapWidgetState extends State<MapWidget> {
 | 
					class _CurrentTripMapState extends State<CurrentTripMap> {
 | 
				
			||||||
  late GoogleMapController mapController;
 | 
					  late GoogleMapController mapController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  CameraPosition _cameraPosition = CameraPosition(
 | 
					  CameraPosition _cameraPosition = CameraPosition(
 | 
				
			||||||
@@ -67,9 +68,27 @@ class _MapWidgetState extends State<MapWidget> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    widget.trip?.addListener(setMapMarkers);
 | 
					    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(
 | 
					    return GoogleMap(
 | 
				
			||||||
      onMapCreated: _onMapCreated,
 | 
					      onMapCreated: _onMapCreated,
 | 
				
			||||||
      initialCameraPosition: _cameraPosition,
 | 
					      initialCameraPosition: _cameraPosition,
 | 
				
			||||||
@@ -79,7 +98,9 @@ class _MapWidgetState extends State<MapWidget> {
 | 
				
			|||||||
      cloudMapId: MAP_ID,
 | 
					      cloudMapId: MAP_ID,
 | 
				
			||||||
      mapToolbarEnabled: false,
 | 
					      mapToolbarEnabled: false,
 | 
				
			||||||
      zoomControlsEnabled: false,
 | 
					      zoomControlsEnabled: false,
 | 
				
			||||||
 | 
					      myLocationEnabled: useLocation,
 | 
				
			||||||
 | 
					      myLocationButtonEnabled: false,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										82
									
								
								frontend/lib/modules/current_trip_panel.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								frontend/lib/modules/current_trip_panel.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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}'),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}  
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:anyway/main.dart';
 | 
				
			||||||
import 'package:anyway/structs/trip.dart';
 | 
					import 'package:anyway/structs/trip.dart';
 | 
				
			||||||
import 'package:auto_size_text/auto_size_text.dart';
 | 
					import 'package:auto_size_text/auto_size_text.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
@@ -8,6 +9,13 @@ Widget saveButton(Trip trip) => ElevatedButton(
 | 
				
			|||||||
  onPressed: () async {
 | 
					  onPressed: () async {
 | 
				
			||||||
    SharedPreferences prefs = await SharedPreferences.getInstance();
 | 
					    SharedPreferences prefs = await SharedPreferences.getInstance();
 | 
				
			||||||
    trip.toPrefs(prefs);
 | 
					    trip.toPrefs(prefs);
 | 
				
			||||||
 | 
					    rootScaffoldMessengerKey.currentState!.showSnackBar(
 | 
				
			||||||
 | 
					      SnackBar(
 | 
				
			||||||
 | 
					        content: Text('Trip saved'),
 | 
				
			||||||
 | 
					        duration: Duration(seconds: 2),
 | 
				
			||||||
 | 
					        dismissDirection: DismissDirection.horizontal
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  child: SizedBox(
 | 
					  child: SizedBox(
 | 
				
			||||||
    width: 100,
 | 
					    width: 100,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										31
									
								
								frontend/lib/modules/current_trip_summary.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								frontend/lib/modules/current_trip_summary.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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}'),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -18,10 +18,6 @@ class _LandmarkCardState extends State<LandmarkCard> {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    ThemeData theme = Theme.of(context);
 | 
					    ThemeData theme = Theme.of(context);
 | 
				
			||||||
    ButtonStyle buttonStyle = TextButton.styleFrom(
 | 
					 | 
				
			||||||
      backgroundColor: Colors.orange,
 | 
					 | 
				
			||||||
      fixedSize: Size.fromHeight(20)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return Container(
 | 
					    return Container(
 | 
				
			||||||
      height: 160,
 | 
					      height: 160,
 | 
				
			||||||
      child: Card(
 | 
					      child: Card(
 | 
				
			||||||
@@ -40,7 +36,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
 | 
				
			|||||||
              width: 160,
 | 
					              width: 160,
 | 
				
			||||||
              child: CachedNetworkImage(
 | 
					              child: CachedNetworkImage(
 | 
				
			||||||
                imageUrl: widget.landmark.imageURL ?? '',
 | 
					                imageUrl: widget.landmark.imageURL ?? '',
 | 
				
			||||||
                placeholder: (context, url) => CircularProgressIndicator(),
 | 
					                placeholder: (context, url) => Center(child: CircularProgressIndicator()),
 | 
				
			||||||
                errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
 | 
					                errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
 | 
				
			||||||
                // TODO: make this a switch statement to load a placeholder if null
 | 
					                // TODO: make this a switch statement to load a placeholder if null
 | 
				
			||||||
                // cover the whole container meaning the image will be cropped
 | 
					                // cover the whole container meaning the image will be cropped
 | 
				
			||||||
@@ -88,21 +84,18 @@ class _LandmarkCardState extends State<LandmarkCard> {
 | 
				
			|||||||
                        // show the type, the website, and the wikipedia link as buttons/labels in a row
 | 
					                        // show the type, the website, and the wikipedia link as buttons/labels in a row
 | 
				
			||||||
                        children: [
 | 
					                        children: [
 | 
				
			||||||
                          TextButton.icon(
 | 
					                          TextButton.icon(
 | 
				
			||||||
                            style: buttonStyle,
 | 
					 | 
				
			||||||
                            onPressed: () {},
 | 
					                            onPressed: () {},
 | 
				
			||||||
                            icon: widget.landmark.type.icon,
 | 
					                            icon: widget.landmark.type.icon,
 | 
				
			||||||
                            label: Text(widget.landmark.type.name),
 | 
					                            label: Text(widget.landmark.type.name),
 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                          if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0)
 | 
					                          if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0)
 | 
				
			||||||
                            TextButton.icon(
 | 
					                            TextButton.icon(
 | 
				
			||||||
                              style: buttonStyle,
 | 
					 | 
				
			||||||
                              onPressed: () {},
 | 
					                              onPressed: () {},
 | 
				
			||||||
                              icon: Icon(Icons.hourglass_bottom),
 | 
					                              icon: Icon(Icons.hourglass_bottom),
 | 
				
			||||||
                              label: Text('${widget.landmark.duration!.inMinutes} minutes'),
 | 
					                              label: Text('${widget.landmark.duration!.inMinutes} minutes'),
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                          if (widget.landmark.websiteURL != null)
 | 
					                          if (widget.landmark.websiteURL != null)
 | 
				
			||||||
                            TextButton.icon(
 | 
					                            TextButton.icon(
 | 
				
			||||||
                              style: buttonStyle,
 | 
					 | 
				
			||||||
                              onPressed: () async {
 | 
					                              onPressed: () async {
 | 
				
			||||||
                                // open a browser with the website link
 | 
					                                // open a browser with the website link
 | 
				
			||||||
                                await launchUrl(Uri.parse(widget.landmark.websiteURL!));
 | 
					                                await launchUrl(Uri.parse(widget.landmark.websiteURL!));
 | 
				
			||||||
@@ -112,7 +105,6 @@ class _LandmarkCardState extends State<LandmarkCard> {
 | 
				
			|||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                          if (widget.landmark.wikipediaURL != null)
 | 
					                          if (widget.landmark.wikipediaURL != null)
 | 
				
			||||||
                            TextButton.icon(
 | 
					                            TextButton.icon(
 | 
				
			||||||
                              style: buttonStyle,
 | 
					 | 
				
			||||||
                              onPressed: () async {
 | 
					                              onPressed: () async {
 | 
				
			||||||
                                // open a browser with the wikipedia link
 | 
					                                // open a browser with the wikipedia link
 | 
				
			||||||
                                await launchUrl(Uri.parse(widget.landmark.wikipediaURL!));
 | 
					                                await launchUrl(Uri.parse(widget.landmark.wikipediaURL!));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anyway/constants.dart';
 | 
				
			||||||
import 'package:anyway/structs/landmark.dart';
 | 
					import 'package:anyway/structs/landmark.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,21 +17,9 @@ class ThemedMarker extends StatelessWidget {
 | 
				
			|||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    // This returns an outlined circle, with an icon corresponding to the landmark type
 | 
					    // 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
 | 
					    // 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;
 | 
					    Widget? positionIndicator;
 | 
				
			||||||
    if (landmark.type != start && landmark.type != finish) {
 | 
					    if (landmark.type != typeStart && landmark.type != typeFinish) {
 | 
				
			||||||
      positionIndicator = Positioned(
 | 
					      positionIndicator = Positioned(
 | 
				
			||||||
        top: 0,
 | 
					        top: 0,
 | 
				
			||||||
        right: 0,
 | 
					        right: 0,
 | 
				
			||||||
@@ -51,14 +40,14 @@ class ThemedMarker extends StatelessWidget {
 | 
				
			|||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          Container(
 | 
					          Container(
 | 
				
			||||||
            decoration: BoxDecoration(
 | 
					            decoration: BoxDecoration(
 | 
				
			||||||
              gradient: LinearGradient(
 | 
					              gradient: APP_GRADIENT,
 | 
				
			||||||
                colors: [Colors.red, Colors.yellow]
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              shape: BoxShape.circle,
 | 
					              shape: BoxShape.circle,
 | 
				
			||||||
              border: Border.all(color: Colors.black, width: 5),
 | 
					              border: Border.all(color: Colors.black, width: 5),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            padding: EdgeInsets.all(5),
 | 
					            width: 70,
 | 
				
			||||||
            child: icon
 | 
					            height: 70,
 | 
				
			||||||
 | 
					            padding: const EdgeInsets.all(5),
 | 
				
			||||||
 | 
					            child: Icon(landmark.type.icon.icon, size: 50),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          if (positionIndicator != null) positionIndicator,
 | 
					          if (positionIndicator != null) positionIndicator,
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:anyway/layout.dart';
 | 
					import 'package:anyway/layout.dart';
 | 
				
			||||||
 | 
					import 'package:anyway/main.dart';
 | 
				
			||||||
import 'package:anyway/structs/preferences.dart';
 | 
					import 'package:anyway/structs/preferences.dart';
 | 
				
			||||||
import 'package:anyway/structs/trip.dart';
 | 
					import 'package:anyway/structs/trip.dart';
 | 
				
			||||||
import 'package:anyway/utils/fetch_trip.dart';
 | 
					import 'package:anyway/utils/fetch_trip.dart';
 | 
				
			||||||
@@ -8,8 +9,12 @@ import 'package:flutter/material.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class NewTripButton extends StatefulWidget {
 | 
					class NewTripButton extends StatefulWidget {
 | 
				
			||||||
  final Trip trip;
 | 
					  final Trip trip;
 | 
				
			||||||
 | 
					  final UserPreferences preferences;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const NewTripButton({required this.trip});
 | 
					  const NewTripButton({
 | 
				
			||||||
 | 
					    required this.trip,
 | 
				
			||||||
 | 
					    required this.preferences,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<NewTripButton> createState() => _NewTripButtonState();
 | 
					  State<NewTripButton> createState() => _NewTripButtonState();
 | 
				
			||||||
@@ -23,42 +28,39 @@ class _NewTripButtonState extends State<NewTripButton> {
 | 
				
			|||||||
      listenable: widget.trip,
 | 
					      listenable: widget.trip,
 | 
				
			||||||
      builder: (BuildContext context, Widget? child) {
 | 
					      builder: (BuildContext context, Widget? child) {
 | 
				
			||||||
        if (widget.trip.landmarks.isEmpty){
 | 
					        if (widget.trip.landmarks.isEmpty){
 | 
				
			||||||
 | 
					          // Fallback if the trip setup is lagging behind
 | 
				
			||||||
 | 
					          // This should in theory never happen
 | 
				
			||||||
          return Container();
 | 
					          return Container();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return FloatingActionButton.extended(
 | 
					        return FloatingActionButton.extended(
 | 
				
			||||||
          onPressed: () async {
 | 
					          onPressed: onPressed,
 | 
				
			||||||
            Future<UserPreferences> preferences = loadUserPreferences();
 | 
					          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;
 | 
					      Trip trip = widget.trip;
 | 
				
			||||||
            fetchTrip(trip, preferences);
 | 
					      fetchTrip(trip, widget.preferences);
 | 
				
			||||||
      Navigator.of(context).push(
 | 
					      Navigator.of(context).push(
 | 
				
			||||||
        MaterialPageRoute(
 | 
					        MaterialPageRoute(
 | 
				
			||||||
          builder: (context) => BasePage(mainScreen: "map", trip: trip)
 | 
					          builder: (context) => BasePage(mainScreen: "map", trip: trip)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          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,
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
      } 
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,9 +6,13 @@ import 'dart:developer';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import 'package:anyway/structs/trip.dart';
 | 
					import 'package:anyway/structs/trip.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:geolocator/geolocator.dart';
 | 
				
			||||||
 | 
					import 'package:shared_preferences/shared_preferences.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NewTripLocationSearch extends StatefulWidget {
 | 
					class NewTripLocationSearch extends StatefulWidget {
 | 
				
			||||||
 | 
					  Future<SharedPreferences> prefs = SharedPreferences.getInstance();
 | 
				
			||||||
  Trip trip;
 | 
					  Trip trip;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  NewTripLocationSearch(
 | 
					  NewTripLocationSearch(
 | 
				
			||||||
    this.trip,
 | 
					    this.trip,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@@ -39,26 +43,66 @@ class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
 | 
				
			|||||||
          uuid: 'pending',
 | 
					          uuid: 'pending',
 | 
				
			||||||
          name: query,
 | 
					          name: query,
 | 
				
			||||||
          location: [location.latitude, location.longitude],
 | 
					          location: [location.latitude, location.longitude],
 | 
				
			||||||
          type: start
 | 
					          type: typeStart
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  late Widget locationSearchBar = SearchBar(
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					 | 
				
			||||||
    return SearchBar(
 | 
					 | 
				
			||||||
    hintText: 'Enter a city name or long press on the map.',
 | 
					    hintText: 'Enter a city name or long press on the map.',
 | 
				
			||||||
    onSubmitted: setTripLocation,
 | 
					    onSubmitted: setTripLocation,
 | 
				
			||||||
    controller: _controller,
 | 
					    controller: _controller,
 | 
				
			||||||
    leading: Icon(Icons.search),
 | 
					    leading: Icon(Icons.search),
 | 
				
			||||||
      trailing: [ElevatedButton(
 | 
					    trailing: [
 | 
				
			||||||
 | 
					      ElevatedButton(
 | 
				
			||||||
        onPressed: () {
 | 
					        onPressed: () {
 | 
				
			||||||
          setTripLocation(_controller.text);
 | 
					          setTripLocation(_controller.text);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        child: Text('Search'),
 | 
					        child: Text('Search'),
 | 
				
			||||||
      ),]
 | 
					      )
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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'),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,13 +1,13 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
// A map that allows the user to select a location for a new trip.
 | 
					// A map that allows the user to select a location for a new trip.
 | 
				
			||||||
import 'dart:developer';
 | 
					import 'dart:developer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:anyway/constants.dart';
 | 
					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/landmark.dart';
 | 
				
			||||||
import 'package:anyway/structs/trip.dart';
 | 
					import 'package:anyway/structs/trip.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:google_maps_flutter/google_maps_flutter.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';
 | 
					import 'package:widget_to_marker/widget_to_marker.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,7 +22,7 @@ class NewTripMap extends StatefulWidget {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _NewTripMapState extends State<NewTripMap> {
 | 
					class _NewTripMapState extends State<NewTripMap> {
 | 
				
			||||||
  final CameraPosition _cameraPosition = CameraPosition(
 | 
					  final CameraPosition _cameraPosition = const CameraPosition(
 | 
				
			||||||
    target: LatLng(48.8566, 2.3522),
 | 
					    target: LatLng(48.8566, 2.3522),
 | 
				
			||||||
    zoom: 11.0,
 | 
					    zoom: 11.0,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@@ -37,7 +37,7 @@ class _NewTripMapState extends State<NewTripMap> {
 | 
				
			|||||||
        uuid: 'pending',
 | 
					        uuid: 'pending',
 | 
				
			||||||
        name: 'start',
 | 
					        name: 'start',
 | 
				
			||||||
        location: [location.latitude, location.longitude],
 | 
					        location: [location.latitude, location.longitude],
 | 
				
			||||||
        type: start
 | 
					        type: typeStart
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -70,10 +70,26 @@ class _NewTripMapState extends State<NewTripMap> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    widget.trip.addListener(updateTripDetails);
 | 
					    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(
 | 
					    return GoogleMap(
 | 
				
			||||||
      onMapCreated: _onMapCreated,
 | 
					      onMapCreated: _onMapCreated,
 | 
				
			||||||
      initialCameraPosition: _cameraPosition,
 | 
					      initialCameraPosition: _cameraPosition,
 | 
				
			||||||
@@ -82,6 +98,8 @@ class _NewTripMapState extends State<NewTripMap> {
 | 
				
			|||||||
      cloudMapId: MAP_ID,
 | 
					      cloudMapId: MAP_ID,
 | 
				
			||||||
      mapToolbarEnabled: false,
 | 
					      mapToolbarEnabled: false,
 | 
				
			||||||
      zoomControlsEnabled: false,
 | 
					      zoomControlsEnabled: false,
 | 
				
			||||||
 | 
					      myLocationButtonEnabled: false,
 | 
				
			||||||
 | 
					      myLocationEnabled: useLocation,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								frontend/lib/modules/new_trip_options_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								frontend/lib/modules/new_trip_options_button.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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')
 | 
				
			||||||
 | 
					        ); 
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,20 +16,23 @@ class OnboardingCard extends StatelessWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  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
 | 
					    // have a different color for each card, incrementing the hue
 | 
				
			||||||
    Color currentColor = baseColor.withAlpha(baseColor.alpha - index * 30);
 | 
					    Color currentColor = baseColor.withAlpha(baseColor.alpha - index * 30);
 | 
				
			||||||
    return Container(
 | 
					    return Container(
 | 
				
			||||||
      color: currentColor,
 | 
					      color: currentColor,
 | 
				
			||||||
 | 
					      alignment: Alignment.center,
 | 
				
			||||||
      child: Padding(
 | 
					      child: Padding(
 | 
				
			||||||
        padding: EdgeInsets.all(20),
 | 
					        padding: EdgeInsets.all(20),
 | 
				
			||||||
        child: Column(
 | 
					        child: Column(
 | 
				
			||||||
 | 
					          mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
          children: [
 | 
					          children: [
 | 
				
			||||||
            Text(
 | 
					            Text(
 | 
				
			||||||
              title,
 | 
					              title,
 | 
				
			||||||
              style: TextStyle(
 | 
					              style: TextStyle(
 | 
				
			||||||
                fontSize: 24,
 | 
					                fontSize: 24,
 | 
				
			||||||
                fontWeight: FontWeight.bold,
 | 
					                fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					                color: Colors.white,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Padding(padding: EdgeInsets.only(top: 20)),
 | 
					            Padding(padding: EdgeInsets.only(top: 20)),
 | 
				
			||||||
@@ -44,7 +47,8 @@ class OnboardingCard extends StatelessWidget {
 | 
				
			|||||||
                fontSize: 16,
 | 
					                fontSize: 16,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ],
 | 
					
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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:flutter/material.dart';
 | 
				
			||||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
 | 
					import 'package:sliding_up_panel/sliding_up_panel.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:anyway/structs/trip.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_map.dart';
 | 
				
			||||||
 | 
					import 'package:anyway/modules/current_trip_panel.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,16 +26,21 @@ class _TripPageState extends State<TripPage> {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return SlidingUpPanel(
 | 
					    return SlidingUpPanel(
 | 
				
			||||||
        panelBuilder: (sc) => _panelFull(sc),
 | 
					        // use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview
 | 
				
			||||||
        // collapsed: _floatingCollapsed(),
 | 
					        panelBuilder: (scrollcontroller) => CurrentTripPanel(controller: scrollcontroller, trip: widget.trip),
 | 
				
			||||||
        body: MapWidget(trip: widget.trip),
 | 
					        // using collapsed and panelBuilder seems to show both at the same time, so we include the greeter in the panelBuilder
 | 
				
			||||||
        // renderPanelSheet: false,
 | 
					        // collapsed: Greeter(trip: widget.trip),
 | 
				
			||||||
        // backdropEnabled: true,
 | 
					        body: CurrentTripMap(trip: widget.trip),
 | 
				
			||||||
        maxHeight: MediaQuery.of(context).size.height * 0.8,
 | 
					        minHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT,
 | 
				
			||||||
        padding: EdgeInsets.only(left: 10, right: 10, top: 25, bottom: 10),
 | 
					        maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT,
 | 
				
			||||||
        // panelSnapping: false,
 | 
					        // padding in this context is annoying: it offsets the notion of vertical alignment.
 | 
				
			||||||
        borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
 | 
					        // children that want to be centered vertically need to have their size adjusted by 2x the padding
 | 
				
			||||||
        boxShadow: [
 | 
					        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(
 | 
					          BoxShadow(
 | 
				
			||||||
            blurRadius: 20.0,
 | 
					            blurRadius: 20.0,
 | 
				
			||||||
            color: Colors.black,
 | 
					            color: Colors.black,
 | 
				
			||||||
@@ -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}'),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,7 @@
 | 
				
			|||||||
import 'package:anyway/modules/new_trip_button.dart';
 | 
					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: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/structs/trip.dart";
 | 
				
			||||||
import 'package:anyway/modules/new_trip_location_search.dart';
 | 
					import 'package:anyway/modules/new_trip_location_search.dart';
 | 
				
			||||||
import 'package:anyway/modules/new_trip_map.dart';
 | 
					import 'package:anyway/modules/new_trip_map.dart';
 | 
				
			||||||
@@ -19,7 +15,6 @@ class NewTripPage extends StatefulWidget {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _NewTripPageState extends State<NewTripPage> {
 | 
					class _NewTripPageState extends State<NewTripPage> {
 | 
				
			||||||
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
 | 
					 | 
				
			||||||
  final TextEditingController latController = TextEditingController();
 | 
					  final TextEditingController latController = TextEditingController();
 | 
				
			||||||
  final TextEditingController lonController = TextEditingController();
 | 
					  final TextEditingController lonController = TextEditingController();
 | 
				
			||||||
  Trip trip = Trip();
 | 
					  Trip trip = Trip();
 | 
				
			||||||
@@ -40,7 +35,7 @@ class _NewTripPageState extends State<NewTripPage> {
 | 
				
			|||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      floatingActionButton: NewTripButton(trip: trip),
 | 
					      floatingActionButton: NewTripOptionsButton(trip: trip),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										113
									
								
								frontend/lib/pages/new_trip_preferences.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								frontend/lib/pages/new_trip_preferences.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					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),
 | 
				
			||||||
 | 
					          //   )
 | 
				
			||||||
 | 
					          // ),
 | 
				
			||||||
 | 
					          Padding(padding: EdgeInsets.only(top: 30)),
 | 
				
			||||||
 | 
					          Center(
 | 
				
			||||||
 | 
					            child: FutureBuilder(
 | 
				
			||||||
 | 
					              future: widget.trip.cityName,
 | 
				
			||||||
 | 
					              builder: (context, snapshot) => Text(
 | 
				
			||||||
 | 
					                'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}',
 | 
				
			||||||
 | 
					                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          Center(
 | 
				
			||||||
 | 
					            child: Padding(
 | 
				
			||||||
 | 
					            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]),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      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(preferences.maxTime.description),
 | 
				
			||||||
 | 
					        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();
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Column(
 | 
				
			||||||
 | 
					      children: sliders
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import 'package:anyway/modules/onboarding_card.dart';
 | 
					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';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OnboardingPage extends StatefulWidget {
 | 
					class OnboardingPage extends StatefulWidget {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										188
									
								
								frontend/lib/pages/settings.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								frontend/lib/pages/settings.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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() {
 | 
				
			||||||
 | 
					    Future<SharedPreferences> preferences = SharedPreferences.getInstance();
 | 
				
			||||||
 | 
					    return Row(
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        Text('Use location services'),
 | 
				
			||||||
 | 
					        // white space
 | 
				
			||||||
 | 
					        Spacer(),
 | 
				
			||||||
 | 
					        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(
 | 
				
			||||||
 | 
					      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));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,13 +4,13 @@ import 'dart:convert';
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
					import 'package:shared_preferences/shared_preferences.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LandmarkType sightseeing = LandmarkType(name: 'sightseeing');
 | 
					LandmarkType typeSightseeing = LandmarkType(name: 'sightseeing');
 | 
				
			||||||
LandmarkType nature = LandmarkType(name: 'nature');
 | 
					LandmarkType typeNature = LandmarkType(name: 'nature');
 | 
				
			||||||
LandmarkType shopping = LandmarkType(name: 'shopping');
 | 
					LandmarkType typeShopping = LandmarkType(name: 'shopping');
 | 
				
			||||||
// LandmarkType museum = LandmarkType(name: 'Museum');
 | 
					// LandmarkType museum = LandmarkType(name: 'Museum');
 | 
				
			||||||
// LandmarkType restaurant = LandmarkType(name: 'Restaurant');
 | 
					// LandmarkType restaurant = LandmarkType(name: 'Restaurant');
 | 
				
			||||||
LandmarkType start = LandmarkType(name: 'start');
 | 
					LandmarkType typeStart = LandmarkType(name: 'start');
 | 
				
			||||||
LandmarkType finish = LandmarkType(name: 'finish');
 | 
					LandmarkType typeFinish = LandmarkType(name: 'finish');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class Landmark extends LinkedListEntry<Landmark>{
 | 
					final class Landmark extends LinkedListEntry<Landmark>{
 | 
				
			||||||
@@ -130,22 +130,22 @@ class LandmarkType {
 | 
				
			|||||||
  LandmarkType({required this.name, this.icon = const Icon(Icons.location_on)}) {
 | 
					  LandmarkType({required this.name, this.icon = const Icon(Icons.location_on)}) {
 | 
				
			||||||
    switch (name) {
 | 
					    switch (name) {
 | 
				
			||||||
      case 'sightseeing':
 | 
					      case 'sightseeing':
 | 
				
			||||||
        icon = Icon(Icons.church);
 | 
					        icon = const Icon(Icons.church);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case 'nature':
 | 
					      case 'nature':
 | 
				
			||||||
        icon = Icon(Icons.eco);
 | 
					        icon = const Icon(Icons.eco);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case 'shopping':
 | 
					      case 'shopping':
 | 
				
			||||||
        icon = Icon(Icons.shopping_cart);
 | 
					        icon = const Icon(Icons.shopping_cart);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case 'start':
 | 
					      case 'start':
 | 
				
			||||||
        icon = Icon(Icons.play_arrow);
 | 
					        icon = const Icon(Icons.play_arrow);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case 'finish':
 | 
					      case 'finish':
 | 
				
			||||||
        icon = Icon(Icons.flag);
 | 
					        icon = const Icon(Icons.flag);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        icon = Icon(Icons.location_on);
 | 
					        icon = const Icon(Icons.location_on);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
 | 
					import 'package:anyway/structs/landmark.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SinglePreference {
 | 
					class SinglePreference {
 | 
				
			||||||
@@ -20,16 +20,6 @@ class SinglePreference {
 | 
				
			|||||||
    this.minVal = 0,
 | 
					    this.minVal = 0,
 | 
				
			||||||
    this.maxVal = 5,
 | 
					    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;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,64 +29,41 @@ class UserPreferences {
 | 
				
			|||||||
    slug: "sightseeing",
 | 
					    slug: "sightseeing",
 | 
				
			||||||
    description: "How much do you like sightseeing?",
 | 
					    description: "How much do you like sightseeing?",
 | 
				
			||||||
    value: 0,
 | 
					    value: 0,
 | 
				
			||||||
    icon: Icon(Icons.church),
 | 
					    icon: typeSightseeing.icon,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  SinglePreference shopping = SinglePreference(
 | 
					  SinglePreference shopping = SinglePreference(
 | 
				
			||||||
    name: "Shopping",
 | 
					    name: "Shopping",
 | 
				
			||||||
    slug: "shopping",
 | 
					    slug: "shopping",
 | 
				
			||||||
    description: "How much do you like shopping?",
 | 
					    description: "How much do you like shopping?",
 | 
				
			||||||
    value: 0,
 | 
					    value: 0,
 | 
				
			||||||
    icon: Icon(Icons.shopping_bag),
 | 
					    icon: typeShopping.icon,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  SinglePreference nature = SinglePreference(
 | 
					  SinglePreference nature = SinglePreference(
 | 
				
			||||||
    name: "Nature",
 | 
					    name: "Nature",
 | 
				
			||||||
    slug: "nature",
 | 
					    slug: "nature",
 | 
				
			||||||
    description: "How much do you like nature?",
 | 
					    description: "How much do you like nature?",
 | 
				
			||||||
    value: 0,
 | 
					    value: 0,
 | 
				
			||||||
    icon: Icon(Icons.landscape),
 | 
					    icon: typeNature.icon,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SinglePreference maxTime = SinglePreference(
 | 
					  SinglePreference maxTime = SinglePreference(
 | 
				
			||||||
    name: "Trip duration",
 | 
					    name: "Trip duration",
 | 
				
			||||||
    slug: "duration",
 | 
					    slug: "duration",
 | 
				
			||||||
    description: "How long do you want your trip to be?",
 | 
					    description: "How long should your trip be?",
 | 
				
			||||||
    value: 30,
 | 
					    value: 30,
 | 
				
			||||||
    minVal: 30,
 | 
					    minVal: 30,
 | 
				
			||||||
    maxVal: 720,
 | 
					    maxVal: 720,
 | 
				
			||||||
    icon: Icon(Icons.timer),
 | 
					    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() {
 | 
					  Map<String, dynamic> toJson() {
 | 
				
			||||||
      // This is "opinionated" JSON, corresponding to the backend's expectations
 | 
					      // This is "opinionated" JSON, corresponding to the backend's expectations
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        "sightseeing": {"type": "sightseeing", "score": sightseeing.value},
 | 
					        "sightseeing": {"type": "sightseeing", "score": sightseeing.value},
 | 
				
			||||||
        "shopping": {"type": "shopping", "score": shopping.value},
 | 
					        "shopping": {"type": "shopping", "score": shopping.value},
 | 
				
			||||||
        "nature": {"type": "nature", "score": nature.value},
 | 
					        "nature": {"type": "nature", "score": nature.value},
 | 
				
			||||||
        "max_time_minute": maxTime.value,
 | 
					        "max_time_minute": maxTime.value
 | 
				
			||||||
        "detour_tolerance_minute": maxDetour.value
 | 
					 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Future<UserPreferences> loadUserPreferences() async {
 | 
					 | 
				
			||||||
  UserPreferences prefs = UserPreferences();
 | 
					 | 
				
			||||||
  await prefs.load();
 | 
					 | 
				
			||||||
  return prefs;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -29,11 +29,10 @@ Dio dio = Dio(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
fetchTrip(
 | 
					fetchTrip(
 | 
				
			||||||
  Trip trip,
 | 
					  Trip trip,
 | 
				
			||||||
  Future<UserPreferences> preferences,
 | 
					  UserPreferences preferences,
 | 
				
			||||||
) async {
 | 
					) async {
 | 
				
			||||||
  UserPreferences prefs = await preferences;
 | 
					 | 
				
			||||||
  Map<String, dynamic> data = {
 | 
					  Map<String, dynamic> data = {
 | 
				
			||||||
    "preferences": prefs.toJson(),
 | 
					    "preferences": preferences.toJson(),
 | 
				
			||||||
    "start": trip.landmarks!.first.location,
 | 
					    "start": trip.landmarks!.first.location,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  String dataString = jsonEncode(data);
 | 
					  String dataString = jsonEncode(data);
 | 
				
			||||||
@@ -47,11 +46,16 @@ fetchTrip(
 | 
				
			|||||||
  // handle errors
 | 
					  // handle errors
 | 
				
			||||||
  if (response.statusCode != 200) {
 | 
					  if (response.statusCode != 200) {
 | 
				
			||||||
    trip.updateUUID("error");
 | 
					    trip.updateUUID("error");
 | 
				
			||||||
    if (response.data["detail"] != null) {
 | 
					    String errorDetail;
 | 
				
			||||||
      trip.updateError(response.data["detail"]);
 | 
					    if (response.data.runtimeType == String) {
 | 
				
			||||||
      log(response.data["detail"]);
 | 
					      errorDetail = response.data;
 | 
				
			||||||
      // throw Exception(response.data["detail"]);
 | 
					    } 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 {
 | 
					  } else {
 | 
				
			||||||
    Map<String, dynamic> json = response.data;
 | 
					    Map<String, dynamic> json = response.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,12 +5,14 @@
 | 
				
			|||||||
import FlutterMacOS
 | 
					import FlutterMacOS
 | 
				
			||||||
import Foundation
 | 
					import Foundation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import geolocator_apple
 | 
				
			||||||
import path_provider_foundation
 | 
					import path_provider_foundation
 | 
				
			||||||
import shared_preferences_foundation
 | 
					import shared_preferences_foundation
 | 
				
			||||||
import sqflite
 | 
					import sqflite
 | 
				
			||||||
import url_launcher_macos
 | 
					import url_launcher_macos
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
 | 
					func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
 | 
				
			||||||
 | 
					  GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
 | 
				
			||||||
  PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
 | 
					  PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
 | 
				
			||||||
  SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
 | 
					  SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
 | 
				
			||||||
  SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
 | 
					  SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -264,6 +264,54 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "3.2.0"
 | 
					    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:
 | 
					  google_maps:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -496,6 +544,54 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.3.0"
 | 
					    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:
 | 
					  petitparser:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -588,10 +684,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: shared_preferences_web
 | 
					      name: shared_preferences_web
 | 
				
			||||||
      sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936"
 | 
					      sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.4.0"
 | 
					    version: "2.4.2"
 | 
				
			||||||
  shared_preferences_windows:
 | 
					  shared_preferences_windows:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -825,10 +921,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: web
 | 
					      name: web
 | 
				
			||||||
      sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
 | 
					      sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.5.1"
 | 
					    version: "1.1.0"
 | 
				
			||||||
  widget_to_marker:
 | 
					  widget_to_marker:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,8 @@ dependencies:
 | 
				
			|||||||
  flutter_svg: ^2.0.10+1
 | 
					  flutter_svg: ^2.0.10+1
 | 
				
			||||||
  url_launcher: ^6.3.0
 | 
					  url_launcher: ^6.3.0
 | 
				
			||||||
  flutter_launcher_icons: ^0.13.1
 | 
					  flutter_launcher_icons: ^0.13.1
 | 
				
			||||||
 | 
					  permission_handler: ^11.3.1
 | 
				
			||||||
 | 
					  geolocator: ^13.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev_dependencies:
 | 
					dev_dependencies:
 | 
				
			||||||
  flutter_test:
 | 
					  flutter_test:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,9 +6,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "generated_plugin_registrant.h"
 | 
					#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>
 | 
					#include <url_launcher_windows/url_launcher_windows.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
 | 
					void RegisterPlugins(flutter::PluginRegistry* registry) {
 | 
				
			||||||
 | 
					  GeolocatorWindowsRegisterWithRegistrar(
 | 
				
			||||||
 | 
					      registry->GetRegistrarForPlugin("GeolocatorWindows"));
 | 
				
			||||||
 | 
					  PermissionHandlerWindowsPluginRegisterWithRegistrar(
 | 
				
			||||||
 | 
					      registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
 | 
				
			||||||
  UrlLauncherWindowsRegisterWithRegistrar(
 | 
					  UrlLauncherWindowsRegisterWithRegistrar(
 | 
				
			||||||
      registry->GetRegistrarForPlugin("UrlLauncherWindows"));
 | 
					      registry->GetRegistrarForPlugin("UrlLauncherWindows"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,8 @@
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
list(APPEND FLUTTER_PLUGIN_LIST
 | 
					list(APPEND FLUTTER_PLUGIN_LIST
 | 
				
			||||||
 | 
					  geolocator_windows
 | 
				
			||||||
 | 
					  permission_handler_windows
 | 
				
			||||||
  url_launcher_windows
 | 
					  url_launcher_windows
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user