Merge pull request 'Usability and styling' (#24) from feature/frontend-usability-and-styling into main
Reviewed-on: #24
This commit is contained in:
commit
06dc0c7c3b
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user