better errorhandling, slimmed down optimizer
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m40s
Build and release APK / Build APK (pull_request) Successful in 5m26s

This commit is contained in:
2024-08-05 15:27:25 +02:00
parent 71d9554d97
commit 89511f39cb
9 changed files with 138 additions and 70 deletions

View File

@@ -1,4 +1,4 @@
const String APP_NAME = 'AnyWay';
const String API_URL_BASE = 'https://anyway.kluster.moll.re';
String API_URL_BASE = 'https://anyway.kluster.moll.re';

View File

@@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:anyway/structs/trip.dart';
import 'package:auto_size_text/auto_size_text.dart';
@@ -15,12 +17,15 @@ class Greeter extends StatefulWidget {
}
class _GreeterState extends State<Greeter> {
Widget greeterBuilder (BuildContext context, Widget? child) {
ThemeData theme = Theme.of(context);
TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24);
Widget topGreeter;
if (widget.trip.landmarks.length > 1) {
if (widget.trip.uuid != 'pending') {
topGreeter = FutureBuilder(
future: widget.trip.cityName,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
@@ -28,17 +33,20 @@ class _GreeterState extends State<Greeter> {
return AutoSizeText(
maxLines: 1,
'Welcome to ${snapshot.data}!',
style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
style: greeterStyle
);
} else if (snapshot.hasError) {
return const AutoSizeText(
log('Error while fetching city name');
return AutoSizeText(
maxLines: 1,
'Welcome to your trip!'
'Welcome to your trip!',
style: greeterStyle
);
} else {
return const AutoSizeText(
return AutoSizeText(
maxLines: 1,
'Welcome to ...'
'Welcome to ...',
style: greeterStyle
);
}
}
@@ -54,14 +62,24 @@ class _GreeterState extends State<Greeter> {
future: widget.trip.cityName,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(
return AutoSizeText(
maxLines: 1,
'Generating your trip to ${snapshot.data}...',
style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
style: greeterStyle
);
} else if (snapshot.hasError) {
return const Text('Error while fetching city name');
// 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 const Text('Generating your trip...');
return AutoSizeText(
maxLines: 1,
'Generating your trip...',
style: greeterStyle
);
}
),
Padding(

View File

@@ -1,4 +1,3 @@
import 'dart:collection';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -19,32 +18,19 @@ class LandmarksOverview extends StatefulWidget {
}
class _LandmarksOverviewState extends State<LandmarksOverview> {
// final Future<List<Landmark>> _landmarks = fetchLandmarks();
@override
Widget build(BuildContext context) {
return ListenableBuilder(//<LinkedList<Landmark>>
return ListenableBuilder(
listenable: widget.trip!,
builder: (BuildContext context, Widget? child) {
Trip trip = widget.trip!;
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
List<Widget> children;
if (trip.uuid == 'pending') {
// the trip is still being fetched from the api
children = [Center(child: CircularProgressIndicator())];
} else if (trip.uuid == 'error') {
children = [
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${trip.cityName}'),
),
];
} else {
if (trip.uuid != 'pending' && trip.uuid != 'error') {
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
if (trip.landmarks.length <= 1) {
children = [
const Text("No landmarks in this trip"),
@@ -55,7 +41,26 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
saveButton(),
];
}
} else if(trip.uuid == 'pending') {
// the trip is still being fetched from the api
children = [Center(child: CircularProgressIndicator())];
} else {
// trip.uuid == 'error'
// show the error raised by the api
// String error =
children = [
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${trip.errorDescription}'),
),
];
}
return Column(
children: children,
);
@@ -119,11 +124,6 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
Widget stepBetweenLandmarks(Landmark current, Landmark next) {
// This is a simple widget that draws a line between landmark-cards
// It's a vertical dotted line
// Next to the line is the icon for the mode of transport (walking for now) and the estimated time
// There is also a button to open the navigation instructions as a new intent
// next landmark is not actually required, but it ensures that the widget is deleted when the next landmark is removed (which makes sense, because then there will be another step)
int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5;
// ~/ is integer division (rounding)
return Container(

View File

@@ -1,3 +1,4 @@
import 'package:anyway/constants.dart';
import 'package:anyway/structs/preferences.dart';
import 'package:flutter/material.dart';
@@ -24,6 +25,32 @@ class _ProfilePageState extends State<ProfilePage> {
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();
},
),
],
);
}
);
});
}
)

View File

@@ -14,6 +14,7 @@ class Trip with ChangeNotifier {
int totalTime;
LinkedList<Landmark> landmarks;
// could be empty as well
String? errorDescription;
Future<String> get cityName async {
List<double>? location = landmarks.firstOrNull?.location;
@@ -64,6 +65,11 @@ class Trip with ChangeNotifier {
landmarks.remove(landmark);
notifyListeners();
}
void updateError(String error) {
errorDescription = error;
notifyListeners();
}
factory Trip.fromPrefs(SharedPreferences prefs, String uuid) {
String? content = prefs.getString('trip_$uuid');

View File

@@ -11,9 +11,11 @@ import "package:anyway/structs/preferences.dart";
Dio dio = Dio(
BaseOptions(
baseUrl: API_URL_BASE,
// baseUrl: 'http://localhost:8000',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 120),
// also accept 500 errors, since we cannot rule out that the server is at fault. We still want to gracefully handle these errors
validateStatus: (status) => status! <= 500,
receiveDataWhenStatusError: true,
// api is notoriously slow
// headers: {
// HttpHeaders.userAgentHeader: 'dio',
@@ -45,24 +47,20 @@ fetchTrip(
// handle errors
if (response.statusCode != 200) {
trip.updateUUID("error");
throw Exception('Failed to load trip');
}
if (response.data["error"] != null) {
trip.updateUUID("error");
throw Exception(response.data["error"]);
if (response.data["detail"] != null) {
trip.updateError(response.data["detail"]);
// throw Exception(response.data["detail"]);
}
}
log(response.data.toString());
Map<String, dynamic> json = response.data;
// only fill in the trip "meta" data for now
trip.loadFromJson(json);
// now fill the trip with landmarks
// if (trip.landmarks.isNotEmpty) {
// trip.landmarks.clear();
// }
// we are going to recreate all the landmarks from the information given by the api
// we are going to recreate ALL the landmarks from the information given by the api
trip.landmarks.remove(trip.landmarks.first);
String? nextUUID = json["first_landmark_uuid"];
while (nextUUID != null) {
@@ -83,8 +81,8 @@ Future<(Landmark, String?)> fetchLandmark(String uuid) async {
if (response.statusCode != 200) {
throw Exception('Failed to load landmark');
}
if (response.data["error"] != null) {
throw Exception(response.data["error"]);
if (response.data["detail"] != null) {
throw Exception(response.data["detail"]);
}
log(response.data.toString());
Map<String, dynamic> json = response.data;