better errorhandling, slimmed down optimizer
This commit is contained in:
		@@ -29,9 +29,11 @@ def new_trip(preferences: Preferences, start: tuple[float, float], end: tuple[fl
 | 
				
			|||||||
    :return: the uuid of the first landmark in the optimized route
 | 
					    :return: the uuid of the first landmark in the optimized route
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    if preferences is None:
 | 
					    if preferences is None:
 | 
				
			||||||
        raise ValueError("Please provide preferences in the form of a 'Preference' BaseModel class.")
 | 
					        raise HTTPException(status_code=406, detail="Preferences not provided")
 | 
				
			||||||
 | 
					    if preferences.shopping.score == 0 and preferences.sightseeing.score == 0 and preferences.nature.score == 0:
 | 
				
			||||||
 | 
					        raise HTTPException(status_code=406, detail="All preferences are 0.")
 | 
				
			||||||
    if start is None:
 | 
					    if start is None:
 | 
				
			||||||
        raise ValueError("Please provide the starting coordinates as a tuple of floats.")
 | 
					        raise HTTPException(status_code=406, detail="Start coordinates not provided")
 | 
				
			||||||
    if end is None:
 | 
					    if end is None:
 | 
				
			||||||
        end = start
 | 
					        end = start
 | 
				
			||||||
        logger.info("No end coordinates provided. Using start=end.")
 | 
					        logger.info("No end coordinates provided. Using start=end.")
 | 
				
			||||||
@@ -50,7 +52,12 @@ def new_trip(preferences: Preferences, start: tuple[float, float], end: tuple[fl
 | 
				
			|||||||
    landmarks_short.append(end_landmark)
 | 
					    landmarks_short.append(end_landmark)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # First stage optimization
 | 
					    # First stage optimization
 | 
				
			||||||
    base_tour = optimizer.solve_optimization(preferences.max_time_minute, landmarks_short)
 | 
					    try:
 | 
				
			||||||
 | 
					        base_tour = optimizer.solve_optimization(preferences.max_time_minute, landmarks_short)
 | 
				
			||||||
 | 
					    except ArithmeticError:
 | 
				
			||||||
 | 
					        raise HTTPException(status_code=500, detail="No solution found")
 | 
				
			||||||
 | 
					    except TimeoutError:
 | 
				
			||||||
 | 
					        raise HTTPException(status_code=500, detail="Optimzation took too long")
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # Second stage optimization
 | 
					    # Second stage optimization
 | 
				
			||||||
    refined_tour = refiner.refine_optimization(landmarks, base_tour, preferences.max_time_minute, preferences.detour_tolerance_minute)
 | 
					    refined_tour = refiner.refine_optimization(landmarks, base_tour, preferences.max_time_minute, preferences.detour_tolerance_minute)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
city_bbox_side: 5000 #m
 | 
					city_bbox_side: 5000 #m
 | 
				
			||||||
radius_close_to: 50
 | 
					radius_close_to: 50
 | 
				
			||||||
church_coeff: 0.8
 | 
					church_coeff: 0.8
 | 
				
			||||||
park_coeff: 1.2
 | 
					park_coeff: 1.0
 | 
				
			||||||
tag_coeff: 10
 | 
					tag_coeff: 10
 | 
				
			||||||
N_important: 40
 | 
					N_important: 40
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,6 @@ class LandmarkManager:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    logger = logging.getLogger(__name__)
 | 
					    logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    city_bbox_side: int     # bbox side in meters
 | 
					 | 
				
			||||||
    radius_close_to: int    # radius in meters
 | 
					    radius_close_to: int    # radius in meters
 | 
				
			||||||
    church_coeff: float     # coeff to adjsut score of churches
 | 
					    church_coeff: float     # coeff to adjsut score of churches
 | 
				
			||||||
    park_coeff: float       # coeff to adjust score of parks
 | 
					    park_coeff: float       # coeff to adjust score of parks
 | 
				
			||||||
@@ -36,12 +35,17 @@ class LandmarkManager:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        with constants.LANDMARK_PARAMETERS_PATH.open('r') as f:
 | 
					        with constants.LANDMARK_PARAMETERS_PATH.open('r') as f:
 | 
				
			||||||
            parameters = yaml.safe_load(f)
 | 
					            parameters = yaml.safe_load(f)
 | 
				
			||||||
            self.city_bbox_side = parameters['city_bbox_side']
 | 
					            self.max_bbox_side = parameters['city_bbox_side']
 | 
				
			||||||
            self.radius_close_to = parameters['radius_close_to']
 | 
					            self.radius_close_to = parameters['radius_close_to']
 | 
				
			||||||
            self.church_coeff = parameters['church_coeff']
 | 
					            self.church_coeff = parameters['church_coeff']
 | 
				
			||||||
            self.park_coeff = parameters['park_coeff']
 | 
					            self.park_coeff = parameters['park_coeff']
 | 
				
			||||||
            self.tag_coeff = parameters['tag_coeff']
 | 
					            self.tag_coeff = parameters['tag_coeff']
 | 
				
			||||||
            self.N_important = parameters['N_important']
 | 
					            self.N_important = parameters['N_important']
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
 | 
				
			||||||
 | 
					            parameters = yaml.safe_load(f)
 | 
				
			||||||
 | 
					            self.walking_speed = parameters['average_walking_speed']
 | 
				
			||||||
 | 
					            self.detour_factor = parameters['detour_factor']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.overpass = Overpass()
 | 
					        self.overpass = Overpass()
 | 
				
			||||||
        CachingStrategy.use(JSON, cacheDir=constants.OSM_CACHE_DIR)
 | 
					        CachingStrategy.use(JSON, cacheDir=constants.OSM_CACHE_DIR)
 | 
				
			||||||
@@ -65,23 +69,26 @@ class LandmarkManager:
 | 
				
			|||||||
                - A list of the most important landmarks based on the user's preferences.
 | 
					                - A list of the most important landmarks based on the user's preferences.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        max_walk_dist = (preferences.max_time_minute/2)/60*self.walking_speed*1000/self.detour_factor
 | 
				
			||||||
 | 
					        reachable_bbox_side = min(max_walk_dist, self.max_bbox_side)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        L = []
 | 
					        L = []
 | 
				
			||||||
        bbox = self.create_bbox(center_coordinates)
 | 
					        bbox = self.create_bbox(center_coordinates, reachable_bbox_side)
 | 
				
			||||||
        # list for sightseeing
 | 
					        # list for sightseeing
 | 
				
			||||||
        if preferences.sightseeing.score != 0:
 | 
					        if preferences.sightseeing.score != 0:
 | 
				
			||||||
            score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.church_coeff)  
 | 
					            score_function = lambda loc, n_tags: int((((n_tags**1.2)*self.tag_coeff) )*self.church_coeff)   # self.count_elements_close_to(loc) +
 | 
				
			||||||
            L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], preferences.sightseeing.type, score_function)
 | 
					            L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], preferences.sightseeing.type, score_function)
 | 
				
			||||||
            L += L1
 | 
					            L += L1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # list for nature
 | 
					        # list for nature
 | 
				
			||||||
        if preferences.nature.score != 0:
 | 
					        if preferences.nature.score != 0:
 | 
				
			||||||
            score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.park_coeff)  
 | 
					            score_function = lambda loc, n_tags: int((((n_tags**1.2)*self.tag_coeff) )*self.park_coeff)   # self.count_elements_close_to(loc) +
 | 
				
			||||||
            L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], preferences.nature.type, score_function)
 | 
					            L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], preferences.nature.type, score_function)
 | 
				
			||||||
            L += L2
 | 
					            L += L2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # list for shopping
 | 
					        # list for shopping
 | 
				
			||||||
        if preferences.shopping.score != 0:
 | 
					        if preferences.shopping.score != 0:
 | 
				
			||||||
            score_function = lambda loc, n_tags: int(self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff))
 | 
					            score_function = lambda loc, n_tags: int(((n_tags**1.2)*self.tag_coeff)) # self.count_elements_close_to(loc) +
 | 
				
			||||||
            L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], preferences.shopping.type, score_function)
 | 
					            L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], preferences.shopping.type, score_function)
 | 
				
			||||||
            L += L3
 | 
					            L += L3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -183,12 +190,13 @@ class LandmarkManager:
 | 
				
			|||||||
            return 0
 | 
					            return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_bbox(self, coordinates: tuple[float, float]) -> tuple[float, float, float, float]:
 | 
					    def create_bbox(self, coordinates: tuple[float, float], reachable_bbox_side: int) -> tuple[float, float, float, float]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Create a bounding box around the given coordinates.
 | 
					        Create a bounding box around the given coordinates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Args:
 | 
					        Args:
 | 
				
			||||||
            coordinates (tuple[float, float]): The latitude and longitude of the center of the bounding box.
 | 
					            coordinates (tuple[float, float]): The latitude and longitude of the center of the bounding box.
 | 
				
			||||||
 | 
					            reachable_bbox_side (int): The side length of the bounding box in meters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude
 | 
					            tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude
 | 
				
			||||||
