UI and UX fixes to make new trips (and the errors it might have less confusing) #26
| @@ -41,9 +41,8 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Load secrets from github |       - name: Load secrets from github | ||||||
|         run: | |         run: | | ||||||
|           echo "${{ secrets.ANDROID_SECRET_PROPERTIES }}" > secrets.properties |           echo "${{ secrets.ANDROID_SECRET_PROPERTIES_BASE64 }}" | base64 -d > secrets.properties | ||||||
|           echo "${{ secrets.ANDROID_GOOGLE_PLAY_JSON }}" > fastlane/google-key.json |           echo "${{ secrets.ANDROID_GOOGLE_PLAY_JSON_BASE64 }}" | base64 -d > fastlane/google-key.json | ||||||
|           # decode the base64 encoded google key |  | ||||||
|           echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore |           echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore | ||||||
|         working-directory: android |         working-directory: android | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								frontend/lib/modules/current_trip_error_message.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								frontend/lib/modules/current_trip_error_message.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | import 'package:anyway/structs/trip.dart'; | ||||||
|  | import 'package:auto_size_text/auto_size_text.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
|  | class CurrentTripErrorMessage extends StatefulWidget { | ||||||
|  |   final Trip trip; | ||||||
|  |   const CurrentTripErrorMessage({ | ||||||
|  |     super.key, | ||||||
|  |     required this.trip, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<CurrentTripErrorMessage> createState() => _CurrentTripErrorMessageState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _CurrentTripErrorMessageState extends State<CurrentTripErrorMessage> { | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) => Center( | ||||||
|  |     child: Row( | ||||||
|  |       mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |       children: [ | ||||||
|  |         const Icon( | ||||||
|  |           Icons.error_outline, | ||||||
|  |           color: Colors.red, | ||||||
|  |           size: 50, | ||||||
|  |         ), | ||||||
|  |         const Padding( | ||||||
|  |           padding: EdgeInsets.only(left: 10), | ||||||
|  |         ), | ||||||
|  |         AutoSizeText( | ||||||
|  |           'Error: ${widget.trip.errorDescription}', | ||||||
|  |           maxLines: 3, | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ) | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -1,37 +1,27 @@ | |||||||
| import 'dart:developer'; | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
| import 'package:anyway/constants.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:anyway/pages/current_trip.dart'; | ||||||
|  | import 'package:anyway/structs/trip.dart'; | ||||||
|  |  | ||||||
| class Greeter extends StatefulWidget { |  | ||||||
|  | class CurrentTripGreeter extends StatefulWidget { | ||||||
|   final Trip trip; |   final Trip trip; | ||||||
|  |  | ||||||
|   Greeter({ |   CurrentTripGreeter({ | ||||||
|  |     super.key, | ||||||
|     required this.trip, |     required this.trip, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<Greeter> createState() => _GreeterState(); |   State<CurrentTripGreeter> createState() => _CurrentTripGreeterState(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| class _GreeterState extends State<Greeter> { | class _CurrentTripGreeterState extends State<CurrentTripGreeter> { | ||||||
|    |   @override | ||||||
|   Widget greeterBuilder (BuildContext context, Widget? child) { |   Widget build(BuildContext context) => Center( | ||||||
|     final Shader textGradient = APP_GRADIENT.createShader(Rect.fromLTWH(0.0, 0.0, 200.0, 70.0)); |     child: FutureBuilder( | ||||||
|     TextStyle greeterStyle = TextStyle( |  | ||||||
|       foreground: Paint()..shader = textGradient, |  | ||||||
|       fontWeight: FontWeight.bold, |  | ||||||
|       fontSize: 26 |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     Widget topGreeter; |  | ||||||
|  |  | ||||||
|     if (widget.trip.uuid != 'pending') { |  | ||||||
|       topGreeter = FutureBuilder( |  | ||||||
|       future: widget.trip.cityName, |       future: widget.trip.cityName, | ||||||
|       builder: (BuildContext context, AsyncSnapshot<String> snapshot) { |       builder: (BuildContext context, AsyncSnapshot<String> snapshot) { | ||||||
|         if (snapshot.hasData) { |         if (snapshot.hasData) { | ||||||
| @@ -41,7 +31,6 @@ class _GreeterState extends State<Greeter> { | |||||||
|             style: greeterStyle |             style: greeterStyle | ||||||
|           ); |           ); | ||||||
|         } else if (snapshot.hasError) { |         } else if (snapshot.hasError) { | ||||||
|             log('Error while fetching city name'); |  | ||||||
|           return AutoSizeText( |           return AutoSizeText( | ||||||
|             maxLines: 1, |             maxLines: 1, | ||||||
|             'Welcome to your trip!', |             'Welcome to your trip!', | ||||||
| @@ -55,57 +44,7 @@ class _GreeterState extends State<Greeter> { | |||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       ); |  | ||||||
|     } else { |  | ||||||
|       // still awaiting the trip |  | ||||||
|       // We can hopefully infer the city name from the cityName future |  | ||||||
|       // Show a linear loader at the bottom and an info message above |  | ||||||
|       topGreeter = Column( |  | ||||||
|         mainAxisAlignment: MainAxisAlignment.end, |  | ||||||
|         children: [ |  | ||||||
|           FutureBuilder( |  | ||||||
|             future: widget.trip.cityName, |  | ||||||
|             builder: (BuildContext context, AsyncSnapshot<String> snapshot) { |  | ||||||
|               if (snapshot.hasData) { |  | ||||||
|                 return AutoSizeText( |  | ||||||
|                   maxLines: 1, |  | ||||||
|                   'Generating your trip to ${snapshot.data}...', |  | ||||||
|                   style: greeterStyle |  | ||||||
|                 ); |  | ||||||
|               } else if (snapshot.hasError) { |  | ||||||
|                 // the exact error is shown in the central part of the trip overview. No need to show it here |  | ||||||
|                 return AutoSizeText( |  | ||||||
|                   maxLines: 1, |  | ||||||
|                   'Error while loading trip.', |  | ||||||
|                   style: greeterStyle |  | ||||||
|                   ); |  | ||||||
|               } |  | ||||||
|               return AutoSizeText( |  | ||||||
|                   maxLines: 1, |  | ||||||
|                   'Generating your trip...', |  | ||||||
|                   style: greeterStyle |  | ||||||
|                   ); |  | ||||||
|             } |  | ||||||
|           ), |  | ||||||
|           Padding( |  | ||||||
|             padding: EdgeInsets.all(5), |  | ||||||
|             child: const LinearProgressIndicator() |  | ||||||
|     ) |     ) | ||||||
|         ] |  | ||||||
|   ); |   ); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return Center( |  | ||||||
|       child: topGreeter, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     return ListenableBuilder( |  | ||||||
|       listenable: widget.trip, |  | ||||||
|       builder: greeterBuilder, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
							
								
								
									
										60
									
								
								frontend/lib/modules/current_trip_loading_indicator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								frontend/lib/modules/current_trip_loading_indicator.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:auto_size_text/auto_size_text.dart'; | ||||||
|  |  | ||||||
|  | import 'package:anyway/structs/trip.dart'; | ||||||
|  | import 'package:anyway/pages/current_trip.dart'; | ||||||
|  |  | ||||||
|  | class CurrentTripLoadingIndicator extends StatefulWidget { | ||||||
|  |   final Trip trip; | ||||||
|  |   const CurrentTripLoadingIndicator({ | ||||||
|  |     super.key, | ||||||
|  |     required this.trip, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<CurrentTripLoadingIndicator> createState() => _CurrentTripLoadingIndicatorState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _CurrentTripLoadingIndicatorState extends State<CurrentTripLoadingIndicator> { | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) => Center( | ||||||
|  |     child: FutureBuilder( | ||||||
|  |       future: widget.trip.cityName, | ||||||
|  |       builder: (BuildContext context, AsyncSnapshot<String> snapshot) { | ||||||
|  |         Widget greeter; | ||||||
|  |         Widget loadingIndicator = const Padding( | ||||||
|  |           padding: EdgeInsets.only(top: 10), | ||||||
|  |           child: CircularProgressIndicator() | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         if (snapshot.hasData) { | ||||||
|  |           greeter = AutoSizeText( | ||||||
|  |             maxLines: 1, | ||||||
|  |             'Generating your trip to ${snapshot.data}...', | ||||||
|  |             style: greeterStyle, | ||||||
|  |           ); | ||||||
|  |         } else if (snapshot.hasError) { | ||||||
|  |           // the exact error is shown in the central part of the trip overview. No need to show it here | ||||||
|  |           greeter = AutoSizeText( | ||||||
|  |             maxLines: 1, | ||||||
|  |             'Error while loading trip.', | ||||||
|  |             style: greeterStyle, | ||||||
|  |             ); | ||||||
|  |         } else { | ||||||
|  |           greeter = AutoSizeText( | ||||||
|  |             maxLines: 1, | ||||||
|  |             'Generating your trip...', | ||||||
|  |             style: greeterStyle, | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         return Column( | ||||||
|  |           mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             greeter, | ||||||
|  |             loadingIndicator, | ||||||
|  |           ], | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -1,4 +1,6 @@ | |||||||
| import 'package:anyway/constants.dart'; | import 'package:anyway/constants.dart'; | ||||||
|  | import 'package:anyway/modules/current_trip_error_message.dart'; | ||||||
|  | import 'package:anyway/modules/current_trip_loading_indicator.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
| import 'package:anyway/structs/trip.dart'; | import 'package:anyway/structs/trip.dart'; | ||||||
| @@ -28,16 +30,36 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> { | |||||||
|     return ListenableBuilder( |     return ListenableBuilder( | ||||||
|       listenable: widget.trip, |       listenable: widget.trip, | ||||||
|       builder: (context, child) { |       builder: (context, child) { | ||||||
|         if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') { |         if (widget.trip.uuid == 'error') { | ||||||
|  |           return Align( | ||||||
|  |               alignment: Alignment.topCenter, | ||||||
|  |               child: 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: CurrentTripErrorMessage(trip: widget.trip) | ||||||
|  |               ), | ||||||
|  |             ); | ||||||
|  |         } else if (widget.trip.uuid == 'pending') { | ||||||
|  |             return Align( | ||||||
|  |               alignment: Alignment.topCenter, | ||||||
|  |               child: 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: CurrentTripLoadingIndicator(trip: widget.trip), | ||||||
|  |               ), | ||||||
|  |             ); | ||||||
|  |         } else { | ||||||
|           return ListView( |           return ListView( | ||||||
|             controller: widget.controller, |             controller: widget.controller, | ||||||
|             padding: const EdgeInsets.only(bottom: 30, left: 5, right: 5), |             padding: const EdgeInsets.only(bottom: 30), | ||||||
|             children: [ |             children: [ | ||||||
|               SizedBox( |               SizedBox( | ||||||
|                 // reuse the exact same height as the panel has when collapsed |                 // reuse the exact same height as the panel has when collapsed | ||||||
|                 // this way the greeter will be centered when the panel is collapsed |                 // this way the greeter will be centered when the panel is collapsed | ||||||
|                 height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20, |                 height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20, | ||||||
|                 child: Greeter(trip: widget.trip), |                 child: CurrentTripGreeter(trip: widget.trip), | ||||||
|               ), |               ), | ||||||
|  |  | ||||||
|               const Padding(padding: EdgeInsets.only(top: 10)), |               const Padding(padding: EdgeInsets.only(top: 10)), | ||||||
| @@ -53,28 +75,6 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> { | |||||||
|               Center(child: saveButton(widget.trip)), |               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}'), |  | ||||||
|               ), |  | ||||||
|             ], |  | ||||||
|           ); |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -6,6 +6,12 @@ import 'package:anyway/structs/trip.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'; | import 'package:anyway/modules/current_trip_panel.dart'; | ||||||
|  |  | ||||||
|  | final Shader textGradient = APP_GRADIENT.createShader(Rect.fromLTWH(0.0, 0.0, 200.0, 70.0)); | ||||||
|  | TextStyle greeterStyle = TextStyle( | ||||||
|  |   foreground: Paint()..shader = textGradient, | ||||||
|  |   fontWeight: FontWeight.bold, | ||||||
|  |   fontSize: 26 | ||||||
|  | ); | ||||||
|  |  | ||||||
|  |  | ||||||
| class TripPage extends StatefulWidget { | class TripPage extends StatefulWidget { | ||||||
| @@ -35,7 +41,7 @@ class _TripPageState extends State<TripPage> { | |||||||
|         maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT, |         maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT, | ||||||
|         // padding in this context is annoying: it offsets the notion of vertical alignment. |         // padding in this context is annoying: it offsets the notion of vertical alignment. | ||||||
|         // children that want to be centered vertically need to have their size adjusted by 2x the padding |         // children that want to be centered vertically need to have their size adjusted by 2x the padding | ||||||
|         padding: const EdgeInsets.only(top: 10), |         padding: const EdgeInsets.all(10.0), | ||||||
|         // Panel snapping should not be disabled because it significantly improves the user experience |         // Panel snapping should not be disabled because it significantly improves the user experience | ||||||
|         // panelSnapping: false |         // panelSnapping: false | ||||||
|         borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), |         borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), | ||||||
|   | |||||||
| @@ -38,10 +38,18 @@ fetchTrip( | |||||||
|   String dataString = jsonEncode(data); |   String dataString = jsonEncode(data); | ||||||
|   log(dataString); |   log(dataString); | ||||||
|  |  | ||||||
|   final response = await dio.post( |   late Response response; | ||||||
|  |   try { | ||||||
|  |      response = await dio.post( | ||||||
|       "/trip/new", |       "/trip/new", | ||||||
|       data: data |       data: data | ||||||
|     ); |     ); | ||||||
|  |   } catch (e) { | ||||||
|  |     trip.updateUUID("error"); | ||||||
|  |     trip.updateError(e.toString()); | ||||||
|  |     log(e.toString()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // handle errors |   // handle errors | ||||||
|   if (response.statusCode != 200) { |   if (response.statusCode != 200) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user