account for changed itineraries once landmarks are marked as done or deleted
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m43s
				
			
		
			
				
	
				Run linting on the backend code / Build (pull_request) Successful in 26s
				
			
		
			
				
	
				Run testing on the backend code / Build (pull_request) Failing after 3m49s
				
			
		
			
				
	
				Build and release debug APK / Build APK (pull_request) Failing after 3m35s
				
			
		
			
				
	
				Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 26s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m43s
				
			Run linting on the backend code / Build (pull_request) Successful in 26s
				
			Run testing on the backend code / Build (pull_request) Failing after 3m49s
				
			Build and release debug APK / Build APK (pull_request) Failing after 3m35s
				
			Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 26s
				
			This commit is contained in:
		| @@ -1,3 +1,5 @@ | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/landmark.dart'; | ||||
| @@ -24,10 +26,16 @@ List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector | ||||
|         LandmarkCard(landmark, trip), | ||||
|       ); | ||||
|  | ||||
|       if (!landmark.visited && landmark.next != null) { | ||||
|         children.add( | ||||
|           StepBetweenLandmarks(current: landmark, next: landmark.next!) | ||||
|         ); | ||||
|       if (!landmark.visited) { | ||||
|         Landmark? nextLandmark = landmark.next; | ||||
|         while (nextLandmark != null && nextLandmark.visited) { | ||||
|           nextLandmark = nextLandmark.next; | ||||
|         }  | ||||
|         if (nextLandmark != null) { | ||||
|           children.add( | ||||
|             StepBetweenLandmarks(current: landmark, next: nextLandmark!) | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'dart:async'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:auto_size_text/auto_size_text.dart'; | ||||
|  | ||||
| @@ -37,7 +38,10 @@ class _CurrentTripLoadingIndicatorState extends State<CurrentTripLoadingIndicato | ||||
|       // As a gimmick, and a way to show that the app is still working, show a few loading dots | ||||
|       const Align( | ||||
|         alignment: Alignment.bottomCenter, | ||||
|         child: StatusText(), | ||||
|         child: Padding( | ||||
|           padding: EdgeInsets.only(bottom: 12), | ||||
|           child: StatusText(), | ||||
|         ) | ||||
|       ) | ||||
|     ], | ||||
|   ); | ||||
| @@ -81,19 +85,19 @@ Widget loadingText(Trip trip) => FutureBuilder( | ||||
|     Widget greeter; | ||||
|  | ||||
|     if (snapshot.hasData) { | ||||
|       greeter = AnimatedGradientText( | ||||
|         text: 'Creating your trip to ${snapshot.data}...', | ||||
|       greeter = AnimatedDotsText( | ||||
|         baseText: 'Creating 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 = AnimatedGradientText( | ||||
|         text: 'Error while loading trip.', | ||||
|       greeter = Text( | ||||
|         'Error while loading trip.', | ||||
|         style: greeterStyle, | ||||
|       ); | ||||
|     } else { | ||||
|       greeter = AnimatedGradientText( | ||||
|         text: 'Creating your trip...', | ||||
|       greeter = AnimatedDotsText( | ||||
|         baseText: 'Creating your trip', | ||||
|         style: greeterStyle, | ||||
|       ); | ||||
|     } | ||||
| @@ -101,61 +105,44 @@ Widget loadingText(Trip trip) => FutureBuilder( | ||||
|   } | ||||
| ); | ||||
|  | ||||
| class AnimatedGradientText extends StatefulWidget { | ||||
|   final String text; | ||||
| class AnimatedDotsText extends StatefulWidget { | ||||
|   final String baseText; | ||||
|   final TextStyle style; | ||||
|  | ||||
|   const AnimatedGradientText({ | ||||
|   const AnimatedDotsText({ | ||||
|     Key? key, | ||||
|     required this.text, | ||||
|     required this.baseText, | ||||
|     required this.style, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   _AnimatedGradientTextState createState() => _AnimatedGradientTextState(); | ||||
|   _AnimatedDotsTextState createState() => _AnimatedDotsTextState(); | ||||
| } | ||||
|  | ||||
| class _AnimatedGradientTextState extends State<AnimatedGradientText> with SingleTickerProviderStateMixin { | ||||
|   late AnimationController _controller; | ||||
| class _AnimatedDotsTextState extends State<AnimatedDotsText> { | ||||
|   int dotCount = 0; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _controller = AnimationController( | ||||
|       duration: const Duration(seconds: 1), | ||||
|       vsync: this, | ||||
|     )..repeat(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     _controller.dispose(); | ||||
|     super.dispose(); | ||||
|     Timer.periodic(const Duration(seconds: 1), (timer) { | ||||
|       if (mounted) { | ||||
|         setState(() { | ||||
|           dotCount = (dotCount + 1) % 4; | ||||
|           // show up to 3 dots | ||||
|         }); | ||||
|       } else { | ||||
|         timer.cancel(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AnimatedBuilder( | ||||
|       animation: _controller, | ||||
|       builder: (context, child) { | ||||
|         return ShaderMask( | ||||
|           shaderCallback: (bounds) { | ||||
|             return LinearGradient( | ||||
|               colors: [GRADIENT_START, GRADIENT_END, GRADIENT_START], | ||||
|               stops: [ | ||||
|                 _controller.value - 1.0, | ||||
|                 _controller.value, | ||||
|                 _controller.value + 1.0, | ||||
|               ], | ||||
|               tileMode: TileMode.mirror, | ||||
|             ).createShader(bounds); | ||||
|           }, | ||||
|           child: Text( | ||||
|             widget.text, | ||||
|             style: widget.style, | ||||
|           ), | ||||
|         ); | ||||
|       }, | ||||
|     String dots = '.' * dotCount; | ||||
|     return Text( | ||||
|       '${widget.baseText}$dots', | ||||
|       style: widget.style, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -47,32 +47,20 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|             AspectRatio( | ||||
|               aspectRatio: 3 / 4, | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                 children: [ | ||||
|                   if (widget.landmark.imageURL != null && widget.landmark.imageURL!.isNotEmpty) | ||||
|                     Expanded( | ||||
|                       child: CachedNetworkImage( | ||||
|                         imageUrl: widget.landmark.imageURL!, | ||||
|                         placeholder: (context, url) => Center(child: CircularProgressIndicator()), | ||||
|                         errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), | ||||
|                         fit: BoxFit.cover, | ||||
|                         placeholder: (context, url) => const Center(child: CircularProgressIndicator()), | ||||
|                         errorWidget: (context, url, error) => imagePlaceholder(widget.landmark), | ||||
|                         fit: BoxFit.cover | ||||
|                       ) | ||||
|                     ) | ||||
|                   else | ||||
|                     Expanded( | ||||
|                       child:  | ||||
|                       Container( | ||||
|                         decoration: BoxDecoration( | ||||
|                           gradient: LinearGradient( | ||||
|                             begin: Alignment.topLeft, | ||||
|                             end: Alignment.bottomRight, | ||||
|                             colors: [GRADIENT_START, GRADIENT_END], | ||||
|                           ), | ||||
|                         ), | ||||
|                         child: Center( | ||||
|                           child: Icon(widget.landmark.type.icon.icon, size: 50), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                     imagePlaceholder(widget.landmark), | ||||
|  | ||||
|                   if (widget.landmark.type != typeStart && widget.landmark.type != typeFinish) | ||||
|                     Container( | ||||
|                       color: PRIMARY_COLOR, | ||||
| @@ -96,47 +84,54 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|  | ||||
|             // Main information, useful buttons on the right | ||||
|             Expanded( | ||||
|               child: Padding( | ||||
|                 padding: const EdgeInsets.all(10), | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Text( | ||||
|                       widget.landmark.name, | ||||
|                       style: Theme.of(context).textTheme.titleMedium, | ||||
|                       overflow: TextOverflow.ellipsis, | ||||
|                       maxLines: 2, | ||||
|                     ), | ||||
|                        | ||||
|                     if (widget.landmark.nameEN != null) | ||||
|                       Text( | ||||
|                         widget.landmark.nameEN!, | ||||
|                         style: Theme.of(context).textTheme.bodyMedium, | ||||
|                         maxLines: 1, | ||||
|                         overflow: TextOverflow.ellipsis, | ||||
|                       ), | ||||
|  | ||||
|                     // fill the vspace | ||||
|                     const Spacer(), | ||||
|  | ||||
|                     SingleChildScrollView( | ||||
|                       // allows the buttons to be scrolled | ||||
|                       scrollDirection: Axis.horizontal, | ||||
|                       child: Wrap( | ||||
|                         spacing: 10, | ||||
|                         // show the type, the website, and the wikipedia link as buttons/labels in a row | ||||
|                         children: [ | ||||
|                           doneToggleButton(), | ||||
|                           if (widget.landmark.websiteURL != null) | ||||
|                             websiteButton(), | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   Padding( | ||||
|                     padding: const EdgeInsets.all(10), | ||||
|                     child: Column( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                       children: [ | ||||
|                         Text( | ||||
|                           widget.landmark.name, | ||||
|                           style: Theme.of(context).textTheme.titleMedium, | ||||
|                           overflow: TextOverflow.ellipsis, | ||||
|                           maxLines: 2, | ||||
|                         ), | ||||
|                            | ||||
|                           optionsButton() | ||||
|                         ], | ||||
|                       ), | ||||
|                         if (widget.landmark.nameEN != null) | ||||
|                           Text( | ||||
|                             widget.landmark.nameEN!, | ||||
|                             style: Theme.of(context).textTheme.bodyMedium, | ||||
|                             maxLines: 1, | ||||
|                             overflow: TextOverflow.ellipsis, | ||||
|                           ), | ||||
|                       ] | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|                   ), | ||||
|  | ||||
|                   // fill the vspace | ||||
|                   const Spacer(), | ||||
|  | ||||
|                   SingleChildScrollView( | ||||
|                     scrollDirection: Axis.horizontal, | ||||
|                     padding: EdgeInsets.only(left: 5, right: 5, bottom: 10), | ||||
|                     // the scroll view should be flush once the buttons are scrolled to the left | ||||
|                     // but initially there should be some padding | ||||
|                     child: Wrap( | ||||
|                       spacing: 10, | ||||
|                       // show the type, the website, and the wikipedia link as buttons/labels in a row | ||||
|                       children: [ | ||||
|                         doneToggleButton(), | ||||
|                         if (widget.landmark.websiteURL != null) | ||||
|                           websiteButton(), | ||||
|                          | ||||
|                         optionsButton() | ||||
|                       ], | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|               ) | ||||
|             ) | ||||
|           ], | ||||
|         ) | ||||
| @@ -195,3 +190,21 @@ class _LandmarkCardState extends State<LandmarkCard> { | ||||
|     ], | ||||
|   ); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| Widget imagePlaceholder (Landmark landmark) => Expanded( | ||||
|   child:  | ||||
|   Container( | ||||
|     decoration: const BoxDecoration( | ||||
|       gradient: LinearGradient( | ||||
|         begin: Alignment.topLeft, | ||||
|         end: Alignment.bottomRight, | ||||
|         colors: [GRADIENT_START, GRADIENT_END], | ||||
|       ), | ||||
|     ), | ||||
|     child: Center( | ||||
|       child: Icon(landmark.type.icon.icon, size: 50), | ||||
|     ), | ||||
|   ), | ||||
| ); | ||||
|   | ||||
| @@ -22,6 +22,14 @@ class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     int? time = widget.current.tripTime?.inMinutes; | ||||
|      | ||||
|     // since landmarks might have been marked as visited, the next landmark might not be the immediate next in the list | ||||
|     // => the precomputed trip time is not valid anymore | ||||
|     if (widget.current.next != widget.next) { | ||||
|       time = null; | ||||
|     } | ||||
|      | ||||
|     // round 0 travel time to 1 minute | ||||
|     if (time != null && time < 1) { | ||||
|       time = 1; | ||||
|     } | ||||
|   | ||||
| @@ -29,9 +29,10 @@ final class Landmark extends LinkedListEntry<Landmark>{ | ||||
|   final Duration? duration; | ||||
|   bool visited; | ||||
|  | ||||
|   // Next node | ||||
|   // Next node is implicitly available through the LinkedListEntry mixin | ||||
|   // final Landmark? next; | ||||
|   final Duration? tripTime; | ||||
|   Duration? tripTime; | ||||
|   // the trip time depends on the next landmark, so it is not final | ||||
|  | ||||
|  | ||||
|   Landmark({ | ||||
|   | ||||
| @@ -75,8 +75,16 @@ class Trip with ChangeNotifier { | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   void removeLandmark(Landmark landmark) { | ||||
|   void removeLandmark (Landmark landmark) async { | ||||
|     Landmark? previous = landmark.previous; | ||||
|     Landmark? next = landmark.next; | ||||
|     landmarks.remove(landmark); | ||||
|     // removing the landmark means we need to recompute the time between the two adjoined landmarks | ||||
|     if (previous != null && next != null) { | ||||
|       // previous.next = next happens automatically since we are using a LinkedList | ||||
|       previous.tripTime = null; | ||||
|       // TODO | ||||
|     } | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user