better visual coherence

This commit is contained in:
Remy Moll 2024-09-25 14:47:13 +02:00
parent d323194ea7
commit 88b825ea31
16 changed files with 221 additions and 131 deletions

View File

@ -13,6 +13,11 @@ const Color GRADIENT_END = Color(0xFFE72E77);
const Color PRIMARY_COLOR = Color(0xFFF38F1A); 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( ThemeData APP_THEME = ThemeData(
primaryColor: PRIMARY_COLOR, primaryColor: PRIMARY_COLOR,
@ -33,20 +38,43 @@ ThemeData APP_THEME = ThemeData(
), ),
textButtonTheme: TextButtonThemeData( textButtonTheme: const TextButtonThemeData(
style: ButtonStyle(
style: TextButton.styleFrom( foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
backgroundColor: PRIMARY_COLOR, side: WidgetStatePropertyAll(
textStyle: TextStyle( BorderSide(
color: Colors.red color: PRIMARY_COLOR,
width: 1,
),
),
) )
), ),
),
buttonTheme: ButtonThemeData( elevatedButtonTheme: const ElevatedButtonThemeData(
buttonColor: PRIMARY_COLOR, style: ButtonStyle(
textTheme: ButtonTextTheme.primary, 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
)
); );

View File

@ -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) {

View File

@ -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"),
); );

View File

@ -1,7 +1,7 @@
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';

View 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}'),
),
],
);
}
}
);
}
}

View File

@ -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,

View 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}'),
],
);
}
}

View File

@ -36,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
@ -84,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: theme.iconButtonTheme.style,
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: theme.iconButtonTheme.style,
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: theme.iconButtonTheme.style,
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!));
@ -108,7 +105,6 @@ class _LandmarkCardState extends State<LandmarkCard> {
), ),
if (widget.landmark.wikipediaURL != null) if (widget.landmark.wikipediaURL != null)
TextButton.icon( TextButton.icon(
style: theme.iconButtonTheme.style,
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!));

View File

@ -17,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,
@ -56,8 +44,10 @@ class ThemedMarker extends StatelessWidget {
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,
], ],

View File

@ -42,7 +42,7 @@ 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
) )
); );
} }
@ -65,9 +65,8 @@ class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
late Widget useCurrentLocationButton = ElevatedButton( late Widget useCurrentLocationButton = ElevatedButton(
onPressed: () { onPressed: () async {
// setTripLocation(location);
// TODO: get current location
}, },
child: Text('Use current location'), child: Text('Use current location'),
); );

View File

@ -2,7 +2,7 @@
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';
@ -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
) )
); );
} }

View File

@ -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,15 +26,20 @@ 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),
// using collapsed and panelBuilder seems to show both at the same time, so we include the greeter in the panelBuilder
// collapsed: Greeter(trip: widget.trip),
body: CurrentTripMap(trip: widget.trip), body: CurrentTripMap(trip: widget.trip),
// renderPanelSheet: false, minHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT,
// backdropEnabled: true, maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT,
maxHeight: MediaQuery.of(context).size.height * 0.8, // padding in this context is annoying: it offsets the notion of vertical alignment.
padding: EdgeInsets.only(left: 10, right: 10, top: 25, bottom: 10), // children that want to be centered vertically need to have their size adjusted by 2x the padding
// panelSnapping: false, padding: const EdgeInsets.only(top: 10),
borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), // 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: const [
BoxShadow( BoxShadow(
blurRadius: 20.0, blurRadius: 20.0,
@ -44,41 +48,4 @@ class _TripPageState extends State<TripPage> {
], ],
); );
} }
Widget _panelFull(ScrollController sc) {
return ListenableBuilder(
listenable: widget.trip,
builder: (context, child) {
if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') {
return ListView(
controller: sc,
padding: EdgeInsets.only(bottom: 35),
children: [
Greeter(trip: widget.trip),
...landmarksList(widget.trip),
Padding(padding: EdgeInsets.only(top: 10)),
Center(child: saveButton(widget.trip)),
],
);
} else if(widget.trip.uuid == 'pending') {
return Greeter(trip: widget.trip);
} else {
return Column(
children: [
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${widget.trip.errorDescription}'),
),
],
);
}
}
);
}
} }

View File

@ -28,25 +28,26 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
// child: Icon(Icons.person, size: 100), // child: Icon(Icons.person, size: 100),
// ) // )
// ), // ),
Padding(padding: EdgeInsets.only(top: 30)),
Center( Center(
child: FutureBuilder( child: FutureBuilder(
future: widget.trip.cityName, future: widget.trip.cityName,
builder: (context, snapshot) => Text( builder: (context, snapshot) => Text(
'New trip to ${snapshot.hasData ? snapshot.data! : "..."}', 'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}',
style: TextStyle(fontSize: 24) style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)
) )
) )
), ),
Divider(indent: 25, endIndent: 25, height: 50),
Center( Center(
child: Padding( child: Padding(
padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10), padding: EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 0),
child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18)) child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18))
), ),
), ),
Divider(indent: 25, endIndent: 25, height: 50),
durationPicker(preferences.maxTime), durationPicker(preferences.maxTime),
preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]), preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]),
@ -63,7 +64,7 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
shadowColor: Colors.grey, shadowColor: Colors.grey,
child: ListTile( child: ListTile(
leading: Icon(Icons.timer), leading: Icon(Icons.timer),
title: Text('How long should the trip be?'), title: Text(preferences.maxTime.description),
subtitle: CupertinoTimerPicker( subtitle: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm, mode: CupertinoTimerPickerMode.hm,
initialTimerDuration: Duration(minutes: 90), initialTimerDuration: Duration(minutes: 90),
@ -84,8 +85,6 @@ class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
for (SinglePreference pref in prefs) { for (SinglePreference pref in prefs) {
sliders.add( sliders.add(
Card( Card(
margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
shadowColor: Colors.grey,
child: ListTile( child: ListTile(
leading: pref.icon, leading: pref.icon,
title: Text(pref.name), title: Text(pref.name),

View File

@ -174,8 +174,8 @@ class _SettingsPageState extends State<SettingsPage> {
Text('Our privacy policy is available under:'), Text('Our privacy policy is available under:'),
TextButton.icon( TextButton.icon(
icon: Icon(Icons.info, color: Colors.white), icon: Icon(Icons.info),
label: Text(PRIVACY_URL, style: TextStyle(color: Colors.white)), label: Text(PRIVACY_URL),
onPressed: () async{ onPressed: () async{
await launchUrl(Uri.parse(PRIVACY_URL)); await launchUrl(Uri.parse(PRIVACY_URL));
} }

View File

@ -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

View File

@ -1,3 +1,4 @@
import 'package:anyway/structs/landmark.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -28,27 +29,27 @@ 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,