12 Commits

Author SHA1 Message Date
9850e949c3 Merge pull request 'remove unneeded screenshots' (#28) from frontend/fix/remove-screenshots into main
Some checks failed
Build and deploy the backend to production / Build and push image (push) Has been cancelled
Build and deploy the backend to production / Deploy to production (push) Has been cancelled
/ push-to-remote (push) Successful in 13s
Reviewed-on: #28
2024-10-22 09:54:44 +00:00
5fc25a3c39 remove unneeded screenshots
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
2024-10-22 11:52:59 +02:00
f76cd603f3 Merge pull request 'Better landmark finding' (#27) from fix/backend/moore-tags into main
All checks were successful
Build and deploy the backend to production / Build and push image (push) Successful in 1m38s
/ push-to-remote (push) Successful in 13s
Build and deploy the backend to production / Deploy to production (push) Successful in 14s
Reviewed-on: #27
2024-10-22 09:20:53 +00:00
67f748244f update deployment config
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
Build and deploy the backend to staging / Deploy to staging (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Has been cancelled
2024-10-15 10:30:20 +02:00
66fa55e8c3 remove boring bridges
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m40s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 14s
2024-10-14 19:10:31 +00:00
f4729a2de7 fix pipfile mismatch
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m8s
Build and release APK / Build APK (pull_request) Failing after 5m41s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 14s
2024-10-09 23:41:58 +02:00
40edd923c3 remove geopy dependency
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Failing after 53s
Build and deploy the backend to staging / Deploy to staging (pull_request) Has been skipped
Build and release APK / Build APK (pull_request) Failing after 6m44s
2024-10-09 16:12:41 +02:00
6ad749eeed fix? bug where landmarks are returned as none 2024-10-09 16:10:40 +02:00
c20ebf3d63 add more tags and filter more restrictively
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m57s
Build and release APK / Build APK (pull_request) Failing after 5m40s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 19s
2024-10-01 16:10:52 +02:00
6facde6e0b Merge pull request 'UI and UX fixes to make new trips (and the errors it might have less confusing)' (#26) from fix/frontend/greeter-ui into main
Some checks failed
Build and deploy the backend to production / Deploy to production (push) Has been cancelled
Build and deploy the backend to production / Build and push image (push) Has been cancelled
/ push-to-remote (push) Successful in 16s
Reviewed-on: #26
2024-09-30 16:09:10 +00:00
55b0a1b793 make ui at least usable throughout
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
2024-09-30 18:07:27 +02:00
39df97f4d1 fix multiline secrets by using base64 2024-09-30 18:07:17 +02:00
30 changed files with 1379 additions and 1266 deletions

View File

@@ -9,9 +9,9 @@ name = "pypi"
numpy = "*" numpy = "*"
fastapi = "*" fastapi = "*"
pydantic = "*" pydantic = "*"
geopy = "*"
shapely = "*" shapely = "*"
scipy = "*" scipy = "*"
osmpythontools = "*" osmpythontools = "*"
pywikibot = "*" pywikibot = "*"
pymemcache = "*" pymemcache = "*"
fastapi-cli = "*"

2208
backend/Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ OSM_CACHE_DIR = Path(cache_dir_string)
import logging import logging
# if we are in a debug session, set verbose and rich logging # if we are in a debug session, set verbose and rich logging
if os.getenv('DEBUG', False): if os.getenv('DEBUG', "false") == "true":
from rich.logging import RichHandler from rich.logging import RichHandler
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG, level=logging.DEBUG,

View File

@@ -1,3 +1,6 @@
# Tags were picked mostly arbitrarily, based on the OSM wiki and the OSM tags page.
# See https://taginfo.openstreetmap.org for more inspiration.
nature: nature:
leisure: park leisure: park
geological: '' geological: ''
@@ -11,7 +14,24 @@ nature:
- alpine_hut - alpine_hut
- viewpoint - viewpoint
- zoo - zoo
waterway: waterfall - resort
- picnic_site
water:
- pond
- lake
- river
- basin
- stream
- lagoon
- rapids
waterway:
- waterfall
- river
- canal
- dam
- dock
- boatyard
shopping: shopping:
shop: shop:
@@ -23,10 +43,48 @@ sightseeing:
- museum - museum
- attraction - attraction
- gallery - gallery
- artwork
- aquarium
historic: '' historic: ''
amenity: amenity:
- planetarium - planetarium
- place_of_worship - place_of_worship
- fountain - fountain
- townhall
water: water:
- reflecting_pool - reflecting_pool
bridge:
- aqueduct
- viaduct
- boardwalk
- cantilever
- abandoned
building:
- church
- chapel
- mosque
- synagogue
- ruins
- temple
- government
- cathedral
- castle
- museum
# to be used later on
restauration:
shop:
- coffee
- bakery
- restaurant
- pastry
amenity:
- restaurant
- cafe
- ice_cream
- food_court
- biergarten

View File

@@ -21,8 +21,8 @@ if constants.MEMCACHED_HOST_PATH is None:
else: else:
client = Client( client = Client(
constants.MEMCACHED_HOST_PATH, constants.MEMCACHED_HOST_PATH,
timeout=1, timeout = 1,
allow_unicode_keys=True, allow_unicode_keys = True,
encoding='utf-8', encoding = 'utf-8',
serde=serde.pickle_serde serde = serde.pickle_serde
) )

View File

@@ -5,7 +5,7 @@ from uuid import uuid4
# Output to frontend # Output to frontend
class Landmark(BaseModel) : class Landmark(BaseModel) :
# Properties of the landmark # Properties of the landmark
name : str name : str
type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish'] type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish']
@@ -22,22 +22,22 @@ class Landmark(BaseModel) :
# Unique ID of a given landmark # Unique ID of a given landmark
uuid: str = Field(default_factory=uuid4) uuid: str = Field(default_factory=uuid4)
# Additional properties depending on specific tour # Additional properties depending on specific tour
must_do : Optional[bool] = False must_do : Optional[bool] = False
must_avoid : Optional[bool] = False must_avoid : Optional[bool] = False
is_secondary : Optional[bool] = False # TODO future is_secondary : Optional[bool] = False # TODO future
time_to_reach_next : Optional[int] = 0 time_to_reach_next : Optional[int] = 0
next_uuid : Optional[str] = None next_uuid : Optional[str] = None
def __str__(self) -> str: def __str__(self) -> str:
time_to_next_str = f", time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else "" time_to_next_str = f", time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else ""
is_secondary_str = f", secondary" if self.is_secondary else "" is_secondary_str = f", secondary" if self.is_secondary else ""
type_str = '(' + self.type + ')' type_str = '(' + self.type + ')'
if self.type in ["start", "finish", "nature", "shopping"] : type_str += '\t ' if self.type in ["start", "finish", "nature", "shopping"] : type_str += '\t '
return f'Landmark{type_str}: [{self.name} @{self.location}, score={self.attractiveness}{time_to_next_str}{is_secondary_str}]' return f'Landmark{type_str}: [{self.name} @{self.location}, score={self.attractiveness}{time_to_next_str}{is_secondary_str}]'
def distance(self, value: 'Landmark') -> float: def distance(self, value: 'Landmark') -> float:
return (self.location[0] - value.location[0])**2 + (self.location[1] - value.location[1])**2 return (self.location[0] - value.location[0])**2 + (self.location[1] - value.location[1])**2

View File

@@ -22,7 +22,8 @@ class Trip(BaseModel):
# Store the trip in the cache # Store the trip in the cache
cache_client.set(f"trip_{trip.uuid}", trip) cache_client.set(f"trip_{trip.uuid}", trip)
cache_client.set_many({f"landmark_{landmark.uuid}": landmark for landmark in landmarks}, expire=3600) # make sure to await the result (noreply=False). Otherwise the cache might not be inplace when the trip is actually requested
cache_client.set_many({f"landmark_{landmark.uuid}": landmark for landmark in landmarks}, expire=3600, noreply=False)
# is equivalent to: # is equivalent to:
# for landmark in landmarks: # for landmark in landmarks:
# cache_client.set(f"landmark_{landmark.uuid}", landmark, expire=3600) # cache_client.set(f"landmark_{landmark.uuid}", landmark, expire=3600)

View File

@@ -1,5 +1,5 @@
import yaml import yaml
from geopy.distance import geodesic from math import sin, cos, sqrt, atan2, radians
import constants import constants
@@ -8,6 +8,7 @@ with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
DETOUR_FACTOR = parameters['detour_factor'] DETOUR_FACTOR = parameters['detour_factor']
AVERAGE_WALKING_SPEED = parameters['average_walking_speed'] AVERAGE_WALKING_SPEED = parameters['average_walking_speed']
EARTH_RADIUS_KM = 6373
def get_time(p1: tuple[float, float], p2: tuple[float, float]) -> int: def get_time(p1: tuple[float, float], p2: tuple[float, float]) -> int:
""" """
@@ -22,16 +23,28 @@ def get_time(p1: tuple[float, float], p2: tuple[float, float]) -> int:
""" """
# Compute the straight-line distance in km if p1 == p2:
if p1 == p2 :
return 0 return 0
else: else:
dist = geodesic(p1, p2).kilometers # Compute the distance in km along the surface of the Earth
# (assume spherical Earth)
# this is the haversine formula, stolen from stackoverflow
# in order to not use any external libraries
lat1, lon1 = radians(p1[0]), radians(p1[1])
lat2, lon2 = radians(p2[0]), radians(p2[1])
# Consider the detour factor for average cityto deterline walking distance (in km) dlon = lon2 - lon1
walk_dist = dist*DETOUR_FACTOR dlat = lat2 - lat1
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance = EARTH_RADIUS_KM * c
# Consider the detour factor for average an average city
walk_distance = distance * DETOUR_FACTOR
# Time to walk this distance (in minutes) # Time to walk this distance (in minutes)
walk_time = walk_dist/AVERAGE_WALKING_SPEED*60 walk_time = walk_distance / AVERAGE_WALKING_SPEED * 60
return round(walk_time) return round(walk_time)

View File

@@ -10,7 +10,8 @@ from structs.landmark import Landmark
from .take_most_important import take_most_important from .take_most_important import take_most_important
import constants import constants
# silence the overpass logger
logging.getLogger('OSMPythonTools').setLevel(level=logging.CRITICAL)
class LandmarkManager: class LandmarkManager:
@@ -206,11 +207,15 @@ class LandmarkManager:
query = overpassQueryBuilder( query = overpassQueryBuilder(
bbox = bbox, bbox = bbox,
elementType = ['way', 'relation'], elementType = ['way', 'relation'],
# selector can in principle be a list already,
# but it generates the intersection of the queries
# we want the union
selector = sel, selector = sel,
# conditions = [], conditions = ['count_tags()>5'],
includeCenter = True, includeCenter = True,
out = 'body' out = 'body'
) )
self.logger.debug(f"Query: {query}")
try: try:
result = self.overpass.query(query) result = self.overpass.query(query)
@@ -336,7 +341,7 @@ def dict_to_selector_list(d: dict) -> list:
for key, value in d.items(): for key, value in d.items():
if type(value) == list: if type(value) == list:
val = '|'.join(value) val = '|'.join(value)
return_list.append(f'{key}~"{val}"') return_list.append(f'{key}~"^({val})$"')
elif type(value) == str and len(value) == 0: elif type(value) == str and len(value) == 0:
return_list.append(f'{key}') return_list.append(f'{key}')
else: else:

View File

@@ -3,7 +3,6 @@ import numpy as np
from scipy.optimize import linprog from scipy.optimize import linprog
from collections import defaultdict, deque from collections import defaultdict, deque
from geopy.distance import geodesic
from structs.landmark import Landmark from structs.landmark import Landmark
from .get_time_separation import get_time from .get_time_separation import get_time

View File

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

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

View File

@@ -1,111 +1,50 @@
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> {
Widget greeterBuilder (BuildContext context, Widget? child) {
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
);
Widget topGreeter;
if (widget.trip.uuid != 'pending') {
topGreeter = FutureBuilder(
future: widget.trip.cityName,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return AutoSizeText(
maxLines: 1,
'Welcome to ${snapshot.data}!',
style: greeterStyle
);
} else if (snapshot.hasError) {
log('Error while fetching city name');
return AutoSizeText(
maxLines: 1,
'Welcome to your trip!',
style: greeterStyle
);
} else {
return AutoSizeText(
maxLines: 1,
'Welcome to ...',
style: greeterStyle
);
}
}
);
} 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Center(
return ListenableBuilder( child: FutureBuilder(
listenable: widget.trip, future: widget.trip.cityName,
builder: greeterBuilder, builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
); if (snapshot.hasData) {
} return AutoSizeText(
maxLines: 1,
'Welcome to ${snapshot.data}!',
style: greeterStyle
);
} else if (snapshot.hasError) {
return AutoSizeText(
maxLines: 1,
'Welcome to your trip!',
style: greeterStyle
);
} else {
return AutoSizeText(
maxLines: 1,
'Welcome to ...',
style: greeterStyle
);
}
}
)
);
} }

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

View File

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

View File

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

View File

@@ -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;
"/trip/new", try {
data: data response = await dio.post(
); "/trip/new",
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) {