account for changed itineraries once landmarks are marked as done or deleted
This commit is contained in:
parent
56c55883ea
commit
6f2f86f936
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:anyway/structs/landmark.dart';
|
import 'package:anyway/structs/landmark.dart';
|
||||||
@ -24,10 +26,16 @@ List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector
|
|||||||
LandmarkCard(landmark, trip),
|
LandmarkCard(landmark, trip),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!landmark.visited && landmark.next != null) {
|
if (!landmark.visited) {
|
||||||
children.add(
|
Landmark? nextLandmark = landmark.next;
|
||||||
StepBetweenLandmarks(current: landmark, next: 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:flutter/material.dart';
|
||||||
import 'package:auto_size_text/auto_size_text.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
|
// As a gimmick, and a way to show that the app is still working, show a few loading dots
|
||||||
const Align(
|
const Align(
|
||||||
alignment: Alignment.bottomCenter,
|
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;
|
Widget greeter;
|
||||||
|
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
greeter = AnimatedGradientText(
|
greeter = AnimatedDotsText(
|
||||||
text: 'Creating your trip to ${snapshot.data}...',
|
baseText: 'Creating your trip to ${snapshot.data}',
|
||||||
style: greeterStyle,
|
style: greeterStyle,
|
||||||
);
|
);
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
// the exact error is shown in the central part of the trip overview. No need to show it here
|
// the exact error is shown in the central part of the trip overview. No need to show it here
|
||||||
greeter = AnimatedGradientText(
|
greeter = Text(
|
||||||
text: 'Error while loading trip.',
|
'Error while loading trip.',
|
||||||
style: greeterStyle,
|
style: greeterStyle,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
greeter = AnimatedGradientText(
|
greeter = AnimatedDotsText(
|
||||||
text: 'Creating your trip...',
|
baseText: 'Creating your trip',
|
||||||
style: greeterStyle,
|
style: greeterStyle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -101,61 +105,44 @@ Widget loadingText(Trip trip) => FutureBuilder(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
class AnimatedGradientText extends StatefulWidget {
|
class AnimatedDotsText extends StatefulWidget {
|
||||||
final String text;
|
final String baseText;
|
||||||
final TextStyle style;
|
final TextStyle style;
|
||||||
|
|
||||||
const AnimatedGradientText({
|
const AnimatedDotsText({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.text,
|
required this.baseText,
|
||||||
required this.style,
|
required this.style,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AnimatedGradientTextState createState() => _AnimatedGradientTextState();
|
_AnimatedDotsTextState createState() => _AnimatedDotsTextState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnimatedGradientTextState extends State<AnimatedGradientText> with SingleTickerProviderStateMixin {
|
class _AnimatedDotsTextState extends State<AnimatedDotsText> {
|
||||||
late AnimationController _controller;
|
int dotCount = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = AnimationController(
|
Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
duration: const Duration(seconds: 1),
|
if (mounted) {
|
||||||
vsync: this,
|
setState(() {
|
||||||
)..repeat();
|
dotCount = (dotCount + 1) % 4;
|
||||||
}
|
// show up to 3 dots
|
||||||
|
});
|
||||||
@override
|
} else {
|
||||||
void dispose() {
|
timer.cancel();
|
||||||
_controller.dispose();
|
}
|
||||||
super.dispose();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnimatedBuilder(
|
String dots = '.' * dotCount;
|
||||||
animation: _controller,
|
return Text(
|
||||||
builder: (context, child) {
|
'${widget.baseText}$dots',
|
||||||
return ShaderMask(
|
style: widget.style,
|
||||||
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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,32 +47,20 @@ class _LandmarkCardState extends State<LandmarkCard> {
|
|||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: 3 / 4,
|
aspectRatio: 3 / 4,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
if (widget.landmark.imageURL != null && widget.landmark.imageURL!.isNotEmpty)
|
if (widget.landmark.imageURL != null && widget.landmark.imageURL!.isNotEmpty)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: widget.landmark.imageURL!,
|
imageUrl: widget.landmark.imageURL!,
|
||||||
placeholder: (context, url) => Center(child: CircularProgressIndicator()),
|
placeholder: (context, url) => const Center(child: CircularProgressIndicator()),
|
||||||
errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
|
errorWidget: (context, url, error) => imagePlaceholder(widget.landmark),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Expanded(
|
imagePlaceholder(widget.landmark),
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.landmark.type != typeStart && widget.landmark.type != typeFinish)
|
if (widget.landmark.type != typeStart && widget.landmark.type != typeFinish)
|
||||||
Container(
|
Container(
|
||||||
color: PRIMARY_COLOR,
|
color: PRIMARY_COLOR,
|
||||||
@ -96,47 +84,54 @@ class _LandmarkCardState extends State<LandmarkCard> {
|
|||||||
|
|
||||||
// Main information, useful buttons on the right
|
// Main information, useful buttons on the right
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Column(
|
||||||
padding: const EdgeInsets.all(10),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.all(10),
|
||||||
Text(
|
child: Column(
|
||||||
widget.landmark.name,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
children: [
|
||||||
overflow: TextOverflow.ellipsis,
|
Text(
|
||||||
maxLines: 2,
|
widget.landmark.name,
|
||||||
),
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
if (widget.landmark.nameEN != null)
|
maxLines: 2,
|
||||||
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(),
|
|
||||||
|
|
||||||
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int? time = widget.current.tripTime?.inMinutes;
|
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) {
|
if (time != null && time < 1) {
|
||||||
time = 1;
|
time = 1;
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,10 @@ final class Landmark extends LinkedListEntry<Landmark>{
|
|||||||
final Duration? duration;
|
final Duration? duration;
|
||||||
bool visited;
|
bool visited;
|
||||||
|
|
||||||
// Next node
|
// Next node is implicitly available through the LinkedListEntry mixin
|
||||||
// final Landmark? next;
|
// final Landmark? next;
|
||||||
final Duration? tripTime;
|
Duration? tripTime;
|
||||||
|
// the trip time depends on the next landmark, so it is not final
|
||||||
|
|
||||||
|
|
||||||
Landmark({
|
Landmark({
|
||||||
|
@ -75,8 +75,16 @@ class Trip with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeLandmark(Landmark landmark) {
|
void removeLandmark (Landmark landmark) async {
|
||||||
|
Landmark? previous = landmark.previous;
|
||||||
|
Landmark? next = landmark.next;
|
||||||
landmarks.remove(landmark);
|
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();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user