@@ -199,7 +207,7 @@ class LandmarkManager:
 | 
				
			|||||||
        lon = coordinates[1]
 | 
					        lon = coordinates[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Half the side length in km (since it's a square bbox)
 | 
					        # Half the side length in km (since it's a square bbox)
 | 
				
			||||||
        half_side_length_km = self.city_bbox_side / 2 / 1000
 | 
					        half_side_length_km = reachable_bbox_side / 2 / 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Convert distance to degrees
 | 
					        # Convert distance to degrees
 | 
				
			||||||
        lat_diff = half_side_length_km / 111  # 1 degree latitude is approximately 111 km
 | 
					        lat_diff = half_side_length_km / 111  # 1 degree latitude is approximately 111 km
 | 
				
			||||||
@@ -288,19 +296,24 @@ class LandmarkManager:
 | 
				
			|||||||
                        break
 | 
					                        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if "wikipedia" in tag:
 | 
					                    if "wikipedia" in tag:
 | 
				
			||||||
                        n_tags += 3             # wikipedia entries count more
 | 
					                        n_tags += 1             # wikipedia entries count more
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if tag == "wikidata":
 | 
					                    # if tag == "wikidata":
 | 
				
			||||||
                        Q = elem.tag('wikidata')
 | 
					                    #     Q = elem.tag('wikidata')
 | 
				
			||||||
                        site = Site("wikidata", "wikidata")
 | 
					                    #     site = Site("wikidata", "wikidata")
 | 
				
			||||||
                        item = ItemPage(site, Q)
 | 
					                    #     item = ItemPage(site, Q)
 | 
				
			||||||
                        item.get()
 | 
					                    #     item.get()
 | 
				
			||||||
                        n_languages = len(item.labels)
 | 
					                    #     n_languages = len(item.labels)
 | 
				
			||||||
                        n_tags += n_languages/10
 | 
					                    #     n_tags += n_languages/10
 | 
				
			||||||
 | 
					                    if "viewpoint" in tag:
 | 
				
			||||||
 | 
					                        n_tags += 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if elem_type != "nature":
 | 
					                    if elem_type != "nature":
 | 
				
			||||||
                        if "leisure" in tag and elem.tag('leisure') == "park":
 | 
					                        if "leisure" in tag and elem.tag('leisure') == "park":
 | 
				
			||||||
                            elem_type = "nature"
 | 
					                            elem_type = "nature"
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if elem_type == "nature":
 | 
				
			||||||
 | 
					                        n_tags += 1 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if landmarktype != "shopping":
 | 
					                    if landmarktype != "shopping":
 | 
				
			||||||
                        if "shop" in tag:
 | 
					                        if "shop" in tag:
 | 
				
			||||||
@@ -310,7 +323,6 @@ class LandmarkManager:
 | 
				
			|||||||
                        if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']:
 | 
					                        if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']:
 | 
				
			||||||
                            skip = True
 | 
					                            skip = True
 | 
				
			||||||
                            break
 | 
					                            break
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if skip:
 | 
					                if skip:
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
const String APP_NAME = 'AnyWay';
 | 
					const String APP_NAME = 'AnyWay';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const String API_URL_BASE = 'https://anyway.kluster.moll.re';
 | 
					String API_URL_BASE = 'https://anyway.kluster.moll.re';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import 'dart:developer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:anyway/structs/trip.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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,12 +17,15 @@ class Greeter extends StatefulWidget {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class _GreeterState extends State<Greeter> {
 | 
					class _GreeterState extends State<Greeter> {
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  Widget greeterBuilder (BuildContext context, Widget? child) {
 | 
					  Widget greeterBuilder (BuildContext context, Widget? child) {
 | 
				
			||||||
    ThemeData theme = Theme.of(context);
 | 
					    ThemeData theme = Theme.of(context);
 | 
				
			||||||
 | 
					    TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Widget topGreeter;
 | 
					    Widget topGreeter;
 | 
				
			||||||
    if (widget.trip.landmarks.length > 1) {
 | 
					
 | 
				
			||||||
 | 
					    if (widget.trip.uuid != 'pending') {
 | 
				
			||||||
      topGreeter = FutureBuilder(
 | 
					      topGreeter = FutureBuilder(
 | 
				
			||||||
        future: widget.trip.cityName,
 | 
					        future: widget.trip.cityName,
 | 
				
			||||||
        builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
 | 
					        builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
 | 
				
			||||||
@@ -28,17 +33,20 @@ class _GreeterState extends State<Greeter> {
 | 
				
			|||||||
            return AutoSizeText(
 | 
					            return AutoSizeText(
 | 
				
			||||||
              maxLines: 1,
 | 
					              maxLines: 1,
 | 
				
			||||||
              'Welcome to ${snapshot.data}!',
 | 
					              'Welcome to ${snapshot.data}!',
 | 
				
			||||||
              style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
 | 
					              style: greeterStyle
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          } else if (snapshot.hasError) {
 | 
					          } else if (snapshot.hasError) {
 | 
				
			||||||
            return const AutoSizeText(
 | 
					            log('Error while fetching city name');
 | 
				
			||||||
 | 
					            return AutoSizeText(
 | 
				
			||||||
              maxLines: 1,
 | 
					              maxLines: 1,
 | 
				
			||||||
              'Welcome to your trip!'
 | 
					              'Welcome to your trip!',
 | 
				
			||||||
 | 
					              style: greeterStyle
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            return const AutoSizeText(
 | 
					            return AutoSizeText(
 | 
				
			||||||
              maxLines: 1,
 | 
					              maxLines: 1,
 | 
				
			||||||
              'Welcome to ...'
 | 
					              'Welcome to ...',
 | 
				
			||||||
 | 
					              style: greeterStyle
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -54,14 +62,24 @@ class _GreeterState extends State<Greeter> {
 | 
				
			|||||||
            future: widget.trip.cityName,
 | 
					            future: widget.trip.cityName,
 | 
				
			||||||
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
 | 
					            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
 | 
				
			||||||
              if (snapshot.hasData) {
 | 
					              if (snapshot.hasData) {
 | 
				
			||||||
                return Text(
 | 
					                return AutoSizeText(
 | 
				
			||||||
 | 
					                  maxLines: 1,
 | 
				
			||||||
                  'Generating your trip to ${snapshot.data}...',
 | 
					                  'Generating your trip to ${snapshot.data}...',
 | 
				
			||||||
                  style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
 | 
					                  style: greeterStyle
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              } else if (snapshot.hasError) {
 | 
					              } 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(
 | 
					          Padding(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
import 'dart:collection';
 | 
					 | 
				
			||||||
import 'dart:developer';
 | 
					import 'dart:developer';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
					import 'package:shared_preferences/shared_preferences.dart';
 | 
				
			||||||
@@ -19,32 +18,19 @@ class LandmarksOverview extends StatefulWidget {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _LandmarksOverviewState extends State<LandmarksOverview> {
 | 
					class _LandmarksOverviewState extends State<LandmarksOverview> {
 | 
				
			||||||
  // final Future<List<Landmark>> _landmarks = fetchLandmarks();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return ListenableBuilder(//<LinkedList<Landmark>>
 | 
					    return ListenableBuilder(
 | 
				
			||||||
      listenable: widget.trip!,
 | 
					      listenable: widget.trip!,
 | 
				
			||||||
      builder: (BuildContext context, Widget? child) {
 | 
					      builder: (BuildContext context, Widget? child) {
 | 
				
			||||||
        Trip trip = widget.trip!;
 | 
					        Trip trip = widget.trip!;
 | 
				
			||||||
        log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
 | 
					        log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        List<Widget> children;
 | 
					        List<Widget> children;
 | 
				
			||||||
        if (trip.uuid == 'pending') {
 | 
					        
 | 
				
			||||||
          // the trip is still being fetched from the api
 | 
					        if (trip.uuid != 'pending' && trip.uuid != 'error') {
 | 
				
			||||||
          children = [Center(child: CircularProgressIndicator())];
 | 
					          log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
 | 
				
			||||||
        } 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.landmarks.length <= 1) {
 | 
					          if (trip.landmarks.length <= 1) {
 | 
				
			||||||
            children = [
 | 
					            children = [
 | 
				
			||||||
              const Text("No landmarks in this trip"),
 | 
					              const Text("No landmarks in this trip"),
 | 
				
			||||||
@@ -55,7 +41,26 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
 | 
				
			|||||||
              saveButton(),
 | 
					              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(
 | 
					        return Column(
 | 
				
			||||||
          children: children,
 | 
					          children: children,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@@ -119,11 +124,6 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Widget stepBetweenLandmarks(Landmark current, Landmark next) {
 | 
					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;
 | 
					  int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5;
 | 
				
			||||||
  // ~/ is integer division (rounding)
 | 
					  // ~/ is integer division (rounding)
 | 
				
			||||||
  return Container(
 | 
					  return Container(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anyway/constants.dart';
 | 
				
			||||||
import 'package:anyway/structs/preferences.dart';
 | 
					import 'package:anyway/structs/preferences.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,6 +25,32 @@ class _ProfilePageState extends State<ProfilePage> {
 | 
				
			|||||||
            onChanged: (bool? newValue) {
 | 
					            onChanged: (bool? newValue) {
 | 
				
			||||||
              setState(() {
 | 
					              setState(() {
 | 
				
			||||||
                debugMode = newValue!;
 | 
					                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();
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
              });
 | 
					              });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ class Trip with ChangeNotifier {
 | 
				
			|||||||
  int totalTime;
 | 
					  int totalTime;
 | 
				
			||||||
  LinkedList<Landmark> landmarks;
 | 
					  LinkedList<Landmark> landmarks;
 | 
				
			||||||
  // could be empty as well
 | 
					  // could be empty as well
 | 
				
			||||||
 | 
					  String? errorDescription;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<String> get cityName async {
 | 
					  Future<String> get cityName async {
 | 
				
			||||||
    List<double>? location = landmarks.firstOrNull?.location; 
 | 
					    List<double>? location = landmarks.firstOrNull?.location; 
 | 
				
			||||||
@@ -64,6 +65,11 @@ class Trip with ChangeNotifier {
 | 
				
			|||||||
    landmarks.remove(landmark);
 | 
					    landmarks.remove(landmark);
 | 
				
			||||||
    notifyListeners();
 | 
					    notifyListeners();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  void updateError(String error) {
 | 
				
			||||||
 | 
					    errorDescription = error;
 | 
				
			||||||
 | 
					    notifyListeners();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  factory Trip.fromPrefs(SharedPreferences prefs, String uuid) {
 | 
					  factory Trip.fromPrefs(SharedPreferences prefs, String uuid) {
 | 
				
			||||||
    String? content = prefs.getString('trip_$uuid');
 | 
					    String? content = prefs.getString('trip_$uuid');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,11 @@ import "package:anyway/structs/preferences.dart";
 | 
				
			|||||||
Dio dio = Dio(
 | 
					Dio dio = Dio(
 | 
				
			||||||
    BaseOptions(
 | 
					    BaseOptions(
 | 
				
			||||||
      baseUrl: API_URL_BASE,
 | 
					      baseUrl: API_URL_BASE,
 | 
				
			||||||
      // baseUrl: 'http://localhost:8000',
 | 
					 | 
				
			||||||
      connectTimeout: const Duration(seconds: 5),
 | 
					      connectTimeout: const Duration(seconds: 5),
 | 
				
			||||||
      receiveTimeout: const Duration(seconds: 120),
 | 
					      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
 | 
					      // api is notoriously slow
 | 
				
			||||||
      // headers: {
 | 
					      // headers: {
 | 
				
			||||||
      //   HttpHeaders.userAgentHeader: 'dio',
 | 
					      //   HttpHeaders.userAgentHeader: 'dio',
 | 
				
			||||||
@@ -45,24 +47,20 @@ fetchTrip(
 | 
				
			|||||||
  // handle errors
 | 
					  // handle errors
 | 
				
			||||||
  if (response.statusCode != 200) {
 | 
					  if (response.statusCode != 200) {
 | 
				
			||||||
    trip.updateUUID("error");
 | 
					    trip.updateUUID("error");
 | 
				
			||||||
    throw Exception('Failed to load trip');
 | 
					    if (response.data["detail"] != null) {
 | 
				
			||||||
  }
 | 
					      trip.updateError(response.data["detail"]);
 | 
				
			||||||
  if (response.data["error"] != null) {
 | 
					      // throw Exception(response.data["detail"]);
 | 
				
			||||||
    trip.updateUUID("error");
 | 
					    }
 | 
				
			||||||
    throw Exception(response.data["error"]);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  log(response.data.toString());
 | 
					  log(response.data.toString());
 | 
				
			||||||
  Map<String, dynamic> json = response.data;
 | 
					  Map<String, dynamic> json = response.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // only fill in the trip "meta" data for now
 | 
					  // only fill in the trip "meta" data for now
 | 
				
			||||||
  trip.loadFromJson(json);
 | 
					  trip.loadFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // now fill the trip with landmarks
 | 
					  // now fill the trip with landmarks
 | 
				
			||||||
  // if (trip.landmarks.isNotEmpty) {
 | 
					  // we are going to recreate ALL the landmarks from the information given by the api
 | 
				
			||||||
  //   trip.landmarks.clear();
 | 
					 | 
				
			||||||
  // }
 | 
					 | 
				
			||||||
  // we are going to recreate all the landmarks from the information given by the api
 | 
					 | 
				
			||||||
  trip.landmarks.remove(trip.landmarks.first);
 | 
					  trip.landmarks.remove(trip.landmarks.first);
 | 
				
			||||||
  String? nextUUID = json["first_landmark_uuid"];
 | 
					  String? nextUUID = json["first_landmark_uuid"];
 | 
				
			||||||
  while (nextUUID != null) {
 | 
					  while (nextUUID != null) {
 | 
				
			||||||
@@ -83,8 +81,8 @@ Future<(Landmark, String?)> fetchLandmark(String uuid) async {
 | 
				
			|||||||
  if (response.statusCode != 200) {
 | 
					  if (response.statusCode != 200) {
 | 
				
			||||||
    throw Exception('Failed to load landmark');
 | 
					    throw Exception('Failed to load landmark');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (response.data["error"] != null) {
 | 
					  if (response.data["detail"] != null) {
 | 
				
			||||||
    throw Exception(response.data["error"]);
 | 
					    throw Exception(response.data["detail"]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  log(response.data.toString());
 | 
					  log(response.data.toString());
 | 
				
			||||||
  Map<String, dynamic> json = response.data;
 | 
					  Map<String, dynamic> json = response.data;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user