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