revamp new trip flow
This commit is contained in:
		| @@ -1,11 +1,7 @@ | ||||
| import 'package:anyway/modules/new_trip_button.dart'; | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| import 'package:anyway/modules/new_trip_options_button.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:geocoding/geocoding.dart'; | ||||
| 
 | ||||
| import 'package:anyway/layout.dart'; | ||||
| import 'package:anyway/utils/fetch_trip.dart'; | ||||
| import 'package:anyway/structs/preferences.dart'; | ||||
| import "package:anyway/structs/trip.dart"; | ||||
| import 'package:anyway/modules/new_trip_location_search.dart'; | ||||
| import 'package:anyway/modules/new_trip_map.dart'; | ||||
| @@ -19,7 +15,6 @@ class NewTripPage extends StatefulWidget { | ||||
| } | ||||
| 
 | ||||
| class _NewTripPageState extends State<NewTripPage> { | ||||
|   final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); | ||||
|   final TextEditingController latController = TextEditingController(); | ||||
|   final TextEditingController lonController = TextEditingController(); | ||||
|   Trip trip = Trip(); | ||||
| @@ -40,7 +35,7 @@ class _NewTripPageState extends State<NewTripPage> { | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       floatingActionButton: NewTripButton(trip: trip), | ||||
|       floatingActionButton: NewTripOptionsButton(trip: trip), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										114
									
								
								frontend/lib/pages/new_trip_preferences.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								frontend/lib/pages/new_trip_preferences.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| import 'package:anyway/modules/new_trip_button.dart'; | ||||
| import 'package:anyway/structs/preferences.dart'; | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
|  | ||||
|  | ||||
| class NewTripPreferencesPage extends StatefulWidget { | ||||
|   final Trip trip; | ||||
|   const NewTripPreferencesPage({required this.trip}); | ||||
|  | ||||
|   @override | ||||
|   _NewTripPreferencesPageState createState() => _NewTripPreferencesPageState(); | ||||
| } | ||||
|  | ||||
| class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> { | ||||
|   UserPreferences preferences = UserPreferences(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       body: ListView( | ||||
|         children: [ | ||||
|           // Center( | ||||
|           //   child: CircleAvatar( | ||||
|           //     radius: 100, | ||||
|           //     child: Icon(Icons.person, size: 100), | ||||
|           //   ) | ||||
|           // ), | ||||
|           Center( | ||||
|             child: FutureBuilder( | ||||
|               future: widget.trip.cityName, | ||||
|               builder: (context, snapshot) => Text( | ||||
|                 'New trip to ${snapshot.hasData ? snapshot.data! : "..."}', | ||||
|                 style: TextStyle(fontSize: 24) | ||||
|               ) | ||||
|             ) | ||||
|           ), | ||||
|  | ||||
|           Divider(indent: 25, endIndent: 25, height: 50), | ||||
|  | ||||
|           Center( | ||||
|             child: Padding( | ||||
|             padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10), | ||||
|               child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18)) | ||||
|             ), | ||||
|           ), | ||||
|  | ||||
|           durationPicker(preferences.maxTime), | ||||
|  | ||||
|           preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]), | ||||
|         ] | ||||
|       ), | ||||
|       floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   Widget durationPicker(SinglePreference maxTime) { | ||||
|     return Card( | ||||
|       margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0), | ||||
|       shadowColor: Colors.grey, | ||||
|       child: ListTile( | ||||
|         leading: Icon(Icons.timer), | ||||
|         title: Text('How long should the trip be?'), | ||||
|         subtitle: CupertinoTimerPicker( | ||||
|           mode: CupertinoTimerPickerMode.hm, | ||||
|           initialTimerDuration: Duration(minutes: 90), | ||||
|           minuteInterval: 15, | ||||
|           onTimerDurationChanged: (Duration newDuration) { | ||||
|             setState(() { | ||||
|               preferences.maxTime.value = newDuration.inMinutes; | ||||
|             }); | ||||
|           }, | ||||
|         ) | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   Widget preferenceSliders(List<SinglePreference> prefs) { | ||||
|     List<Card> sliders = []; | ||||
|       for (SinglePreference pref in prefs) { | ||||
|       sliders.add( | ||||
|         Card( | ||||
|           child: ListTile( | ||||
|             leading: pref.icon, | ||||
|             title: Text(pref.name), | ||||
|             subtitle: Slider( | ||||
|               value: pref.value.toDouble(), | ||||
|               min: pref.minVal.toDouble(), | ||||
|               max: pref.maxVal.toDouble(), | ||||
|               divisions: pref.maxVal - pref.minVal, | ||||
|               label: pref.value.toString(), | ||||
|               onChanged: (double newValue) { | ||||
|                 setState(() { | ||||
|                   pref.value = newValue.toInt(); | ||||
|                 }); | ||||
|               }, | ||||
|             ) | ||||
|           ), | ||||
|           margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0), | ||||
|           shadowColor: Colors.grey, | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return Column( | ||||
|       children: sliders | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -1,5 +1,5 @@ | ||||
| import 'package:anyway/modules/onboarding_card.dart'; | ||||
| import 'package:anyway/pages/new_trip.dart'; | ||||
| import 'package:anyway/pages/new_trip_location.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class OnboardingPage extends StatefulWidget { | ||||
|   | ||||
| @@ -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); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										177
									
								
								frontend/lib/pages/settings.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								frontend/lib/pages/settings.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/main.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:permission_handler/permission_handler.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
|  | ||||
|  | ||||
| bool debugMode = false; | ||||
| bool useLocation = false; | ||||
|  | ||||
| class SettingsPage extends StatefulWidget { | ||||
|   @override | ||||
|   _SettingsPageState createState() => _SettingsPageState(); | ||||
| } | ||||
|  | ||||
| class _SettingsPageState extends State<SettingsPage> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return ListView( | ||||
|       padding: EdgeInsets.all(15), | ||||
|       children: [ | ||||
|         // First a round, centered image | ||||
|         Center( | ||||
|           child: CircleAvatar( | ||||
|             radius: 75, | ||||
|             child: Icon(Icons.settings, size: 100), | ||||
|           ) | ||||
|         ), | ||||
|         Center( | ||||
|           child: Text('Global settings', style: TextStyle(fontSize: 24)) | ||||
|         ), | ||||
|  | ||||
|         Divider(indent: 25, endIndent: 25, height: 50), | ||||
|  | ||||
|         darkMode(), | ||||
|         setLocationUsage(), | ||||
|         setDebugMode(), | ||||
|  | ||||
|         Divider(indent: 25, endIndent: 25, height: 50), | ||||
|  | ||||
|         privacyInfo(), | ||||
|       ] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget setDebugMode() { | ||||
|     return Row( | ||||
|       children: [ | ||||
|         Text('Debugging: use a custom API URL'), | ||||
|         // white space | ||||
|         Spacer(), | ||||
|         Switch( | ||||
|           value: debugMode, | ||||
|           onChanged: (bool? newValue) { | ||||
|             setState(() { | ||||
|               debugMode = newValue!; | ||||
|               if (debugMode) { | ||||
|                 showDialog( | ||||
|                   context: context, | ||||
|                   builder: (BuildContext context) { | ||||
|                     return AlertDialog( | ||||
|                       title: Text('Debug mode - use a custom API endpoint'), | ||||
|                       content: TextField( | ||||
|                         decoration: InputDecoration( | ||||
|                           hintText: 'https://anyway-stg.anydev.info' | ||||
|                         ), | ||||
|                         onChanged: (value) { | ||||
|                           setState(() { | ||||
|                             API_URL_BASE = value; | ||||
|                           }); | ||||
|                         }, | ||||
|                       ), | ||||
|                       actions: [ | ||||
|                         TextButton( | ||||
|                           child: Text('OK'), | ||||
|                           onPressed: () { | ||||
|                             Navigator.of(context).pop(); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ], | ||||
|                     ); | ||||
|                   } | ||||
|                 ); | ||||
|               } | ||||
|             }); | ||||
|           } | ||||
|         ) | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|   Widget darkMode() { | ||||
|     return Row( | ||||
|       children: [ | ||||
|         Text('Dark mode'), | ||||
|         Spacer(), | ||||
|         Switch( | ||||
|           value: Theme.of(context).brightness == Brightness.dark, | ||||
|           onChanged: (bool? newValue) { | ||||
|             setState(() { | ||||
|               rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|                 SnackBar(content: Text('Dark mode is not implemented yet')) | ||||
|               ); | ||||
|               // if (newValue!) { | ||||
|               //   // Dark mode | ||||
|               //   Theme.of(context).brightness = Brightness.dark; | ||||
|               // } else { | ||||
|               //   // Light mode | ||||
|               //   Theme.of(context).brightness = Brightness.light; | ||||
|               // } | ||||
|             }); | ||||
|           } | ||||
|         ) | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|   Widget setLocationUsage() { | ||||
|     return Row( | ||||
|       children: [ | ||||
|         Text('Use location services'), | ||||
|         // white space | ||||
|         Spacer(), | ||||
|         Switch( | ||||
|           value: useLocation, | ||||
|           onChanged: (bool? newValue) async { | ||||
|             await Permission.locationWhenInUse | ||||
|               .onDeniedCallback(() { | ||||
|                 rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|                   SnackBar(content: Text('Location services are required for this feature')) | ||||
|                 ); | ||||
|               }) | ||||
|               .onGrantedCallback(() { | ||||
|                 rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|                   SnackBar(content: Text('Location services are now enabled')) | ||||
|                 ); | ||||
|                 setState(() { | ||||
|                   useLocation = newValue!; | ||||
|                 }); | ||||
|                 SharedPreferences.getInstance().then( | ||||
|                   (SharedPreferences prefs) { | ||||
|                     prefs.setBool('useLocation', useLocation); | ||||
|                   } | ||||
|                 ); | ||||
|               }) | ||||
|               .onPermanentlyDeniedCallback(() { | ||||
|                 rootScaffoldMessengerKey.currentState!.showSnackBar( | ||||
|                   SnackBar(content: Text('Location services are required for this feature')) | ||||
|                 ); | ||||
|               })   | ||||
|             .request(); | ||||
|           } | ||||
|         ) | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   Widget privacyInfo() { | ||||
|     return Center( | ||||
|       child: Column( | ||||
|         children: [ | ||||
|           Text('Our privacy policy is available under:'), | ||||
|            | ||||
|           TextButton.icon( | ||||
|             icon: Icon(Icons.info), | ||||
|             label: Text(PRIVACY_URL), | ||||
|             onPressed: () async{ | ||||
|               await launchUrl(Uri.parse(PRIVACY_URL)); | ||||
|             } | ||||
|           ) | ||||
|         ], | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user