Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
74b6550822 | |||
f76cd603f3 | |||
67f748244f | |||
66fa55e8c3 | |||
f4729a2de7 | |||
40edd923c3 | |||
6ad749eeed | |||
c20ebf3d63 | |||
6facde6e0b | |||
55b0a1b793 | |||
39df97f4d1 |
@@ -9,9 +9,9 @@ name = "pypi"
|
||||
numpy = "*"
|
||||
fastapi = "*"
|
||||
pydantic = "*"
|
||||
geopy = "*"
|
||||
shapely = "*"
|
||||
scipy = "*"
|
||||
osmpythontools = "*"
|
||||
pywikibot = "*"
|
||||
pymemcache = "*"
|
||||
fastapi-cli = "*"
|
||||
|
2208
backend/Pipfile.lock
generated
@@ -16,7 +16,7 @@ OSM_CACHE_DIR = Path(cache_dir_string)
|
||||
|
||||
import 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
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
@@ -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:
|
||||
leisure: park
|
||||
geological: ''
|
||||
@@ -11,7 +14,24 @@ nature:
|
||||
- alpine_hut
|
||||
- viewpoint
|
||||
- zoo
|
||||
waterway: waterfall
|
||||
- resort
|
||||
- picnic_site
|
||||
water:
|
||||
- pond
|
||||
- lake
|
||||
- river
|
||||
- basin
|
||||
- stream
|
||||
- lagoon
|
||||
- rapids
|
||||
waterway:
|
||||
- waterfall
|
||||
- river
|
||||
- canal
|
||||
- dam
|
||||
- dock
|
||||
- boatyard
|
||||
|
||||
|
||||
shopping:
|
||||
shop:
|
||||
@@ -23,10 +43,48 @@ sightseeing:
|
||||
- museum
|
||||
- attraction
|
||||
- gallery
|
||||
- artwork
|
||||
- aquarium
|
||||
|
||||
historic: ''
|
||||
amenity:
|
||||
- planetarium
|
||||
- place_of_worship
|
||||
- fountain
|
||||
- townhall
|
||||
water:
|
||||
- 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
|
||||
|
@@ -21,8 +21,8 @@ if constants.MEMCACHED_HOST_PATH is None:
|
||||
else:
|
||||
client = Client(
|
||||
constants.MEMCACHED_HOST_PATH,
|
||||
timeout=1,
|
||||
allow_unicode_keys=True,
|
||||
encoding='utf-8',
|
||||
serde=serde.pickle_serde
|
||||
timeout = 1,
|
||||
allow_unicode_keys = True,
|
||||
encoding = 'utf-8',
|
||||
serde = serde.pickle_serde
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ from uuid import uuid4
|
||||
|
||||
# Output to frontend
|
||||
class Landmark(BaseModel) :
|
||||
|
||||
|
||||
# Properties of the landmark
|
||||
name : str
|
||||
type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish']
|
||||
@@ -22,22 +22,22 @@ class Landmark(BaseModel) :
|
||||
|
||||
# Unique ID of a given landmark
|
||||
uuid: str = Field(default_factory=uuid4)
|
||||
|
||||
|
||||
# Additional properties depending on specific tour
|
||||
must_do : Optional[bool] = False
|
||||
must_avoid : Optional[bool] = False
|
||||
is_secondary : Optional[bool] = False # TODO future
|
||||
|
||||
|
||||
time_to_reach_next : Optional[int] = 0
|
||||
next_uuid : Optional[str] = None
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
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 ""
|
||||
type_str = '(' + self.type + ')'
|
||||
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}]'
|
||||
|
||||
|
||||
def distance(self, value: 'Landmark') -> float:
|
||||
return (self.location[0] - value.location[0])**2 + (self.location[1] - value.location[1])**2
|
||||
|
||||
|
@@ -22,7 +22,8 @@ class Trip(BaseModel):
|
||||
|
||||
# Store the trip in the cache
|
||||
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:
|
||||
# for landmark in landmarks:
|
||||
# cache_client.set(f"landmark_{landmark.uuid}", landmark, expire=3600)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import yaml
|
||||
from geopy.distance import geodesic
|
||||
from math import sin, cos, sqrt, atan2, radians
|
||||
|
||||
import constants
|
||||
|
||||
@@ -8,6 +8,7 @@ with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
|
||||
DETOUR_FACTOR = parameters['detour_factor']
|
||||
AVERAGE_WALKING_SPEED = parameters['average_walking_speed']
|
||||
|
||||
EARTH_RADIUS_KM = 6373
|
||||
|
||||
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
|
||||
else:
|
||||
dist = geodesic(p1, p2).kilometers
|
||||
else:
|
||||
# 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)
|
||||
walk_dist = dist*DETOUR_FACTOR
|
||||
dlon = lon2 - lon1
|
||||
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)
|
||||
walk_time = walk_dist/AVERAGE_WALKING_SPEED*60
|
||||
walk_time = walk_distance / AVERAGE_WALKING_SPEED * 60
|
||||
|
||||
return round(walk_time)
|
||||
|
@@ -10,7 +10,8 @@ from structs.landmark import Landmark
|
||||
from .take_most_important import take_most_important
|
||||
import constants
|
||||
|
||||
|
||||
# silence the overpass logger
|
||||
logging.getLogger('OSMPythonTools').setLevel(level=logging.CRITICAL)
|
||||
|
||||
|
||||
class LandmarkManager:
|
||||
@@ -206,11 +207,15 @@ class LandmarkManager:
|
||||
query = overpassQueryBuilder(
|
||||
bbox = bbox,
|
||||
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,
|
||||
# conditions = [],
|
||||
conditions = ['count_tags()>5'],
|
||||
includeCenter = True,
|
||||
out = 'body'
|
||||
)
|
||||
self.logger.debug(f"Query: {query}")
|
||||
|
||||
try:
|
||||
result = self.overpass.query(query)
|
||||
@@ -336,7 +341,7 @@ def dict_to_selector_list(d: dict) -> list:
|
||||
for key, value in d.items():
|
||||
if type(value) == list:
|
||||
val = '|'.join(value)
|
||||
return_list.append(f'{key}~"{val}"')
|
||||
return_list.append(f'{key}~"^({val})$"')
|
||||
elif type(value) == str and len(value) == 0:
|
||||
return_list.append(f'{key}')
|
||||
else:
|
||||
|
@@ -3,7 +3,6 @@ import numpy as np
|
||||
|
||||
from scipy.optimize import linprog
|
||||
from collections import defaultdict, deque
|
||||
from geopy.distance import geodesic
|
||||
|
||||
from structs.landmark import Landmark
|
||||
from .get_time_separation import get_time
|
||||
|
@@ -41,9 +41,8 @@ jobs:
|
||||
|
||||
- name: Load secrets from github
|
||||
run: |
|
||||
echo "${{ secrets.ANDROID_SECRET_PROPERTIES }}" > secrets.properties
|
||||
echo "${{ secrets.ANDROID_GOOGLE_PLAY_JSON }}" > fastlane/google-key.json
|
||||
# decode the base64 encoded google key
|
||||
echo "${{ secrets.ANDROID_SECRET_PROPERTIES_BASE64 }}" | base64 -d > secrets.properties
|
||||
echo "${{ secrets.ANDROID_GOOGLE_PLAY_JSON_BASE64 }}" | base64 -d > google-key.json
|
||||
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore
|
||||
working-directory: android
|
||||
|
||||
|
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 637 KiB |
Before Width: | Height: | Size: 573 KiB |
Before Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 360 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 637 KiB |
Before Width: | Height: | Size: 573 KiB |
Before Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 360 KiB |
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,111 +1,50 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:flutter/material.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;
|
||||
|
||||
Greeter({
|
||||
CurrentTripGreeter({
|
||||
super.key,
|
||||
required this.trip,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Greeter> createState() => _GreeterState();
|
||||
State<CurrentTripGreeter> createState() => _CurrentTripGreeterState();
|
||||
}
|
||||
|
||||
|
||||
class _GreeterState extends State<Greeter> {
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class _CurrentTripGreeterState extends State<CurrentTripGreeter> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.trip,
|
||||
builder: greeterBuilder,
|
||||
);
|
||||
}
|
||||
Widget build(BuildContext context) => Center(
|
||||
child: 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) {
|
||||
return AutoSizeText(
|
||||
maxLines: 1,
|
||||
'Welcome to your trip!',
|
||||
style: greeterStyle
|
||||
);
|
||||
} else {
|
||||
return AutoSizeText(
|
||||
maxLines: 1,
|
||||
'Welcome to ...',
|
||||
style: greeterStyle
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
}
|
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/modules/current_trip_error_message.dart';
|
||||
import 'package:anyway/modules/current_trip_loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
@@ -28,16 +30,36 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.trip,
|
||||
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(
|
||||
controller: widget.controller,
|
||||
padding: const EdgeInsets.only(bottom: 30, left: 5, right: 5),
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
children: [
|
||||
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),
|
||||
child: CurrentTripGreeter(trip: widget.trip),
|
||||
),
|
||||
|
||||
const Padding(padding: EdgeInsets.only(top: 10)),
|
||||
@@ -53,28 +75,6 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
|
||||
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_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 {
|
||||
@@ -35,7 +41,7 @@ class _TripPageState extends State<TripPage> {
|
||||
maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT,
|
||||
// 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
|
||||
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
|
||||
// panelSnapping: false
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
|
||||
|
@@ -38,10 +38,18 @@ fetchTrip(
|
||||
String dataString = jsonEncode(data);
|
||||
log(dataString);
|
||||
|
||||
final response = await dio.post(
|
||||
"/trip/new",
|
||||
data: data
|
||||
);
|
||||
late Response response;
|
||||
try {
|
||||
response = await dio.post(
|
||||
"/trip/new",
|
||||
data: data
|
||||
);
|
||||
} catch (e) {
|
||||
trip.updateUUID("error");
|
||||
trip.updateError(e.toString());
|
||||
log(e.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// handle errors
|
||||
if (response.statusCode != 200) {
|
||||
|