Compare commits
	
		
			15 Commits
		
	
	
		
			renovate/c
			...
			e70b455541
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e70b455541 | |||
| ad72d104a0 | |||
| acc7631c55 | |||
| bea3a65fec | |||
| f71b9b19a6 | |||
| 89511f39cb | |||
| 71d9554d97 | |||
| c87a01b2e8 | |||
| 5748630b99 | |||
| 016622c7af | |||
| bf129b201d | |||
| 86bcec6b29 | |||
| 07dde5ab58 | |||
| db82495f11 | |||
| 889b6c2096 | 
							
								
								
									
										32
									
								
								.gitea/workflows/backend_run-lint.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.gitea/workflows/backend_run-lint.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
    paths:
 | 
			
		||||
      - backend/**
 | 
			
		||||
 | 
			
		||||
name: Run linting on the backend code
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    name: Build
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
 | 
			
		||||
    - uses: https://gitea.com/actions/checkout@v4
 | 
			
		||||
 | 
			
		||||
    - name: Install dependencies
 | 
			
		||||
      run: |
 | 
			
		||||
        apt-get update && apt-get install -y python3 python3-pip
 | 
			
		||||
        pip install pipenv
 | 
			
		||||
 | 
			
		||||
    - name: Install packages
 | 
			
		||||
      run: |
 | 
			
		||||
        ls -la
 | 
			
		||||
        # only install dev-packages
 | 
			
		||||
        pipenv install --categories=dev-packages
 | 
			
		||||
      working-directory: backend
 | 
			
		||||
 | 
			
		||||
    - name: Run linter
 | 
			
		||||
      run: pipenv run pylint src
 | 
			
		||||
      working-directory: backend
 | 
			
		||||
							
								
								
									
										32
									
								
								.gitea/workflows/backend_run-test.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.gitea/workflows/backend_run-test.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
    paths:
 | 
			
		||||
      - backend/**
 | 
			
		||||
 | 
			
		||||
name: Run testing on the backend code
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    name: Build
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
 | 
			
		||||
    - uses: https://gitea.com/actions/checkout@v4
 | 
			
		||||
 | 
			
		||||
    - name: Install dependencies
 | 
			
		||||
      run: |
 | 
			
		||||
        apt-get update && apt-get install -y python3 python3-pip
 | 
			
		||||
        pip install pipenv
 | 
			
		||||
 | 
			
		||||
    - name: Install packages
 | 
			
		||||
      run: |
 | 
			
		||||
        ls -la
 | 
			
		||||
        # install all packages, including dev-packages
 | 
			
		||||
        pipenv install --dev
 | 
			
		||||
      working-directory: backend
 | 
			
		||||
 | 
			
		||||
    - name: Run Tests
 | 
			
		||||
      run: pipenv run pytest src
 | 
			
		||||
      working-directory: backend
 | 
			
		||||
@@ -43,8 +43,10 @@ jobs:
 | 
			
		||||
      working-directory: ./frontend
 | 
			
		||||
 | 
			
		||||
    - name: Add required secrets
 | 
			
		||||
      env:
 | 
			
		||||
        ANDROID_SECRETS_PROPERTIES: ${{ secrets.ANDROID_SECRETS_PROPERTIES }}
 | 
			
		||||
      run: |
 | 
			
		||||
        echo ${{ secrets.ANDROID_SECRETS_PROPERTIES }} > ./android/secrets.properties
 | 
			
		||||
        echo "$ANDROID_SECRETS_PROPERTIES" >> ./android/secrets.properties
 | 
			
		||||
      working-directory: ./frontend
 | 
			
		||||
 | 
			
		||||
    - name: Sanity check
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -14,9 +14,9 @@
 | 
			
		||||
                "DEBUG": "true"
 | 
			
		||||
            },
 | 
			
		||||
            "args": [
 | 
			
		||||
                "--app-dir",
 | 
			
		||||
                "src",
 | 
			
		||||
                "main:app",
 | 
			
		||||
                // "--app-dir",
 | 
			
		||||
                // "src",
 | 
			
		||||
                "src.main:app",
 | 
			
		||||
                "--reload",
 | 
			
		||||
            ],
 | 
			
		||||
            "jinja": true,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,10 @@
 | 
			
		||||
# osm-cache
 | 
			
		||||
# osm-cache and wikidata cache
 | 
			
		||||
cache/
 | 
			
		||||
apicache/
 | 
			
		||||
 | 
			
		||||
# wikidata throttle
 | 
			
		||||
*.ctrl
 | 
			
		||||
 | 
			
		||||
# Byte-compiled / optimized / DLL files
 | 
			
		||||
__pycache__/
 | 
			
		||||
*.py[cod]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								backend/.pylintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								backend/.pylintrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
[MAIN]
 | 
			
		||||
max-line-length=240
 | 
			
		||||
@@ -13,5 +13,6 @@ EXPOSE 8000
 | 
			
		||||
# Set environment variables used by the deployment. These can be overridden by the user using this image.
 | 
			
		||||
ENV NUM_WORKERS=1
 | 
			
		||||
ENV OSM_CACHE_DIR=/cache
 | 
			
		||||
ENV MEMCACHED_HOST_PATH=none
 | 
			
		||||
 | 
			
		||||
CMD fastapi run src/main.py --port 8000 --workers $NUM_WORKERS
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ verify_ssl = true
 | 
			
		||||
name = "pypi"
 | 
			
		||||
 | 
			
		||||
[dev-packages]
 | 
			
		||||
pylint = "*"
 | 
			
		||||
pytest = "*"
 | 
			
		||||
 | 
			
		||||
[packages]
 | 
			
		||||
numpy = "*"
 | 
			
		||||
@@ -14,3 +16,4 @@ shapely = "*"
 | 
			
		||||
scipy = "*"
 | 
			
		||||
osmpythontools = "*"
 | 
			
		||||
pywikibot = "*"
 | 
			
		||||
pymemcache = "*"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										952
									
								
								backend/Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										952
									
								
								backend/Pipfile.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										0
									
								
								backend/src/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								backend/src/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -15,13 +15,21 @@ OSM_CACHE_DIR = Path(cache_dir_string)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import yaml
 | 
			
		||||
 | 
			
		||||
LOGGING_CONFIG = LOCATION_PREFIX / 'log_config.yaml'
 | 
			
		||||
config = yaml.safe_load(LOGGING_CONFIG.read_text())
 | 
			
		||||
 | 
			
		||||
logging.config.dictConfig(config)
 | 
			
		||||
 | 
			
		||||
# if we are in a debug session, set the log level to debug
 | 
			
		||||
# if we are in a debug session, set verbose and rich logging
 | 
			
		||||
if os.getenv('DEBUG', False):
 | 
			
		||||
    logging.getLogger().setLevel(logging.DEBUG)
 | 
			
		||||
    from rich.logging import RichHandler
 | 
			
		||||
    logging.basicConfig(
 | 
			
		||||
        level=logging.DEBUG,
 | 
			
		||||
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
 | 
			
		||||
        handlers=[RichHandler()]
 | 
			
		||||
    )
 | 
			
		||||
else:
 | 
			
		||||
    logging.basicConfig(
 | 
			
		||||
        level=logging.INFO,
 | 
			
		||||
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MEMCACHED_HOST_PATH = os.getenv('MEMCACHED_HOST_PATH', None)
 | 
			
		||||
if MEMCACHED_HOST_PATH == "none":
 | 
			
		||||
    MEMCACHED_HOST_PATH = None
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
version: 1
 | 
			
		||||
disable_existing_loggers: False
 | 
			
		||||
formatters:
 | 
			
		||||
  simple:
 | 
			
		||||
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 | 
			
		||||
handlers:
 | 
			
		||||
  console:
 | 
			
		||||
    class: rich.logging.RichHandler
 | 
			
		||||
    formatter: simple
 | 
			
		||||
  # access:
 | 
			
		||||
  #   class: logging.FileHandler
 | 
			
		||||
  #   filename: logs/access.log
 | 
			
		||||
  #   level: INFO
 | 
			
		||||
  #   formatter: simple
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
loggers:
 | 
			
		||||
  uvicorn.error:
 | 
			
		||||
    level: INFO
 | 
			
		||||
    handlers:
 | 
			
		||||
      - console
 | 
			
		||||
    propagate: no
 | 
			
		||||
  # uvicorn.access:
 | 
			
		||||
  #   level: INFO
 | 
			
		||||
  #   handlers:
 | 
			
		||||
  #     - access
 | 
			
		||||
  #   propagate: no
 | 
			
		||||
root:
 | 
			
		||||
  level: INFO
 | 
			
		||||
  handlers:
 | 
			
		||||
    - console
 | 
			
		||||
  propagate: yes
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
import logging
 | 
			
		||||
from fastapi import FastAPI, Query, Body
 | 
			
		||||
from fastapi import FastAPI, Query, Body, HTTPException
 | 
			
		||||
 | 
			
		||||
from structs.landmark import Landmark
 | 
			
		||||
from structs.preferences import Preferences
 | 
			
		||||
from structs.linked_landmarks import LinkedLandmarks
 | 
			
		||||
from utils.landmarks_manager import LandmarkManager
 | 
			
		||||
from utils.optimizer import Optimizer
 | 
			
		||||
from utils.refiner import Refiner
 | 
			
		||||
from .structs.landmark import Landmark
 | 
			
		||||
from .structs.preferences import Preferences
 | 
			
		||||
from .structs.linked_landmarks import LinkedLandmarks
 | 
			
		||||
from .structs.trip import Trip
 | 
			
		||||
from .utils.landmarks_manager import LandmarkManager
 | 
			
		||||
from .utils.optimizer import Optimizer
 | 
			
		||||
from .utils.refiner import Refiner
 | 
			
		||||
from .persistence import client as cache_client
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
@@ -17,8 +19,8 @@ optimizer = Optimizer()
 | 
			
		||||
refiner = Refiner(optimizer=optimizer)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.post("/route/new")
 | 
			
		||||
def get_route(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] | None = None) -> str:
 | 
			
		||||
@app.post("/trip/new")
 | 
			
		||||
def new_trip(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] | None = None) -> Trip:
 | 
			
		||||
    '''
 | 
			
		||||
    Main function to call the optimizer.
 | 
			
		||||
    :param preferences: the preferences specified by the user as the post body
 | 
			
		||||
@@ -27,15 +29,17 @@ def get_route(preferences: Preferences, start: tuple[float, float], end: tuple[f
 | 
			
		||||
    :return: the uuid of the first landmark in the optimized route
 | 
			
		||||
    '''
 | 
			
		||||
    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:
 | 
			
		||||
        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:
 | 
			
		||||
        end = start
 | 
			
		||||
        logger.info("No end coordinates provided. Using start=end.")
 | 
			
		||||
 | 
			
		||||
    start_landmark = Landmark(name='start', type='start', location=(start[0], start[1]), osm_type='start', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
 | 
			
		||||
    end_landmark = Landmark(name='end', type='finish', location=(end[0], end[1]), osm_type='end', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
 | 
			
		||||
    end_landmark = Landmark(name='finish', type='finish', location=(end[0], end[1]), osm_type='end', osm_id=0, attractiveness=0, must_do=True, n_tags = 0)
 | 
			
		||||
 | 
			
		||||
    # Generate the landmarks from the start location
 | 
			
		||||
    landmarks, landmarks_short = manager.generate_landmarks_list(
 | 
			
		||||
@@ -47,22 +51,37 @@ def get_route(preferences: Preferences, start: tuple[float, float], end: tuple[f
 | 
			
		||||
    landmarks_short.insert(0, start_landmark)
 | 
			
		||||
    landmarks_short.append(end_landmark)
 | 
			
		||||
    
 | 
			
		||||
    # TODO infer these parameters from the preferences
 | 
			
		||||
    max_walking_time = 4    # hours
 | 
			
		||||
    detour = 30             # minutes
 | 
			
		||||
 | 
			
		||||
    # First stage optimization
 | 
			
		||||
    base_tour = optimizer.solve_optimization(max_walking_time*60, 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
 | 
			
		||||
    refined_tour = refiner.refine_optimization(landmarks, base_tour, max_walking_time*60, detour)
 | 
			
		||||
    refined_tour = refiner.refine_optimization(landmarks, base_tour, preferences.max_time_minute, preferences.detour_tolerance_minute)
 | 
			
		||||
 | 
			
		||||
    linked_tour = LinkedLandmarks(refined_tour)
 | 
			
		||||
    return linked_tour[0].uuid
 | 
			
		||||
    # upon creation of the trip, persistence of both the trip and its landmarks is ensured. Ca
 | 
			
		||||
    trip = Trip.from_linked_landmarks(linked_tour, cache_client)
 | 
			
		||||
    return trip
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### For already existing trips/landmarks
 | 
			
		||||
@app.get("/trip/{trip_uuid}")
 | 
			
		||||
def get_trip(trip_uuid: str) -> Trip:
 | 
			
		||||
    try:
 | 
			
		||||
        trip = cache_client.get(f"trip_{trip_uuid}")
 | 
			
		||||
        return trip
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Trip not found")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/landmark/{landmark_uuid}")
 | 
			
		||||
def get_landmark(landmark_uuid: str) -> Landmark:
 | 
			
		||||
    #cherche dans linked_tour et retourne le landmark correspondant
 | 
			
		||||
    pass
 | 
			
		||||
    try:
 | 
			
		||||
        landmark = cache_client.get(f"landmark_{landmark_uuid}")
 | 
			
		||||
        return landmark
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Landmark not found")
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
city_bbox_side: 5000 #m
 | 
			
		||||
radius_close_to: 50
 | 
			
		||||
church_coeff: 0.8
 | 
			
		||||
park_coeff: 1.2
 | 
			
		||||
park_coeff: 1.0
 | 
			
		||||
tag_coeff: 10
 | 
			
		||||
N_important: 40
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								backend/src/persistence.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								backend/src/persistence.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
from pymemcache.client.base import Client
 | 
			
		||||
 | 
			
		||||
from .constants import MEMCACHED_HOST_PATH
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DummyClient:
 | 
			
		||||
    _data = {}
 | 
			
		||||
    def set(self, key, value, **kwargs):
 | 
			
		||||
        self._data[key] = value
 | 
			
		||||
 | 
			
		||||
    def set_many(self, data, **kwargs):
 | 
			
		||||
        self._data.update(data)
 | 
			
		||||
 | 
			
		||||
    def get(self, key, **kwargs):
 | 
			
		||||
        return self._data[key]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if MEMCACHED_HOST_PATH is None:
 | 
			
		||||
    client = DummyClient()
 | 
			
		||||
else:
 | 
			
		||||
    client = Client(
 | 
			
		||||
        MEMCACHED_HOST_PATH,
 | 
			
		||||
        timeout=1,
 | 
			
		||||
        allow_unicode_keys=True,
 | 
			
		||||
        encoding='utf-8'
 | 
			
		||||
    )
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import uuid
 | 
			
		||||
from .landmark import Landmark
 | 
			
		||||
from utils.get_time_separation import get_time
 | 
			
		||||
from ..utils.get_time_separation import get_time
 | 
			
		||||
 | 
			
		||||
class LinkedLandmarks:
 | 
			
		||||
    """
 | 
			
		||||
@@ -9,8 +8,7 @@ class LinkedLandmarks:
 | 
			
		||||
    """
 | 
			
		||||
    
 | 
			
		||||
    _landmarks = list[Landmark]
 | 
			
		||||
    total_time = int
 | 
			
		||||
    uuid = str
 | 
			
		||||
    total_time: int = 0
 | 
			
		||||
 | 
			
		||||
    def __init__(self, data: list[Landmark] = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -19,7 +17,6 @@ class LinkedLandmarks:
 | 
			
		||||
        Args:
 | 
			
		||||
            data (list[Landmark], optional): The list of landmarks that are linked together. Defaults to None.
 | 
			
		||||
        """
 | 
			
		||||
        self.uuid = uuid.uuid4()
 | 
			
		||||
        self._landmarks = data if data else []
 | 
			
		||||
        self._link_landmarks()
 | 
			
		||||
 | 
			
		||||
@@ -28,7 +25,6 @@ class LinkedLandmarks:
 | 
			
		||||
        """
 | 
			
		||||
        Create the links between the landmarks in the list by setting their .next_uuid and the .time_to_next attributes.
 | 
			
		||||
        """
 | 
			
		||||
        self.total_time = 0
 | 
			
		||||
        for i, landmark in enumerate(self._landmarks[:-1]):
 | 
			
		||||
            landmark.next_uuid = self._landmarks[i + 1].uuid
 | 
			
		||||
            time_to_next = get_time(landmark.location, self._landmarks[i + 1].location)
 | 
			
		||||
@@ -44,18 +40,4 @@ class LinkedLandmarks:
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def __str__(self) -> str:
 | 
			
		||||
        return f"LinkedLandmarks, total time: {self.total_time} minutes, {len(self._landmarks)} stops: [{','.join([str(landmark) for landmark in self._landmarks])}]"
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def asdict(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Convert the linked landmarks to a json serializable dictionary.
 | 
			
		||||
        
 | 
			
		||||
        Returns:
 | 
			
		||||
            dict: A dictionary representation of the linked landmarks.
 | 
			
		||||
        """
 | 
			
		||||
        return {
 | 
			
		||||
            'uuid': self.uuid,
 | 
			
		||||
            'total_time': self.total_time,
 | 
			
		||||
            'landmarks': [landmark.dict() for landmark in self._landmarks]
 | 
			
		||||
        }
 | 
			
		||||
        return f"LinkedLandmarks [{' ->'.join([str(landmark) for landmark in self._landmarks])}]"
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ from pydantic import BaseModel
 | 
			
		||||
from typing import Optional, Literal
 | 
			
		||||
 | 
			
		||||
class Preference(BaseModel) :
 | 
			
		||||
    name: str
 | 
			
		||||
    type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish']
 | 
			
		||||
    score: int          # score could be from 1 to 5
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								backend/src/structs/trip.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								backend/src/structs/trip.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
from pydantic import BaseModel, Field
 | 
			
		||||
from pymemcache.client.base import Client
 | 
			
		||||
 | 
			
		||||
from .linked_landmarks import LinkedLandmarks
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
class Trip(BaseModel):
 | 
			
		||||
    uuid: str = Field(default_factory=uuid.uuid4)
 | 
			
		||||
    total_time: int
 | 
			
		||||
    first_landmark_uuid: str
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_linked_landmarks(self, landmarks: LinkedLandmarks, cache_client: Client) -> "Trip":
 | 
			
		||||
        """
 | 
			
		||||
        Initialize a new Trip object and ensure it is stored in the cache.
 | 
			
		||||
        """
 | 
			
		||||
        trip = Trip(
 | 
			
		||||
            total_time = landmarks.total_time,
 | 
			
		||||
            first_landmark_uuid = str(landmarks[0].uuid)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # 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)
 | 
			
		||||
        # is equivalent to:
 | 
			
		||||
        # for landmark in landmarks:
 | 
			
		||||
        #     cache_client.set(f"landmark_{landmark.uuid}", landmark, expire=3600)
 | 
			
		||||
 | 
			
		||||
        return trip
 | 
			
		||||
@@ -20,18 +20,9 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    preferences = Preferences(
 | 
			
		||||
                    sightseeing=Preference(
 | 
			
		||||
                                  name='sightseeing', 
 | 
			
		||||
                                  type='sightseeing',
 | 
			
		||||
                                  score = 5),
 | 
			
		||||
                    nature=Preference(
 | 
			
		||||
                                  name='nature', 
 | 
			
		||||
                                  type='nature',
 | 
			
		||||
                                  score = 5),
 | 
			
		||||
                    shopping=Preference(
 | 
			
		||||
                                  name='shopping', 
 | 
			
		||||
                                  type='shopping',
 | 
			
		||||
                                  score = 5),
 | 
			
		||||
        sightseeing=Preference(type='sightseeing', score = 5),
 | 
			
		||||
        nature=Preference(type='nature', score = 5),
 | 
			
		||||
        shopping=Preference(type='shopping', score = 5),
 | 
			
		||||
 | 
			
		||||
        max_time_minute=180,
 | 
			
		||||
        detour_tolerance_minute=30
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								backend/src/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								backend/src/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										34
									
								
								backend/src/tests/test_main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								backend/src/tests/test_main.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
from fastapi.testclient import TestClient
 | 
			
		||||
import pytest
 | 
			
		||||
from ..main import app
 | 
			
		||||
 | 
			
		||||
@pytest.fixture()
 | 
			
		||||
def client():
 | 
			
		||||
    return TestClient(app)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_new_trip_invalid_prefs(client):
 | 
			
		||||
    response = client.post(
 | 
			
		||||
        "/trip/new",
 | 
			
		||||
        json={
 | 
			
		||||
            "preferences": {},
 | 
			
		||||
            "start": [48.8566, 2.3522]
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    assert response.status_code == 422
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_new_trip_single_prefs(client):
 | 
			
		||||
    response = client.post(
 | 
			
		||||
        "/trip/new",
 | 
			
		||||
        json={
 | 
			
		||||
            "preferences": {"sightseeing": {"type": "sightseeing", "score": 1}, "nature": {"type": "nature", "score": 1}, "shopping": {"type": "shopping", "score": 1}, "max_time_minute": 360, "detour_tolerance_minute": 0},
 | 
			
		||||
            "start": [48.8566, 2.3522]
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    assert response.status_code == 200
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_new_trip_matches_prefs(client):
 | 
			
		||||
    # todo
 | 
			
		||||
    pass
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import yaml
 | 
			
		||||
from geopy.distance import geodesic
 | 
			
		||||
 | 
			
		||||
import constants
 | 
			
		||||
from ..constants import OPTIMIZER_PARAMETERS_PATH
 | 
			
		||||
 | 
			
		||||
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
 | 
			
		||||
with OPTIMIZER_PARAMETERS_PATH.open('r') as f:
 | 
			
		||||
    parameters = yaml.safe_load(f)
 | 
			
		||||
    DETOUR_FACTOR = parameters['detour_factor']
 | 
			
		||||
    AVERAGE_WALKING_SPEED = parameters['average_walking_speed']
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,10 @@ from pywikibot import config
 | 
			
		||||
config.put_throttle = 0
 | 
			
		||||
config.maxlag = 0
 | 
			
		||||
 | 
			
		||||
from structs.preferences import Preferences, Preference
 | 
			
		||||
from structs.landmark import Landmark
 | 
			
		||||
from ..structs.preferences import Preferences, Preference
 | 
			
		||||
from ..structs.landmark import Landmark
 | 
			
		||||
from .take_most_important import take_most_important
 | 
			
		||||
import constants
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SIGHTSEEING = 'sightseeing'
 | 
			
		||||
NATURE = 'nature'
 | 
			
		||||
SHOPPING = 'shopping'
 | 
			
		||||
from ..constants import AMENITY_SELECTORS_PATH, LANDMARK_PARAMETERS_PATH, OPTIMIZER_PARAMETERS_PATH, OSM_CACHE_DIR
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +20,6 @@ class LandmarkManager:
 | 
			
		||||
 | 
			
		||||
    logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
    city_bbox_side: int     # bbox side in meters
 | 
			
		||||
    radius_close_to: int    # radius in meters
 | 
			
		||||
    church_coeff: float     # coeff to adjsut score of churches
 | 
			
		||||
    park_coeff: float       # coeff to adjust score of parks
 | 
			
		||||
@@ -35,20 +29,25 @@ class LandmarkManager:
 | 
			
		||||
 | 
			
		||||
    def __init__(self) -> None:
 | 
			
		||||
 | 
			
		||||
        with constants.AMENITY_SELECTORS_PATH.open('r') as f:
 | 
			
		||||
        with AMENITY_SELECTORS_PATH.open('r') as f:
 | 
			
		||||
            self.amenity_selectors = yaml.safe_load(f)
 | 
			
		||||
 | 
			
		||||
        with constants.LANDMARK_PARAMETERS_PATH.open('r') as f:
 | 
			
		||||
        with LANDMARK_PARAMETERS_PATH.open('r') as 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.church_coeff = parameters['church_coeff']
 | 
			
		||||
            self.park_coeff = parameters['park_coeff']
 | 
			
		||||
            self.tag_coeff = parameters['tag_coeff']
 | 
			
		||||
            self.N_important = parameters['N_important']
 | 
			
		||||
            
 | 
			
		||||
        with 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()
 | 
			
		||||
        CachingStrategy.use(JSON, cacheDir=constants.OSM_CACHE_DIR)
 | 
			
		||||
        CachingStrategy.use(JSON, cacheDir=OSM_CACHE_DIR)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def generate_landmarks_list(self, center_coordinates: tuple[float, float], preferences: Preferences) -> tuple[list[Landmark], list[Landmark]]:
 | 
			
		||||
@@ -69,30 +68,33 @@ class LandmarkManager:
 | 
			
		||||
                - 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 = []
 | 
			
		||||
        bbox = self.create_bbox(center_coordinates)
 | 
			
		||||
        bbox = self.create_bbox(center_coordinates, reachable_bbox_side)
 | 
			
		||||
        # list for sightseeing
 | 
			
		||||
        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)  
 | 
			
		||||
            L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], SIGHTSEEING, score_function)
 | 
			
		||||
            self.correct_score(L1, preferences.sightseeing)
 | 
			
		||||
            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)
 | 
			
		||||
            L += L1
 | 
			
		||||
 | 
			
		||||
        # list for nature
 | 
			
		||||
        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)  
 | 
			
		||||
            L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], NATURE, score_function)
 | 
			
		||||
            self.correct_score(L2, preferences.nature)
 | 
			
		||||
            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)
 | 
			
		||||
            L += L2
 | 
			
		||||
 | 
			
		||||
        # list for shopping
 | 
			
		||||
        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))
 | 
			
		||||
            L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], SHOPPING, score_function)
 | 
			
		||||
            self.correct_score(L3, preferences.shopping)
 | 
			
		||||
            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)
 | 
			
		||||
            L += L3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        L = self.remove_duplicates(L)
 | 
			
		||||
        self.correct_score(L, preferences)
 | 
			
		||||
 | 
			
		||||
        L_constrained = take_most_important(L, self.N_important)
 | 
			
		||||
        self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.')
 | 
			
		||||
 | 
			
		||||
@@ -123,7 +125,7 @@ class LandmarkManager:
 | 
			
		||||
        return L_clean
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    def correct_score(self, landmarks: list[Landmark], preference: Preference):
 | 
			
		||||
    def correct_score(self, landmarks: list[Landmark], preferences: Preferences) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Adjust the attractiveness score of each landmark in the list based on user preferences.
 | 
			
		||||
 | 
			
		||||
@@ -132,20 +134,16 @@ class LandmarkManager:
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            landmarks (list[Landmark]): A list of landmarks whose scores need to be corrected.
 | 
			
		||||
            preference (Preference): The user's preference settings that influence the attractiveness score adjustment.
 | 
			
		||||
 | 
			
		||||
        Raises:
 | 
			
		||||
            TypeError: If the type of any landmark in the list does not match the expected type in the preference.
 | 
			
		||||
            preferences (Preferences): The user's preference settings that influence the attractiveness score adjustment.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if len(landmarks) == 0:
 | 
			
		||||
            return
 | 
			
		||||
        
 | 
			
		||||
        if landmarks[0].type != preference.type:
 | 
			
		||||
            raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {landmarks[0].name}")
 | 
			
		||||
 | 
			
		||||
        for elem in landmarks:
 | 
			
		||||
            elem.attractiveness = int(elem.attractiveness*preference.score/5)     # arbitrary computation
 | 
			
		||||
        score_dict = {
 | 
			
		||||
            preferences.sightseeing.type: preferences.sightseeing.score,
 | 
			
		||||
            preferences.nature.type: preferences.nature.score,
 | 
			
		||||
            preferences.shopping.type: preferences.shopping.score
 | 
			
		||||
        }
 | 
			
		||||
        for landmark in landmarks:
 | 
			
		||||
            landmark.attractiveness = int(landmark.attractiveness * score_dict[landmark.type] / 5)        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def count_elements_close_to(self, coordinates: tuple[float, float]) -> int:
 | 
			
		||||
@@ -191,12 +189,13 @@ class LandmarkManager:
 | 
			
		||||
            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.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            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:
 | 
			
		||||
            tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude
 | 
			
		||||
@@ -207,7 +206,7 @@ class LandmarkManager:
 | 
			
		||||
        lon = coordinates[1]
 | 
			
		||||
 | 
			
		||||
        # 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
 | 
			
		||||
        lat_diff = half_side_length_km / 111  # 1 degree latitude is approximately 111 km
 | 
			
		||||
@@ -296,21 +295,26 @@ class LandmarkManager:
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
                    if "wikipedia" in tag:
 | 
			
		||||
                        n_tags += 3             # wikipedia entries count more
 | 
			
		||||
                        n_tags += 1             # wikipedia entries count more
 | 
			
		||||
 | 
			
		||||
                    if tag == "wikidata":
 | 
			
		||||
                        Q = elem.tag('wikidata')
 | 
			
		||||
                        site = Site("wikidata", "wikidata")
 | 
			
		||||
                        item = ItemPage(site, Q)
 | 
			
		||||
                        item.get()
 | 
			
		||||
                        n_languages = len(item.labels)
 | 
			
		||||
                        n_tags += n_languages/10
 | 
			
		||||
                    # if tag == "wikidata":
 | 
			
		||||
                    #     Q = elem.tag('wikidata')
 | 
			
		||||
                    #     site = Site("wikidata", "wikidata")
 | 
			
		||||
                    #     item = ItemPage(site, Q)
 | 
			
		||||
                    #     item.get()
 | 
			
		||||
                    #     n_languages = len(item.labels)
 | 
			
		||||
                    #     n_tags += n_languages/10
 | 
			
		||||
                    if "viewpoint" in tag:
 | 
			
		||||
                        n_tags += 10
 | 
			
		||||
 | 
			
		||||
                    if elem_type != "nature":
 | 
			
		||||
                        if "leisure" in tag and elem.tag('leisure') == "park":
 | 
			
		||||
                            elem_type = "nature"
 | 
			
		||||
                    
 | 
			
		||||
                    if landmarktype != SHOPPING:
 | 
			
		||||
                    if elem_type == "nature":
 | 
			
		||||
                        n_tags += 1 
 | 
			
		||||
 | 
			
		||||
                    if landmarktype != "shopping":
 | 
			
		||||
                        if "shop" in tag:
 | 
			
		||||
                            skip = True
 | 
			
		||||
                            break
 | 
			
		||||
@@ -318,7 +322,6 @@ class LandmarkManager:
 | 
			
		||||
                        if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']:
 | 
			
		||||
                            skip = True
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
                if skip:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,9 @@ from scipy.optimize import linprog
 | 
			
		||||
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
 | 
			
		||||
import constants
 | 
			
		||||
from ..constants import OPTIMIZER_PARAMETERS_PATH
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +26,7 @@ class Optimizer:
 | 
			
		||||
    def __init__(self) :
 | 
			
		||||
 | 
			
		||||
        # load parameters from file
 | 
			
		||||
        with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
 | 
			
		||||
        with OPTIMIZER_PARAMETERS_PATH.open('r') as f:
 | 
			
		||||
            parameters = yaml.safe_load(f)
 | 
			
		||||
            self.detour_factor = parameters['detour_factor']
 | 
			
		||||
            self.average_walking_speed = parameters['average_walking_speed']
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,10 @@ import yaml, logging
 | 
			
		||||
from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull
 | 
			
		||||
from math import pi
 | 
			
		||||
 | 
			
		||||
from structs.landmark import Landmark
 | 
			
		||||
from ..structs.landmark import Landmark
 | 
			
		||||
from . import take_most_important, get_time_separation
 | 
			
		||||
from .optimizer import Optimizer
 | 
			
		||||
import constants
 | 
			
		||||
from ..constants import OPTIMIZER_PARAMETERS_PATH
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -24,7 +24,7 @@ class Refiner :
 | 
			
		||||
        self.optimizer = optimizer
 | 
			
		||||
 | 
			
		||||
        # load parameters from file
 | 
			
		||||
        with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
 | 
			
		||||
        with OPTIMIZER_PARAMETERS_PATH.open('r') as f:
 | 
			
		||||
            parameters = yaml.safe_load(f)
 | 
			
		||||
            self.detour_factor = parameters['detour_factor']
 | 
			
		||||
            self.detour_corridor_width = parameters['detour_corridor_width']
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from structs.landmark import Landmark
 | 
			
		||||
from ..structs.landmark import Landmark
 | 
			
		||||
 | 
			
		||||
def take_most_important(landmarks: list[Landmark], N_important) -> list[Landmark] :
 | 
			
		||||
    L = len(landmarks)
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ if (secretPropertiesFile.exists()) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    namespace "com.example.fast_network_navigation"
 | 
			
		||||
    namespace "com.anydev.anyway"
 | 
			
		||||
    compileSdk flutter.compileSdkVersion
 | 
			
		||||
    ndkVersion flutter.ndkVersion
 | 
			
		||||
 | 
			
		||||
@@ -61,7 +61,7 @@ android {
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
 | 
			
		||||
        applicationId "com.example.fast_network_navigation"
 | 
			
		||||
        applicationId "com.anydev.anyway"
 | 
			
		||||
        // You can update the following values to match your application needs.
 | 
			
		||||
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
 | 
			
		||||
        // Minimum Android version for Google Maps SDK
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:label="fast_network_navigation"
 | 
			
		||||
        android:label="anyway"
 | 
			
		||||
        android:name="${applicationName}"
 | 
			
		||||
        android:icon="@mipmap/ic_launcher">
 | 
			
		||||
        <activity
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package com.example.fast_network_navigation
 | 
			
		||||
package com.anydev.anyway
 | 
			
		||||
 | 
			
		||||
import io.flutter.embedding.android.FlutterActivity
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
	<key>CFBundleInfoDictionaryVersion</key>
 | 
			
		||||
	<string>6.0</string>
 | 
			
		||||
	<key>CFBundleName</key>
 | 
			
		||||
	<string>fast_network_navigation</string>
 | 
			
		||||
	<string>anyway</string>
 | 
			
		||||
	<key>CFBundlePackageType</key>
 | 
			
		||||
	<string>APPL</string>
 | 
			
		||||
	<key>CFBundleShortVersionString</key>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								frontend/lib/constants.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								frontend/lib/constants.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
const String APP_NAME = 'AnyWay';
 | 
			
		||||
 | 
			
		||||
String API_URL_BASE = 'https://anyway.kluster.moll.re';
 | 
			
		||||
 | 
			
		||||
@@ -1,23 +1,29 @@
 | 
			
		||||
import 'package:fast_network_navigation/modules/trips_overview.dart';
 | 
			
		||||
import 'package:fast_network_navigation/pages/new_trip.dart';
 | 
			
		||||
import 'package:fast_network_navigation/pages/tutorial.dart';
 | 
			
		||||
import 'package:fast_network_navigation/structs/trip.dart';
 | 
			
		||||
import 'package:fast_network_navigation/utils/load_trips.dart';
 | 
			
		||||
import 'dart:collection';
 | 
			
		||||
 | 
			
		||||
import 'package:anyway/structs/landmark.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/pages/overview.dart';
 | 
			
		||||
import 'package:fast_network_navigation/pages/profile.dart';
 | 
			
		||||
import 'package:anyway/constants.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:anyway/structs/trip.dart';
 | 
			
		||||
import 'package:anyway/modules/trips_overview.dart';
 | 
			
		||||
import 'package:anyway/utils/load_trips.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:anyway/pages/new_trip.dart';
 | 
			
		||||
import 'package:anyway/pages/tutorial.dart';
 | 
			
		||||
import 'package:anyway/pages/overview.dart';
 | 
			
		||||
import 'package:anyway/pages/profile.dart';
 | 
			
		||||
 | 
			
		||||
// BasePage is the scaffold that holds all other pages
 | 
			
		||||
// A side drawer is used to switch between pages
 | 
			
		||||
class BasePage extends StatefulWidget {
 | 
			
		||||
  final String mainScreen;
 | 
			
		||||
  final Future<Trip>? trip;
 | 
			
		||||
  final Trip? trip;
 | 
			
		||||
 | 
			
		||||
  const BasePage({
 | 
			
		||||
    super.key,
 | 
			
		||||
    required this.mainScreen,
 | 
			
		||||
    this.trip
 | 
			
		||||
    this.trip,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -43,20 +49,20 @@ class _BasePageState extends State<BasePage> {
 | 
			
		||||
    final ThemeData theme = Theme.of(context);
 | 
			
		||||
    
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(title: Text("City Nav")),
 | 
			
		||||
      appBar: AppBar(title: Text(APP_NAME)),
 | 
			
		||||
      body: Center(child: currentView),
 | 
			
		||||
      drawer: Drawer(
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            DrawerHeader(
 | 
			
		||||
              decoration: BoxDecoration(
 | 
			
		||||
                gradient: LinearGradient(colors: [Colors.cyan, theme.primaryColor])
 | 
			
		||||
                gradient: LinearGradient(colors: [Colors.red, Colors.yellow])
 | 
			
		||||
              ),
 | 
			
		||||
              child: Center(
 | 
			
		||||
                child: Text(
 | 
			
		||||
                  'City Nav',
 | 
			
		||||
                  APP_NAME,
 | 
			
		||||
                  style: TextStyle(
 | 
			
		||||
                    color: Colors.white,
 | 
			
		||||
                    color: Colors.grey[800],
 | 
			
		||||
                    fontSize: 24,
 | 
			
		||||
                    fontWeight: FontWeight.bold,
 | 
			
		||||
                  ),
 | 
			
		||||
@@ -126,9 +132,71 @@ class _BasePageState extends State<BasePage> {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Future<Trip> getFirstTrip (Future<List<Trip>> trips) async {
 | 
			
		||||
  List<Trip> tripsf = await trips;
 | 
			
		||||
  return tripsf[0];
 | 
			
		||||
// This function is used to get the first trip from a list of trips
 | 
			
		||||
// TODO: Implement this function
 | 
			
		||||
Trip getFirstTrip(Future<List<Trip>> trips) {
 | 
			
		||||
  Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>());
 | 
			
		||||
  t1.landmarks.add(
 | 
			
		||||
    Landmark(
 | 
			
		||||
      uuid: '0',
 | 
			
		||||
      name: "Start",
 | 
			
		||||
      location: [48.85, 2.32],
 | 
			
		||||
      type: start,
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  t1.landmarks.add(
 | 
			
		||||
    Landmark(
 | 
			
		||||
      uuid: '1',
 | 
			
		||||
      name: "Eiffel Tower",
 | 
			
		||||
      location: [48.859, 2.295],
 | 
			
		||||
      type: sightseeing,
 | 
			
		||||
      imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg"
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  t1.landmarks.add(
 | 
			
		||||
    Landmark(
 | 
			
		||||
      uuid: "2",
 | 
			
		||||
      name: "Notre Dame Cathedral",
 | 
			
		||||
      location: [48.8530, 2.3498],
 | 
			
		||||
      type: sightseeing,
 | 
			
		||||
      imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Notre-Dame_de_Paris%2C_4_October_2017.jpg/440px-Notre-Dame_de_Paris%2C_4_October_2017.jpg"
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  t1.landmarks.add(
 | 
			
		||||
    Landmark(
 | 
			
		||||
      uuid: "3",
 | 
			
		||||
      name: "Louvre palace",
 | 
			
		||||
      location: [48.8606, 2.3376],
 | 
			
		||||
      type: sightseeing,
 | 
			
		||||
      imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Louvre_Museum_Wikimedia_Commons.jpg/540px-Louvre_Museum_Wikimedia_Commons.jpg"
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  t1.landmarks.add(
 | 
			
		||||
    Landmark(
 | 
			
		||||
      uuid: "4",
 | 
			
		||||
      name: "Pont-des-arts",
 | 
			
		||||
      location: [48.8585, 2.3376],
 | 
			
		||||
      type: sightseeing,
 | 
			
		||||
      imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg/560px-Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg"
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  t1.landmarks.add(
 | 
			
		||||
    Landmark(
 | 
			
		||||
      uuid: "5",
 | 
			
		||||
      name: "Panthéon",
 | 
			
		||||
      location: [48.847, 2.347],
 | 
			
		||||
      type: sightseeing,
 | 
			
		||||
      imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Pantheon_of_Paris_007.JPG/1280px-Pantheon_of_Paris_007.JPG"
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  t1.landmarks.add(
 | 
			
		||||
    Landmark(
 | 
			
		||||
      uuid: "6",
 | 
			
		||||
      name: "Galeries Lafayette",
 | 
			
		||||
      location: [48.87, 2.32],
 | 
			
		||||
      type: shopping,
 | 
			
		||||
      imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/de/GaleriesLafayetteNuit.jpg/220px-GaleriesLafayetteNuit.jpg"
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  return t1;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:fast_network_navigation/layout.dart';
 | 
			
		||||
import 'package:anyway/constants.dart';
 | 
			
		||||
import 'package:anyway/layout.dart';
 | 
			
		||||
 | 
			
		||||
void main() => runApp(const App());
 | 
			
		||||
 | 
			
		||||
class App extends StatelessWidget {
 | 
			
		||||
  const App({super.key});
 | 
			
		||||
 | 
			
		||||
  static const appTitle = 'City Nav';
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return MaterialApp(
 | 
			
		||||
      title: appTitle,
 | 
			
		||||
      title: APP_NAME,
 | 
			
		||||
      home: BasePage(mainScreen: "map"),
 | 
			
		||||
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.green),
 | 
			
		||||
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,15 @@
 | 
			
		||||
import 'package:fast_network_navigation/structs/trip.dart';
 | 
			
		||||
import 'dart:developer';
 | 
			
		||||
 | 
			
		||||
import 'package:anyway/structs/trip.dart';
 | 
			
		||||
import 'package:auto_size_text/auto_size_text.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class Greeter extends StatefulWidget {
 | 
			
		||||
  final Future<Trip> trip;
 | 
			
		||||
  final bool standalone;
 | 
			
		||||
  final Trip trip;
 | 
			
		||||
 | 
			
		||||
  Greeter({
 | 
			
		||||
    required this.standalone,
 | 
			
		||||
    required this.trip
 | 
			
		||||
    required this.trip,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -16,44 +17,92 @@ class Greeter extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _GreeterState extends State<Greeter> {
 | 
			
		||||
  Widget greeterBuild (BuildContext context, AsyncSnapshot<Trip> snapshot) {
 | 
			
		||||
  
 | 
			
		||||
  Widget greeterBuilder (BuildContext context, Widget? child) {
 | 
			
		||||
    ThemeData theme = Theme.of(context);
 | 
			
		||||
    String cityName = "";
 | 
			
		||||
    TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24);
 | 
			
		||||
 | 
			
		||||
    Widget topGreeter;
 | 
			
		||||
 | 
			
		||||
    if (widget.trip.uuid != 'pending') {
 | 
			
		||||
      topGreeter = FutureBuilder(
 | 
			
		||||
        future: widget.trip.cityName,
 | 
			
		||||
        builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
 | 
			
		||||
          if (snapshot.hasData) {
 | 
			
		||||
      cityName = snapshot.data?.cityName ?? '...';
 | 
			
		||||
    } else if (snapshot.hasError) {
 | 
			
		||||
      cityName = "error";
 | 
			
		||||
    } else { // still awaiting the cityname
 | 
			
		||||
      cityName = "...";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Widget topGreeter = Text(
 | 
			
		||||
      'Welcome to $cityName!',
 | 
			
		||||
      style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
 | 
			
		||||
            return AutoSizeText(
 | 
			
		||||
              maxLines: 1,
 | 
			
		||||
              'Welcome to ${snapshot.data}!',
 | 
			
		||||
              style: greeterStyle
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
    if (widget.standalone) {
 | 
			
		||||
      return Center(
 | 
			
		||||
        child: Padding(
 | 
			
		||||
          padding: EdgeInsets.only(top: 24.0),
 | 
			
		||||
          child: topGreeter,
 | 
			
		||||
        ),
 | 
			
		||||
          } 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: Column(
 | 
			
		||||
        children: [
 | 
			
		||||
            Padding(padding: EdgeInsets.only(top: 24.0)),
 | 
			
		||||
          // Padding(padding: EdgeInsets.only(top: 20)),
 | 
			
		||||
          topGreeter,
 | 
			
		||||
            bottomGreeter,
 | 
			
		||||
            Padding(padding: EdgeInsets.only(bottom: 24.0)),
 | 
			
		||||
          Padding(
 | 
			
		||||
            padding: EdgeInsets.all(20),
 | 
			
		||||
            child: bottomGreeter
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget bottomGreeter = const Text(
 | 
			
		||||
    "Busy day ahead? Here is how to make the most of it!",
 | 
			
		||||
@@ -65,9 +114,9 @@ class _GreeterState extends State<Greeter> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return FutureBuilder(
 | 
			
		||||
      future: widget.trip,
 | 
			
		||||
      builder: greeterBuild,
 | 
			
		||||
    return ListenableBuilder(
 | 
			
		||||
      listenable: widget.trip,
 | 
			
		||||
      builder: greeterBuilder,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import 'package:fast_network_navigation/structs/landmark.dart';
 | 
			
		||||
import 'package:anyway/structs/landmark.dart';
 | 
			
		||||
import 'package:cached_network_image/cached_network_image.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -31,9 +32,10 @@ class _LandmarkCardState extends State<LandmarkCard> {
 | 
			
		||||
              height: double.infinity,
 | 
			
		||||
              // force a fixed width
 | 
			
		||||
              width: 160,
 | 
			
		||||
              child: Image.network(
 | 
			
		||||
                widget.landmark.imageURL ?? '',
 | 
			
		||||
                errorBuilder: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
 | 
			
		||||
              child: CachedNetworkImage(
 | 
			
		||||
                imageUrl: widget.landmark.imageURL ?? '',
 | 
			
		||||
                placeholder: (context, url) => CircularProgressIndicator(),
 | 
			
		||||
                errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
 | 
			
		||||
                // TODO: make this a switch statement to load a placeholder if null
 | 
			
		||||
                // cover the whole container meaning the image will be cropped
 | 
			
		||||
                fit: BoxFit.cover,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,16 @@
 | 
			
		||||
import 'dart:collection';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/modules/landmark_card.dart';
 | 
			
		||||
import 'package:fast_network_navigation/structs/landmark.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/structs/trip.dart';
 | 
			
		||||
import 'dart:developer';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:anyway/modules/landmark_card.dart';
 | 
			
		||||
import 'package:anyway/structs/landmark.dart';
 | 
			
		||||
import 'package:anyway/structs/trip.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LandmarksOverview extends StatefulWidget {
 | 
			
		||||
  final Future<Trip>? trip;
 | 
			
		||||
  final Trip? trip;
 | 
			
		||||
  const LandmarksOverview({super.key, this.trip});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -19,22 +18,37 @@ class LandmarksOverview extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _LandmarksOverviewState extends State<LandmarksOverview> {
 | 
			
		||||
  // final Future<List<Landmark>> _landmarks = fetchLandmarks();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final Future<LinkedList<Landmark>> _landmarks = getLandmarks(widget.trip);
 | 
			
		||||
    return DefaultTextStyle(
 | 
			
		||||
      style: Theme.of(context).textTheme.displayMedium!,
 | 
			
		||||
      textAlign: TextAlign.center,
 | 
			
		||||
      child: FutureBuilder<LinkedList<Landmark>>(
 | 
			
		||||
        future: _landmarks,
 | 
			
		||||
        builder: (BuildContext context, AsyncSnapshot<LinkedList<Landmark>> snapshot) {
 | 
			
		||||
    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 (snapshot.hasData) {
 | 
			
		||||
            children = [landmarksWithSteps(snapshot.data!), saveButton()];
 | 
			
		||||
          } else if (snapshot.hasError) {
 | 
			
		||||
            children = <Widget>[
 | 
			
		||||
        
 | 
			
		||||
        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"),
 | 
			
		||||
            ];
 | 
			
		||||
          } else {
 | 
			
		||||
            children = [
 | 
			
		||||
              landmarksWithSteps(),
 | 
			
		||||
              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,
 | 
			
		||||
@@ -42,20 +56,15 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
 | 
			
		||||
              ),
 | 
			
		||||
              Padding(
 | 
			
		||||
                padding: const EdgeInsets.only(top: 16),
 | 
			
		||||
                child: Text('Error: ${snapshot.error}', style: TextStyle(fontSize: 12)),
 | 
			
		||||
                child: Text('Error: ${trip.errorDescription}'),
 | 
			
		||||
              ),
 | 
			
		||||
            ];
 | 
			
		||||
          } else {
 | 
			
		||||
            children = [Center(child: CircularProgressIndicator())];
 | 
			
		||||
        }
 | 
			
		||||
          return Center(
 | 
			
		||||
            child: Column(
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
 | 
			
		||||
        return Column(
 | 
			
		||||
          children: children,
 | 
			
		||||
            ),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  Widget saveButton() => ElevatedButton(
 | 
			
		||||
@@ -67,29 +76,56 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
 | 
			
		||||
    child: const Text('Save'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Widget landmarksWithSteps(LinkedList<Landmark> landmarks) {
 | 
			
		||||
  Widget landmarksWithSteps() {
 | 
			
		||||
    return ListenableBuilder(
 | 
			
		||||
      listenable: widget.trip!,
 | 
			
		||||
      builder: (BuildContext context, Widget? child) {
 | 
			
		||||
        List<Widget> children = [];
 | 
			
		||||
  for (Landmark landmark in landmarks) {
 | 
			
		||||
    children.add(LandmarkCard(landmark));
 | 
			
		||||
        for (Landmark landmark in widget.trip!.landmarks) {
 | 
			
		||||
          children.add(
 | 
			
		||||
            Dismissible(
 | 
			
		||||
              key: ValueKey<int>(landmark.hashCode),
 | 
			
		||||
              child: LandmarkCard(landmark),
 | 
			
		||||
              dismissThresholds: {DismissDirection.endToStart: 0.6},
 | 
			
		||||
              onDismissed: (direction) {
 | 
			
		||||
                // Remove the item from the data source.
 | 
			
		||||
                  log(landmark.name);
 | 
			
		||||
                setState(() {
 | 
			
		||||
                  widget.trip!.removeLandmark(landmark);
 | 
			
		||||
                });
 | 
			
		||||
                // Then show a snackbar.
 | 
			
		||||
                ScaffoldMessenger.of(context)
 | 
			
		||||
                    .showSnackBar(SnackBar(content: Text("We won't show ${landmark.name} again")));
 | 
			
		||||
              },
 | 
			
		||||
              background: Container(color: Colors.red),
 | 
			
		||||
              secondaryBackground: Container(
 | 
			
		||||
                color: Colors.red,
 | 
			
		||||
                child: Icon(
 | 
			
		||||
                  Icons.delete,
 | 
			
		||||
                  color: Colors.white,
 | 
			
		||||
                ),
 | 
			
		||||
                padding: EdgeInsets.all(15),
 | 
			
		||||
                alignment: Alignment.centerRight,
 | 
			
		||||
              ),
 | 
			
		||||
            )
 | 
			
		||||
          );
 | 
			
		||||
          if (landmark.next != null) {
 | 
			
		||||
            Widget step = stepBetweenLandmarks(landmark, landmark.next!);
 | 
			
		||||
            children.add(step);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Column(
 | 
			
		||||
          children: children  
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Widget stepBetweenLandmarks(Landmark before, Landmark after) {
 | 
			
		||||
  // 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
 | 
			
		||||
Widget stepBetweenLandmarks(Landmark current, Landmark next) {
 | 
			
		||||
  int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5;
 | 
			
		||||
  // ~/ is integer division (rounding)
 | 
			
		||||
  return Container(
 | 
			
		||||
    margin: EdgeInsets.all(10),
 | 
			
		||||
    padding: EdgeInsets.all(10),
 | 
			
		||||
@@ -108,7 +144,7 @@ Widget stepBetweenLandmarks(Landmark before, Landmark after) {
 | 
			
		||||
        Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            Icon(Icons.directions_walk),
 | 
			
		||||
            Text("5 min", style: TextStyle(fontSize: 10)),
 | 
			
		||||
            Text("~$timeRounded min", style: TextStyle(fontSize: 10)),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
        Spacer(),
 | 
			
		||||
@@ -116,15 +152,17 @@ Widget stepBetweenLandmarks(Landmark before, Landmark after) {
 | 
			
		||||
          onPressed: () {
 | 
			
		||||
            // Open navigation instructions
 | 
			
		||||
          },
 | 
			
		||||
          child: Text("Navigate"),
 | 
			
		||||
          child: Row(
 | 
			
		||||
            children: [
 | 
			
		||||
              Icon(Icons.directions),
 | 
			
		||||
              Text("Directions"),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Future<LinkedList<Landmark>> getLandmarks (Future<Trip>? trip) async {
 | 
			
		||||
  Trip tripf = await trip!;
 | 
			
		||||
  return tripf.landmarks;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,16 @@
 | 
			
		||||
import 'dart:collection';
 | 
			
		||||
import 'dart:developer';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/structs/landmark.dart';
 | 
			
		||||
import 'package:fast_network_navigation/structs/trip.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:anyway/structs/landmark.dart';
 | 
			
		||||
import 'package:anyway/structs/trip.dart';
 | 
			
		||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
 | 
			
		||||
import 'package:widget_to_marker/widget_to_marker.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MapWidget extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
  final Future<Trip>? trip;
 | 
			
		||||
  final Trip? trip;
 | 
			
		||||
 | 
			
		||||
  MapWidget({
 | 
			
		||||
    this.trip
 | 
			
		||||
@@ -19,58 +22,130 @@ class MapWidget extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
class _MapWidgetState extends State<MapWidget> {
 | 
			
		||||
  late GoogleMapController mapController;
 | 
			
		||||
  // coordinates of Paris
 | 
			
		||||
 | 
			
		||||
  CameraPosition _cameraPosition = CameraPosition(
 | 
			
		||||
    target: LatLng(48.8566, 2.3522),
 | 
			
		||||
    zoom: 11.0,
 | 
			
		||||
  );
 | 
			
		||||
  Set<Marker> markers = <Marker>{};
 | 
			
		||||
  Set<Marker> mapMarkers = <Marker>{};
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  void _onMapCreated(GoogleMapController controller) async {
 | 
			
		||||
    mapController = controller;
 | 
			
		||||
    Trip? trip = await widget.trip;
 | 
			
		||||
    List<double>? newLocation = trip?.landmarks.first.location;
 | 
			
		||||
    List<double>? newLocation = widget.trip?.landmarks.firstOrNull?.location;
 | 
			
		||||
    if (newLocation != null) {
 | 
			
		||||
      CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1]));
 | 
			
		||||
      controller.moveCamera(update);
 | 
			
		||||
    }
 | 
			
		||||
    drawLandmarks();
 | 
			
		||||
    setMapMarkers();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  void _onCameraIdle() {
 | 
			
		||||
    // print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  void drawLandmarks() async {
 | 
			
		||||
    // (re)draws landmarks on the map
 | 
			
		||||
    Trip? trip = await widget.trip;
 | 
			
		||||
    LinkedList<Landmark>? landmarks = trip?.landmarks;
 | 
			
		||||
    if (landmarks != null){
 | 
			
		||||
      setState(() {
 | 
			
		||||
        for (Landmark landmark in landmarks) {
 | 
			
		||||
          markers.add(Marker(
 | 
			
		||||
            markerId: MarkerId(landmark.name),
 | 
			
		||||
            position: LatLng(landmark.location[0], landmark.location[1]),
 | 
			
		||||
            infoWindow: InfoWindow(title: landmark.name, snippet: landmark.type.name),
 | 
			
		||||
          ));
 | 
			
		||||
  void setMapMarkers() async {
 | 
			
		||||
    List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? [];
 | 
			
		||||
    Set<Marker> newMarkers = <Marker>{};
 | 
			
		||||
    for (int i = 0; i < landmarks.length; i++) {
 | 
			
		||||
      Landmark landmark = landmarks[i];
 | 
			
		||||
      List<double> location = landmark.location;
 | 
			
		||||
      Marker marker = Marker(
 | 
			
		||||
        markerId: MarkerId(landmark.uuid),
 | 
			
		||||
        position: LatLng(location[0], location[1]),
 | 
			
		||||
        icon: await CustomMarker(landmark: landmark, position: i).toBitmapDescriptor(
 | 
			
		||||
          logicalSize: const Size(150, 150),
 | 
			
		||||
          imageSize: const Size(150, 150)
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      newMarkers.add(marker);
 | 
			
		||||
    }
 | 
			
		||||
    setState(() {
 | 
			
		||||
      mapMarkers = newMarkers;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    widget.trip?.addListener(setMapMarkers);
 | 
			
		||||
    return GoogleMap(
 | 
			
		||||
      onMapCreated: _onMapCreated,
 | 
			
		||||
      initialCameraPosition: _cameraPosition,
 | 
			
		||||
      onCameraIdle: _onCameraIdle,
 | 
			
		||||
      // onLongPress: ,
 | 
			
		||||
      markers: markers,
 | 
			
		||||
      markers: mapMarkers,
 | 
			
		||||
      cloudMapId: '41c21ac9b81dbfd8',
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomMarker extends StatelessWidget {
 | 
			
		||||
  final Landmark landmark;
 | 
			
		||||
  final int position;
 | 
			
		||||
 | 
			
		||||
  CustomMarker({
 | 
			
		||||
    super.key,
 | 
			
		||||
    required this.landmark,
 | 
			
		||||
    required this.position
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    // This returns an outlined circle, with an icon corresponding to the landmark type
 | 
			
		||||
    // As a small dot, the number of the landmark is displayed in the top right
 | 
			
		||||
    Icon icon;
 | 
			
		||||
    if (landmark.type == sightseeing) {
 | 
			
		||||
      icon = Icon(Icons.church, color: Colors.black, size: 50);
 | 
			
		||||
    } else if (landmark.type == nature) {
 | 
			
		||||
      icon = Icon(Icons.park, color: Colors.black, size: 50);
 | 
			
		||||
    } else if (landmark.type == shopping) {
 | 
			
		||||
      icon = Icon(Icons.shopping_cart, color: Colors.black, size: 50);
 | 
			
		||||
    } else if (landmark.type == start || landmark.type == finish) {
 | 
			
		||||
      icon = Icon(Icons.flag, color: Colors.black, size: 50);
 | 
			
		||||
    } else {
 | 
			
		||||
      icon = Icon(Icons.location_on, color: Colors.black, size: 50);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Widget? positionIndicator;
 | 
			
		||||
    if (landmark.type != start && landmark.type != finish) {
 | 
			
		||||
      positionIndicator = Positioned(
 | 
			
		||||
        top: 0,
 | 
			
		||||
        right: 0,
 | 
			
		||||
        child: Container(
 | 
			
		||||
          padding: EdgeInsets.all(5),
 | 
			
		||||
          decoration: BoxDecoration(
 | 
			
		||||
            color: Theme.of(context).primaryColor,
 | 
			
		||||
            shape: BoxShape.circle,
 | 
			
		||||
          ),
 | 
			
		||||
          child: Text('$position', style: TextStyle(color: Colors.white, fontSize: 20)),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return RepaintBoundary(
 | 
			
		||||
      child: Stack(
 | 
			
		||||
        children: [
 | 
			
		||||
          Container(
 | 
			
		||||
            // these are not the final sizes, since the final size is set in the toBitmapDescriptor method
 | 
			
		||||
            // they are useful nevertheless to ensure the scale of the components are correct
 | 
			
		||||
            width: 75,
 | 
			
		||||
            height: 75,
 | 
			
		||||
            decoration: BoxDecoration(
 | 
			
		||||
              gradient: LinearGradient(
 | 
			
		||||
                begin: Alignment.topLeft,
 | 
			
		||||
                end: Alignment.bottomRight,
 | 
			
		||||
                colors: [Colors.red, Colors.yellow]
 | 
			
		||||
              ),
 | 
			
		||||
              shape: BoxShape.circle,
 | 
			
		||||
              border: Border.all(color: Colors.black, width: 5),
 | 
			
		||||
            ),
 | 
			
		||||
            child: icon,
 | 
			
		||||
          ),
 | 
			
		||||
          positionIndicator ?? Container(),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/layout.dart';
 | 
			
		||||
import 'package:fast_network_navigation/structs/trip.dart';
 | 
			
		||||
import 'package:anyway/layout.dart';
 | 
			
		||||
import 'package:anyway/structs/trip.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TripsOverview extends StatefulWidget {
 | 
			
		||||
@@ -25,12 +25,23 @@ class _TripsOverviewState extends State<TripsOverview> {
 | 
			
		||||
      children = List<Widget>.generate(snapshot.data!.length, (index) {
 | 
			
		||||
        Trip trip = snapshot.data![index];
 | 
			
		||||
        return ListTile(
 | 
			
		||||
          title: Text("Trip to ${trip.cityName}"),
 | 
			
		||||
          title: FutureBuilder(
 | 
			
		||||
            future: trip.cityName,
 | 
			
		||||
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
 | 
			
		||||
              if (snapshot.hasData) {
 | 
			
		||||
                return Text("Trip to ${snapshot.data}");
 | 
			
		||||
              } else if (snapshot.hasError) {
 | 
			
		||||
                return Text("Error: ${snapshot.error}");
 | 
			
		||||
              } else {
 | 
			
		||||
                return const Text("Trip to ...");
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
          leading: Icon(Icons.pin_drop),
 | 
			
		||||
          onTap: () {
 | 
			
		||||
            Navigator.of(context).push(
 | 
			
		||||
              MaterialPageRoute(
 | 
			
		||||
                builder: (context) => BasePage(mainScreen: "map", trip: Future.value(trip))
 | 
			
		||||
                builder: (context) => BasePage(mainScreen: "map", trip: trip)
 | 
			
		||||
              )
 | 
			
		||||
            );
 | 
			
		||||
          },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,13 @@
 | 
			
		||||
 | 
			
		||||
import 'package:anyway/structs/landmark.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:geocoding/geocoding.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:anyway/layout.dart';
 | 
			
		||||
import 'package:anyway/utils/fetch_trip.dart';
 | 
			
		||||
import 'package:anyway/structs/preferences.dart';
 | 
			
		||||
import "package:anyway/structs/trip.dart";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewTripPage extends StatefulWidget {
 | 
			
		||||
  const NewTripPage({Key? key}) : super(key: key);
 | 
			
		||||
@@ -9,22 +17,77 @@ class NewTripPage extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _NewTripPageState extends State<NewTripPage> {
 | 
			
		||||
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
 | 
			
		||||
  final TextEditingController latController = TextEditingController();
 | 
			
		||||
  final TextEditingController lonController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: const Text('New Trip'),
 | 
			
		||||
      ),
 | 
			
		||||
      body: Center(
 | 
			
		||||
      body: Form(
 | 
			
		||||
        key: _formKey,
 | 
			
		||||
        child: Padding(
 | 
			
		||||
          padding: const EdgeInsets.all(15.0),
 | 
			
		||||
          child: Column(
 | 
			
		||||
          mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
            crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
            
 | 
			
		||||
            children: <Widget>[
 | 
			
		||||
            const Text(
 | 
			
		||||
              'Create a new trip',
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                decoration: const InputDecoration(hintText: 'Lat'),
 | 
			
		||||
                controller: latController,
 | 
			
		||||
                validator: (String? value) {
 | 
			
		||||
                  if (value == null || value.isEmpty || double.tryParse(value) == null){
 | 
			
		||||
                    return 'Please enter a floating point number';
 | 
			
		||||
                  }
 | 
			
		||||
                  return null;
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                decoration: const InputDecoration(hintText: 'Lon'),
 | 
			
		||||
                controller: lonController,
 | 
			
		||||
 | 
			
		||||
                validator: (String? value) {
 | 
			
		||||
                  if (value == null || value.isEmpty || double.tryParse(value) == null){
 | 
			
		||||
                    return 'Please enter a floating point number';
 | 
			
		||||
                  }
 | 
			
		||||
                  return null;
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
              Divider(height: 15, color: Colors.transparent),
 | 
			
		||||
              ElevatedButton(
 | 
			
		||||
                child: const Text('Create trip'),
 | 
			
		||||
                onPressed: () {
 | 
			
		||||
                  if (_formKey.currentState!.validate()) {
 | 
			
		||||
                    List<double> startPoint = [
 | 
			
		||||
                      double.parse(latController.text),
 | 
			
		||||
                      double.parse(lonController.text)
 | 
			
		||||
                    ];
 | 
			
		||||
                    Future<UserPreferences> preferences = loadUserPreferences();
 | 
			
		||||
                    Trip trip = Trip();
 | 
			
		||||
                    trip.landmarks.add(
 | 
			
		||||
                      Landmark(
 | 
			
		||||
                        location: startPoint,
 | 
			
		||||
                        name: "Start",
 | 
			
		||||
                        type: start,
 | 
			
		||||
                        uuid: "pending"
 | 
			
		||||
                      )
 | 
			
		||||
                    );
 | 
			
		||||
                    fetchTrip(trip, preferences);
 | 
			
		||||
                    Navigator.of(context).push(
 | 
			
		||||
                      MaterialPageRoute(
 | 
			
		||||
                        builder: (context) => BasePage(mainScreen: "map", trip: trip)
 | 
			
		||||
                      )
 | 
			
		||||
                    );
 | 
			
		||||
                  }
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
      ),
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/structs/trip.dart';
 | 
			
		||||
import 'package:anyway/structs/trip.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/modules/landmarks_overview.dart';
 | 
			
		||||
import 'package:fast_network_navigation/modules/map.dart';
 | 
			
		||||
import 'package:fast_network_navigation/modules/greeter.dart';
 | 
			
		||||
import 'package:anyway/modules/landmarks_overview.dart';
 | 
			
		||||
import 'package:anyway/modules/map.dart';
 | 
			
		||||
import 'package:anyway/modules/greeter.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NavigationOverview extends StatefulWidget {
 | 
			
		||||
  final Future<Trip> trip;
 | 
			
		||||
  final Trip trip;
 | 
			
		||||
 | 
			
		||||
  NavigationOverview({
 | 
			
		||||
    required this.trip
 | 
			
		||||
    required this.trip,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -27,53 +27,56 @@ class _NavigationOverviewState extends State<NavigationOverview> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return SlidingUpPanel(
 | 
			
		||||
        renderPanelSheet: false,
 | 
			
		||||
        panel: _floatingPanel(),
 | 
			
		||||
        collapsed: _floatingCollapsed(),
 | 
			
		||||
        body: MapWidget(trip: widget.trip)
 | 
			
		||||
        // collapsed: _floatingCollapsed(),
 | 
			
		||||
        body: MapWidget(trip: widget.trip),
 | 
			
		||||
        // renderPanelSheet: false,
 | 
			
		||||
        // backdropEnabled: true,
 | 
			
		||||
        maxHeight: MediaQuery.of(context).size.height * 0.8,
 | 
			
		||||
        padding: EdgeInsets.all(10),
 | 
			
		||||
        // panelSnapping: false,
 | 
			
		||||
        borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
 | 
			
		||||
        boxShadow: [
 | 
			
		||||
          BoxShadow(
 | 
			
		||||
            blurRadius: 20.0,
 | 
			
		||||
            color: Colors.black,
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _floatingCollapsed(){
 | 
			
		||||
    final ThemeData theme = Theme.of(context);
 | 
			
		||||
    return Container(
 | 
			
		||||
      decoration: BoxDecoration(
 | 
			
		||||
        color: theme.canvasColor,
 | 
			
		||||
        borderRadius: BorderRadius.only(topLeft: Radius.circular(24.0), topRight: Radius.circular(24.0)),
 | 
			
		||||
        boxShadow: []
 | 
			
		||||
      ),
 | 
			
		||||
      
 | 
			
		||||
      child: Greeter(standalone: true, trip: widget.trip)
 | 
			
		||||
    return Greeter(
 | 
			
		||||
      trip: widget.trip
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _floatingPanel(){
 | 
			
		||||
    final ThemeData theme = Theme.of(context);
 | 
			
		||||
    return Container(
 | 
			
		||||
    return Column(
 | 
			
		||||
      children: [
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: const EdgeInsets.all(15),
 | 
			
		||||
          child: 
 | 
			
		||||
            Center(
 | 
			
		||||
              child: Container(
 | 
			
		||||
                width: 40,
 | 
			
		||||
                height: 5,
 | 
			
		||||
                decoration: BoxDecoration(
 | 
			
		||||
        color: Colors.white,
 | 
			
		||||
        borderRadius: BorderRadius.all(Radius.circular(24.0)),
 | 
			
		||||
        boxShadow: [
 | 
			
		||||
          BoxShadow(
 | 
			
		||||
            blurRadius: 20.0,
 | 
			
		||||
            color: theme.shadowColor,
 | 
			
		||||
                  color: Colors.grey[300],
 | 
			
		||||
                  borderRadius: BorderRadius.all(Radius.circular(12.0)),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        Expanded(
 | 
			
		||||
          child: ListView(
 | 
			
		||||
            children: [
 | 
			
		||||
              Greeter(trip: widget.trip),
 | 
			
		||||
              LandmarksOverview(trip: widget.trip)
 | 
			
		||||
            ]
 | 
			
		||||
      ),
 | 
			
		||||
      child: Center(
 | 
			
		||||
        child: Padding(
 | 
			
		||||
        padding: EdgeInsets.all(8.0),
 | 
			
		||||
        child: SingleChildScrollView(
 | 
			
		||||
          child: Column(
 | 
			
		||||
            children: <Widget>[
 | 
			
		||||
              Greeter(standalone: false, trip: widget.trip),
 | 
			
		||||
              LandmarksOverview(trip: widget.trip),
 | 
			
		||||
          )
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import 'package:fast_network_navigation/structs/preferences.dart';
 | 
			
		||||
import 'package:anyway/constants.dart';
 | 
			
		||||
import 'package:anyway/structs/preferences.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool debugMode = false;
 | 
			
		||||
 | 
			
		||||
class ProfilePage extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
@@ -9,6 +11,56 @@ class ProfilePage extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ProfilePageState extends State<ProfilePage> {
 | 
			
		||||
  Future<UserPreferences> _prefs = loadUserPreferences();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  Widget debugButton() {
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: EdgeInsets.only(top: 20),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        children: [
 | 
			
		||||
          Text('Debug mode'),
 | 
			
		||||
          Switch(
 | 
			
		||||
            value: debugMode,
 | 
			
		||||
            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();
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                    );
 | 
			
		||||
                  }
 | 
			
		||||
                );
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return ListView(
 | 
			
		||||
@@ -24,66 +76,82 @@ class _ProfilePageState extends State<ProfilePage> {
 | 
			
		||||
          child: Text('Curious traveler', style: TextStyle(fontSize: 24))
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        Padding(padding: EdgeInsets.all(10)),
 | 
			
		||||
        Divider(indent: 25, endIndent: 25),
 | 
			
		||||
        Padding(padding: EdgeInsets.all(10)),
 | 
			
		||||
        Divider(indent: 25, endIndent: 25, height: 50),
 | 
			
		||||
 | 
			
		||||
        Padding(
 | 
			
		||||
        Center(
 | 
			
		||||
          child: Padding(
 | 
			
		||||
           padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10),
 | 
			
		||||
          child: Text('Please rate your personal preferences so that we can taylor your experience.', style: TextStyle(fontSize: 18))
 | 
			
		||||
            child: Text('For a tailored experience, please rate your discovery preferences.', style: TextStyle(fontSize: 18))
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Now the sliders
 | 
			
		||||
        ImportanceSliders()
 | 
			
		||||
        FutureBuilder(future: _prefs, builder: futureSliders),
 | 
			
		||||
        debugButton()
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget futureSliders(BuildContext context, AsyncSnapshot<UserPreferences> snapshot) {
 | 
			
		||||
    if (snapshot.connectionState == ConnectionState.done) {
 | 
			
		||||
      UserPreferences prefs = snapshot.data!;
 | 
			
		||||
 | 
			
		||||
      return Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          PreferenceSliders(prefs: [prefs.maxTime, prefs.maxDetour]),
 | 
			
		||||
          Divider(indent: 25, endIndent: 25, height: 50),
 | 
			
		||||
          PreferenceSliders(prefs: [prefs.sightseeing, prefs.shopping, prefs.nature])
 | 
			
		||||
        ]
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      return CircularProgressIndicator();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PreferenceSliders extends StatefulWidget {
 | 
			
		||||
  final List<SinglePreference> prefs;
 | 
			
		||||
 | 
			
		||||
class ImportanceSliders extends StatefulWidget {
 | 
			
		||||
  PreferenceSliders({required this.prefs});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<ImportanceSliders> createState() => _ImportanceSlidersState();
 | 
			
		||||
  State<PreferenceSliders> createState() => _PreferenceSlidersState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _ImportanceSlidersState extends State<ImportanceSliders> {
 | 
			
		||||
class _PreferenceSlidersState extends State<PreferenceSliders> {
 | 
			
		||||
 | 
			
		||||
  UserPreferences _prefs = UserPreferences();
 | 
			
		||||
 | 
			
		||||
  List<Card> _createSliders() {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    List<Card> sliders = [];
 | 
			
		||||
    for (SinglePreference pref in _prefs.preferences) {
 | 
			
		||||
      sliders.add(Card(
 | 
			
		||||
      for (SinglePreference pref in widget.prefs) {
 | 
			
		||||
      sliders.add(
 | 
			
		||||
        Card(
 | 
			
		||||
          child: ListTile(
 | 
			
		||||
            leading: pref.icon,
 | 
			
		||||
            title: Text(pref.name),
 | 
			
		||||
            subtitle: Slider(
 | 
			
		||||
              value: pref.value.toDouble(),
 | 
			
		||||
            min: 0,
 | 
			
		||||
            max: 10,
 | 
			
		||||
            divisions: 10,
 | 
			
		||||
              min: pref.minVal.toDouble(),
 | 
			
		||||
              max: pref.maxVal.toDouble(),
 | 
			
		||||
              divisions: pref.maxVal - pref.minVal,
 | 
			
		||||
              label: pref.value.toString(),
 | 
			
		||||
              onChanged: (double newValue) {
 | 
			
		||||
                setState(() {
 | 
			
		||||
                  pref.value = newValue.toInt();
 | 
			
		||||
                _prefs.save();
 | 
			
		||||
                  pref.save();
 | 
			
		||||
                });
 | 
			
		||||
              },
 | 
			
		||||
            )
 | 
			
		||||
          ),
 | 
			
		||||
          margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
 | 
			
		||||
          shadowColor: Colors.grey,
 | 
			
		||||
      ));
 | 
			
		||||
    }
 | 
			
		||||
    return sliders;
 | 
			
		||||
        )
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Column(
 | 
			
		||||
      children: sliders);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    return Column(children: _createSliders());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,15 @@ import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
 | 
			
		||||
const LandmarkType sightseeing = LandmarkType(name: 'sightseeing');
 | 
			
		||||
const LandmarkType nature = LandmarkType(name: 'nature');
 | 
			
		||||
const LandmarkType shopping = LandmarkType(name: 'shopping');
 | 
			
		||||
// const LandmarkType museum = LandmarkType(name: 'Museum');
 | 
			
		||||
// const LandmarkType restaurant = LandmarkType(name: 'Restaurant');
 | 
			
		||||
const LandmarkType start = LandmarkType(name: 'start');
 | 
			
		||||
const LandmarkType finish = LandmarkType(name: 'finish');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
final class Landmark extends LinkedListEntry<Landmark>{
 | 
			
		||||
  // A linked node of a list of Landmarks
 | 
			
		||||
  final String uuid;
 | 
			
		||||
@@ -47,7 +56,7 @@ final class Landmark extends LinkedListEntry<Landmark>{
 | 
			
		||||
        'location': List<dynamic> location,
 | 
			
		||||
        'type': String type,
 | 
			
		||||
      }) {
 | 
			
		||||
      // refine the parsing on a few
 | 
			
		||||
      // refine the parsing on a few fields
 | 
			
		||||
      List<double> locationFixed = List<double>.from(location);
 | 
			
		||||
      // parse the rest separately, they could be missing
 | 
			
		||||
      LandmarkType typeFixed = LandmarkType(name: type);
 | 
			
		||||
@@ -55,11 +64,12 @@ final class Landmark extends LinkedListEntry<Landmark>{
 | 
			
		||||
      final imageURL = json['image_url'] as String?;
 | 
			
		||||
      final description = json['description'] as String?;
 | 
			
		||||
      var duration = Duration(minutes: json['duration'] ?? 0) as Duration?;
 | 
			
		||||
      if (duration == const Duration()) {duration = null;};
 | 
			
		||||
      // if (duration == const Duration()) {duration = null;};
 | 
			
		||||
      final visited = json['visited'] as bool?;
 | 
			
		||||
      var tripTime = Duration(minutes: json['time_to_reach_next'] ?? 0) as Duration?;
 | 
			
		||||
      
 | 
			
		||||
      return Landmark(
 | 
			
		||||
        uuid: uuid, name: name, location: locationFixed, type: typeFixed, isSecondary: isSecondary, imageURL: imageURL, description: description, duration: duration, visited: visited);
 | 
			
		||||
        uuid: uuid, name: name, location: locationFixed, type: typeFixed, isSecondary: isSecondary, imageURL: imageURL, description: description, duration: duration, visited: visited, tripTime: tripTime);
 | 
			
		||||
    } else {
 | 
			
		||||
      throw FormatException('Invalid JSON: $json');
 | 
			
		||||
    }
 | 
			
		||||
@@ -81,7 +91,8 @@ final class Landmark extends LinkedListEntry<Landmark>{
 | 
			
		||||
    'image_url': imageURL,
 | 
			
		||||
    'description': description,
 | 
			
		||||
    'duration': duration?.inMinutes,
 | 
			
		||||
    'visited': visited
 | 
			
		||||
    'visited': visited,
 | 
			
		||||
    'trip_time': tripTime?.inMinutes,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -96,6 +107,14 @@ class LandmarkType {
 | 
			
		||||
    // required this.description,
 | 
			
		||||
    // required this.icon,
 | 
			
		||||
  });
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) {
 | 
			
		||||
    if (other is LandmarkType) {
 | 
			
		||||
      return name == other.name;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
// import "package:fast_network_navigation/structs/landmark.dart";
 | 
			
		||||
 | 
			
		||||
// class Linked<Landmark> {
 | 
			
		||||
//   Landmark? head;
 | 
			
		||||
  
 | 
			
		||||
//   Linked();
 | 
			
		||||
 | 
			
		||||
//   // class methods
 | 
			
		||||
//   bool get isEmpty => head == null;
 | 
			
		||||
 | 
			
		||||
//   // Add a new node to the end of the list
 | 
			
		||||
//   void add(Landmark value) {
 | 
			
		||||
//     if (isEmpty) {
 | 
			
		||||
//       // If the list is empty, set the new node as the head
 | 
			
		||||
//       head = value;
 | 
			
		||||
//     } else {
 | 
			
		||||
//       Landmark? current = head;
 | 
			
		||||
//       while (current!.next != null) {
 | 
			
		||||
//         // Traverse the list to find the last node
 | 
			
		||||
//         current = current.next;
 | 
			
		||||
//       }
 | 
			
		||||
//       current.next = value; // Set the new node as the next node of the last node
 | 
			
		||||
//     }
 | 
			
		||||
//   }
 | 
			
		||||
 | 
			
		||||
//   // Remove the first node with the given value
 | 
			
		||||
//   void remove(Landmark value) {
 | 
			
		||||
//     if (isEmpty) return;
 | 
			
		||||
 | 
			
		||||
//     // If the value is in the head node, update the head to the next node
 | 
			
		||||
//     if (head! == value) {
 | 
			
		||||
//       head = head.next;
 | 
			
		||||
//       return;
 | 
			
		||||
//     }
 | 
			
		||||
 | 
			
		||||
//     var current = head;
 | 
			
		||||
//     while (current!.next != null) {
 | 
			
		||||
//       if (current.next! == value) {
 | 
			
		||||
//         // If the value is found in the next node, skip the next node
 | 
			
		||||
//         current.next = current.next.next;
 | 
			
		||||
//         return;
 | 
			
		||||
//       }
 | 
			
		||||
//       current = current.next;
 | 
			
		||||
//     }
 | 
			
		||||
//   }
 | 
			
		||||
// }
 | 
			
		||||
@@ -3,80 +3,100 @@ import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SinglePreference {
 | 
			
		||||
  String slug;
 | 
			
		||||
  String name;
 | 
			
		||||
  String description;
 | 
			
		||||
  int value;
 | 
			
		||||
  int minVal;
 | 
			
		||||
  int maxVal;
 | 
			
		||||
  Icon icon;
 | 
			
		||||
  String key;
 | 
			
		||||
 | 
			
		||||
  SinglePreference({
 | 
			
		||||
    required this.slug,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.description,
 | 
			
		||||
    required this.value,
 | 
			
		||||
    required this.icon,
 | 
			
		||||
    required this.key,
 | 
			
		||||
    this.minVal = 0,
 | 
			
		||||
    this.maxVal = 5,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserPreferences {
 | 
			
		||||
  List<SinglePreference> preferences = [
 | 
			
		||||
    SinglePreference(
 | 
			
		||||
      name: "Sightseeing",
 | 
			
		||||
      description: "How much do you like sightseeing?",
 | 
			
		||||
      value: 0,
 | 
			
		||||
      icon: Icon(Icons.church),
 | 
			
		||||
      key: "sightseeing",
 | 
			
		||||
    ),
 | 
			
		||||
    SinglePreference(
 | 
			
		||||
      name: "Shopping",
 | 
			
		||||
      description: "How much do you like shopping?",
 | 
			
		||||
      value: 0,
 | 
			
		||||
      icon: Icon(Icons.shopping_bag),
 | 
			
		||||
      key: "shopping",
 | 
			
		||||
    ),
 | 
			
		||||
    SinglePreference(
 | 
			
		||||
      name: "Foods & Drinks",
 | 
			
		||||
      description: "How much do you like eating?",
 | 
			
		||||
      value: 0,
 | 
			
		||||
      icon: Icon(Icons.restaurant),
 | 
			
		||||
      key: "eating",
 | 
			
		||||
    ),
 | 
			
		||||
    SinglePreference(
 | 
			
		||||
      name: "Nightlife",
 | 
			
		||||
      description: "How much do you like nightlife?",
 | 
			
		||||
      value: 0,
 | 
			
		||||
      icon: Icon(Icons.wine_bar),
 | 
			
		||||
      key: "nightlife",
 | 
			
		||||
    ),
 | 
			
		||||
    SinglePreference(
 | 
			
		||||
      name: "Nature",
 | 
			
		||||
      description: "How much do you like nature?",
 | 
			
		||||
      value: 0,
 | 
			
		||||
      icon: Icon(Icons.landscape),
 | 
			
		||||
      key: "nature",
 | 
			
		||||
    ),
 | 
			
		||||
    SinglePreference(
 | 
			
		||||
      name: "Culture",
 | 
			
		||||
      description: "How much do you like culture?",
 | 
			
		||||
      value: 0,
 | 
			
		||||
      icon: Icon(Icons.palette),
 | 
			
		||||
      key: "culture",
 | 
			
		||||
    ),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  void save() async {
 | 
			
		||||
    SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
 | 
			
		||||
    for (SinglePreference pref in preferences) {
 | 
			
		||||
      sharedPrefs.setInt(pref.key, pref.value);
 | 
			
		||||
    }
 | 
			
		||||
      sharedPrefs.setInt('pref_$slug', value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void load() async {
 | 
			
		||||
    SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
 | 
			
		||||
    for (SinglePreference pref in preferences) {
 | 
			
		||||
      pref.value = sharedPrefs.getInt(pref.key) ?? 0;
 | 
			
		||||
      value = sharedPrefs.getInt('pref_$slug') ?? minVal;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserPreferences {
 | 
			
		||||
  SinglePreference sightseeing = SinglePreference(
 | 
			
		||||
    name: "Sightseeing",
 | 
			
		||||
    slug: "sightseeing",
 | 
			
		||||
    description: "How much do you like sightseeing?",
 | 
			
		||||
    value: 0,
 | 
			
		||||
    icon: Icon(Icons.church),
 | 
			
		||||
  );
 | 
			
		||||
  SinglePreference shopping = SinglePreference(
 | 
			
		||||
    name: "Shopping",
 | 
			
		||||
    slug: "shopping",
 | 
			
		||||
    description: "How much do you like shopping?",
 | 
			
		||||
    value: 0,
 | 
			
		||||
    icon: Icon(Icons.shopping_bag),
 | 
			
		||||
  );
 | 
			
		||||
  SinglePreference nature = SinglePreference(
 | 
			
		||||
    name: "Nature",
 | 
			
		||||
    slug: "nature",
 | 
			
		||||
    description: "How much do you like nature?",
 | 
			
		||||
    value: 0,
 | 
			
		||||
    icon: Icon(Icons.landscape),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  SinglePreference maxTime = SinglePreference(
 | 
			
		||||
    name: "Trip duration",
 | 
			
		||||
    slug: "duration",
 | 
			
		||||
    description: "How long do you want your trip to be?",
 | 
			
		||||
    value: 30,
 | 
			
		||||
    minVal: 30,
 | 
			
		||||
    maxVal: 720,
 | 
			
		||||
    icon: Icon(Icons.timer),
 | 
			
		||||
  );
 | 
			
		||||
  SinglePreference maxDetour = SinglePreference(
 | 
			
		||||
    name: "Trip detours",
 | 
			
		||||
    slug: "detours",
 | 
			
		||||
    description: "Are you okay with roaming even if makes the trip longer?",
 | 
			
		||||
    value: 0,
 | 
			
		||||
    maxVal: 30,
 | 
			
		||||
    icon: Icon(Icons.loupe_sharp),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  Future<void> load() async {
 | 
			
		||||
    for (SinglePreference pref in [sightseeing, shopping, nature, maxTime, maxDetour]) {
 | 
			
		||||
      pref.load();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
      // This is "opinionated" JSON, corresponding to the backend's expectations
 | 
			
		||||
      return {
 | 
			
		||||
        "sightseeing": {"type": "sightseeing", "score": sightseeing.value},
 | 
			
		||||
        "shopping": {"type": "shopping", "score": shopping.value},
 | 
			
		||||
        "nature": {"type": "nature", "score": nature.value},
 | 
			
		||||
        "max_time_minute": maxTime.value,
 | 
			
		||||
        "detour_tolerance_minute": maxDetour.value
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Future<UserPreferences> loadUserPreferences() async {
 | 
			
		||||
  UserPreferences prefs = UserPreferences();
 | 
			
		||||
  await prefs.load();
 | 
			
		||||
  return prefs;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
import "package:fast_network_navigation/structs/landmark.dart";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Route {
 | 
			
		||||
  final String name;
 | 
			
		||||
  final Duration duration;
 | 
			
		||||
  final List<Landmark> landmarks;
 | 
			
		||||
 | 
			
		||||
  Route({
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.duration,
 | 
			
		||||
    required this.landmarks
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -4,32 +4,72 @@
 | 
			
		||||
import 'dart:collection';
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/structs/landmark.dart';
 | 
			
		||||
import 'package:anyway/structs/landmark.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:geocoding/geocoding.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
 | 
			
		||||
class Trip {
 | 
			
		||||
  final String uuid;
 | 
			
		||||
  final String cityName;
 | 
			
		||||
  // TODO: cityName should be inferred from coordinates of the Landmarks
 | 
			
		||||
  final LinkedList<Landmark> landmarks;
 | 
			
		||||
class Trip with ChangeNotifier {
 | 
			
		||||
  String uuid;
 | 
			
		||||
  int totalTime;
 | 
			
		||||
  LinkedList<Landmark> landmarks;
 | 
			
		||||
  // could be empty as well
 | 
			
		||||
  String? errorDescription;
 | 
			
		||||
 | 
			
		||||
  Future<String> get cityName async {
 | 
			
		||||
    List<double>? location = landmarks.firstOrNull?.location; 
 | 
			
		||||
    if (GeocodingPlatform.instance == null) {
 | 
			
		||||
      return '$location';
 | 
			
		||||
    } else if (location == null) {
 | 
			
		||||
      return 'Unknown';
 | 
			
		||||
    } else{
 | 
			
		||||
      List<Placemark> placemarks = await placemarkFromCoordinates(location[0], location[1]);
 | 
			
		||||
      return placemarks.first.locality ?? 'Unknown';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  Trip({
 | 
			
		||||
    required this.uuid,
 | 
			
		||||
    required this.cityName,
 | 
			
		||||
    required this.landmarks,
 | 
			
		||||
  });
 | 
			
		||||
    this.uuid = 'pending',
 | 
			
		||||
    this.totalTime = 0,
 | 
			
		||||
    LinkedList<Landmark>? landmarks
 | 
			
		||||
    // a trip can be created with no landmarks, but the list should be initialized anyway
 | 
			
		||||
  }) : landmarks = landmarks ?? LinkedList<Landmark>();
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  factory Trip.fromJson(Map<String, dynamic> json) {
 | 
			
		||||
    return Trip(
 | 
			
		||||
    Trip trip = Trip(
 | 
			
		||||
      uuid: json['uuid'],
 | 
			
		||||
      cityName: json['city_name'],
 | 
			
		||||
      landmarks: LinkedList()
 | 
			
		||||
      totalTime: json['total_time'],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return trip;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void loadFromJson(Map<String, dynamic> json) {
 | 
			
		||||
    uuid = json['uuid'];
 | 
			
		||||
    totalTime = json['total_time'];
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
  void addLandmark(Landmark landmark) {
 | 
			
		||||
    landmarks.add(landmark);
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void updateUUID(String newUUID) {
 | 
			
		||||
    uuid = newUUID;
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void removeLandmark(Landmark landmark) {
 | 
			
		||||
    landmarks.remove(landmark);
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  void updateError(String error) {
 | 
			
		||||
    errorDescription = error;
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  factory Trip.fromPrefs(SharedPreferences prefs, String uuid) {
 | 
			
		||||
    String? content = prefs.getString('trip_$uuid');
 | 
			
		||||
@@ -43,8 +83,8 @@ class Trip {
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => {
 | 
			
		||||
    'uuid': uuid,
 | 
			
		||||
    'city_name': cityName,
 | 
			
		||||
    'entry_uuid': landmarks.first?.uuid ?? ''
 | 
			
		||||
    'total_time': totalTime,
 | 
			
		||||
    'first_landmark_uuid': landmarks.first.uuid
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,52 +0,0 @@
 | 
			
		||||
// import "package:fast_network_navigation/structs/landmark.dart";
 | 
			
		||||
// import 'package:http/http.dart' as http;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Future<List<Landmark>> fetchLandmarks() async {
 | 
			
		||||
//   // final response = await http
 | 
			
		||||
//   //     .get(Uri.parse('https://nav.kluster.moll.re/v1/destination/1'));
 | 
			
		||||
 | 
			
		||||
//   // if (response.statusCode == 200) {
 | 
			
		||||
//     // If the server did return a 200 OK response,
 | 
			
		||||
//     // then parse the JSON.
 | 
			
		||||
//     List<Landmark> landmarks = [
 | 
			
		||||
//       // 48°51′29.6″N 2°17′40.2″E
 | 
			
		||||
//       Landmark(
 | 
			
		||||
//         name: "Eiffel Tower",
 | 
			
		||||
//         location: [48.51296, 2.17402],
 | 
			
		||||
//         type: LandmarkType(name: "Tower"),
 | 
			
		||||
//         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg"
 | 
			
		||||
//         ),
 | 
			
		||||
//       Landmark(
 | 
			
		||||
//         name: "Notre Dame Cathedral",
 | 
			
		||||
//         location: [48.8530, 2.3498],
 | 
			
		||||
//         type: LandmarkType(name: "Monument"),
 | 
			
		||||
//         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Notre-Dame_de_Paris%2C_4_October_2017.jpg/440px-Notre-Dame_de_Paris%2C_4_October_2017.jpg"
 | 
			
		||||
//         ),
 | 
			
		||||
//       Landmark(
 | 
			
		||||
//         name: "Louvre palace",
 | 
			
		||||
//         location: [48.8606, 2.3376],
 | 
			
		||||
//         type: LandmarkType(name: "Museum"),
 | 
			
		||||
//         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Louvre_Museum_Wikimedia_Commons.jpg/540px-Louvre_Museum_Wikimedia_Commons.jpg"
 | 
			
		||||
//         ),
 | 
			
		||||
//       Landmark(
 | 
			
		||||
//         name: "Pont-des-arts",
 | 
			
		||||
//         location: [48.5130, 2.2015],
 | 
			
		||||
//         type: LandmarkType(name: "Bridge"),
 | 
			
		||||
//         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg/560px-Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg"),
 | 
			
		||||
//       Landmark(
 | 
			
		||||
//         name: "Panthéon",
 | 
			
		||||
//         location: [48.5046, 2.2046],
 | 
			
		||||
//         type: LandmarkType(name: "Monument"),
 | 
			
		||||
//         imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Pantheon_of_Paris_007.JPG/1280px-Pantheon_of_Paris_007.JPG"
 | 
			
		||||
//         ),
 | 
			
		||||
//     ];
 | 
			
		||||
//     // sleep 10 seconds
 | 
			
		||||
//     await Future.delayed(Duration(seconds: 5));
 | 
			
		||||
//     return landmarks;
 | 
			
		||||
//   // } else {
 | 
			
		||||
//   //   // If the server did not return a 200 OK response,
 | 
			
		||||
//   //   // then throw an exception.
 | 
			
		||||
//   //   throw Exception('Failed to load destination');
 | 
			
		||||
//   // }
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										93
									
								
								frontend/lib/utils/fetch_trip.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								frontend/lib/utils/fetch_trip.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
import "dart:convert";
 | 
			
		||||
import "dart:developer";
 | 
			
		||||
import 'package:dio/dio.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:anyway/constants.dart';
 | 
			
		||||
import "package:anyway/structs/landmark.dart";
 | 
			
		||||
import "package:anyway/structs/trip.dart";
 | 
			
		||||
import "package:anyway/structs/preferences.dart";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Dio dio = Dio(
 | 
			
		||||
    BaseOptions(
 | 
			
		||||
      baseUrl: API_URL_BASE,
 | 
			
		||||
      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',
 | 
			
		||||
      //   'api': '1.0.0',
 | 
			
		||||
      // },
 | 
			
		||||
      contentType: Headers.jsonContentType,
 | 
			
		||||
      responseType: ResponseType.json,
 | 
			
		||||
        
 | 
			
		||||
  ),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
fetchTrip(
 | 
			
		||||
  Trip trip,
 | 
			
		||||
  Future<UserPreferences> preferences,
 | 
			
		||||
) async {
 | 
			
		||||
  UserPreferences prefs = await preferences;
 | 
			
		||||
  Map<String, dynamic> data = {
 | 
			
		||||
    "preferences": prefs.toJson(),
 | 
			
		||||
    "start": trip.landmarks!.first.location,
 | 
			
		||||
  };
 | 
			
		||||
  String dataString = jsonEncode(data);
 | 
			
		||||
  log(dataString);
 | 
			
		||||
 | 
			
		||||
  final response = await dio.post(
 | 
			
		||||
    "/trip/new",
 | 
			
		||||
    data: data
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // handle errors
 | 
			
		||||
  if (response.statusCode != 200) {
 | 
			
		||||
    trip.updateUUID("error");
 | 
			
		||||
    if (response.data["detail"] != null) {
 | 
			
		||||
      trip.updateError(response.data["detail"]);
 | 
			
		||||
      log(response.data["detail"]);
 | 
			
		||||
      // throw Exception(response.data["detail"]);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    Map<String, dynamic> json = response.data;
 | 
			
		||||
 | 
			
		||||
    // only fill in the trip "meta" data for now
 | 
			
		||||
    trip.loadFromJson(json);
 | 
			
		||||
 | 
			
		||||
    // now fill the trip with landmarks
 | 
			
		||||
    // 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) {
 | 
			
		||||
      var (landmark, newUUID) = await fetchLandmark(nextUUID);
 | 
			
		||||
      trip.addLandmark(landmark);
 | 
			
		||||
      nextUUID = newUUID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  log(response.data.toString());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Future<(Landmark, String?)> fetchLandmark(String uuid) async {
 | 
			
		||||
  final response = await dio.get(
 | 
			
		||||
    "/landmark/$uuid"
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // handle errors
 | 
			
		||||
  if (response.statusCode != 200) {
 | 
			
		||||
    throw Exception('Failed to load landmark');
 | 
			
		||||
  }
 | 
			
		||||
  if (response.data["detail"] != null) {
 | 
			
		||||
    throw Exception(response.data["detail"]);
 | 
			
		||||
  }
 | 
			
		||||
  log(response.data.toString());
 | 
			
		||||
  Map<String, dynamic> json = response.data;
 | 
			
		||||
  String? nextUUID = json["next_uuid"];
 | 
			
		||||
  return (Landmark.fromJson(json), nextUUID);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import 'dart:collection';
 | 
			
		||||
 | 
			
		||||
import 'package:fast_network_navigation/structs/trip.dart';
 | 
			
		||||
import 'package:fast_network_navigation/structs/landmark.dart';
 | 
			
		||||
import 'package:anyway/structs/trip.dart';
 | 
			
		||||
import 'package:anyway/structs/landmark.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
 | 
			
		||||
Future<List<Trip>> loadTrips() async {
 | 
			
		||||
@@ -17,7 +17,7 @@ Future<List<Trip>> loadTrips() async {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (trips.isEmpty) {
 | 
			
		||||
    Trip t1 = Trip(uuid: '1', cityName: 'Paris', landmarks: LinkedList<Landmark>());
 | 
			
		||||
    Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>());
 | 
			
		||||
    t1.landmarks.add(
 | 
			
		||||
      Landmark(
 | 
			
		||||
        uuid: '1',
 | 
			
		||||
@@ -66,7 +66,7 @@ Future<List<Trip>> loadTrips() async {
 | 
			
		||||
    trips.add(t1);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    Trip t2 = Trip(uuid: '2', cityName: 'Vienna', landmarks: LinkedList<Landmark>());
 | 
			
		||||
    Trip t2 = Trip(uuid: '2', landmarks: LinkedList<Landmark>());
 | 
			
		||||
 | 
			
		||||
    t2.landmarks.add(
 | 
			
		||||
      Landmark(
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,10 @@ project(runner LANGUAGES CXX)
 | 
			
		||||
 | 
			
		||||
# The name of the executable created for the application. Change this to change
 | 
			
		||||
# the on-disk name of your application.
 | 
			
		||||
set(BINARY_NAME "fast_network_navigation")
 | 
			
		||||
set(BINARY_NAME "anyway")
 | 
			
		||||
# The unique GTK application identifier for this application. See:
 | 
			
		||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
 | 
			
		||||
set(APPLICATION_ID "com.example.fast_network_navigation")
 | 
			
		||||
set(APPLICATION_ID "com.example.anyway")
 | 
			
		||||
 | 
			
		||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
 | 
			
		||||
# versions of CMake.
 | 
			
		||||
 
 | 
			
		||||
@@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
 | 
			
		||||
  if (use_header_bar) {
 | 
			
		||||
    GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
 | 
			
		||||
    gtk_widget_show(GTK_WIDGET(header_bar));
 | 
			
		||||
    gtk_header_bar_set_title(header_bar, "fast_network_navigation");
 | 
			
		||||
    gtk_header_bar_set_title(header_bar, "anyway");
 | 
			
		||||
    gtk_header_bar_set_show_close_button(header_bar, TRUE);
 | 
			
		||||
    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
 | 
			
		||||
  } else {
 | 
			
		||||
    gtk_window_set_title(window, "fast_network_navigation");
 | 
			
		||||
    gtk_window_set_title(window, "anyway");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  gtk_window_set_default_size(window, 1280, 720);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,12 @@
 | 
			
		||||
import FlutterMacOS
 | 
			
		||||
import Foundation
 | 
			
		||||
 | 
			
		||||
import path_provider_foundation
 | 
			
		||||
import shared_preferences_foundation
 | 
			
		||||
import sqflite
 | 
			
		||||
 | 
			
		||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
 | 
			
		||||
  PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
 | 
			
		||||
  SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
 | 
			
		||||
  SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@
 | 
			
		||||
		331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
 | 
			
		||||
		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
 | 
			
		||||
		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
 | 
			
		||||
		33CC10ED2044A3C60003C045 /* fast_network_navigation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "fast_network_navigation.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 | 
			
		||||
		33CC10ED2044A3C60003C045 /* anyway.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "anyway.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 | 
			
		||||
		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 | 
			
		||||
		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
 | 
			
		||||
		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
 | 
			
		||||
@@ -131,7 +131,7 @@
 | 
			
		||||
		33CC10EE2044A3C60003C045 /* Products */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				33CC10ED2044A3C60003C045 /* fast_network_navigation.app */,
 | 
			
		||||
				33CC10ED2044A3C60003C045 /* anyway.app */,
 | 
			
		||||
				331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
 | 
			
		||||
			);
 | 
			
		||||
			name = Products;
 | 
			
		||||
@@ -217,7 +217,7 @@
 | 
			
		||||
			);
 | 
			
		||||
			name = Runner;
 | 
			
		||||
			productName = Runner;
 | 
			
		||||
			productReference = 33CC10ED2044A3C60003C045 /* fast_network_navigation.app */;
 | 
			
		||||
			productReference = 33CC10ED2044A3C60003C045 /* anyway.app */;
 | 
			
		||||
			productType = "com.apple.product-type.application";
 | 
			
		||||
		};
 | 
			
		||||
/* End PBXNativeTarget section */
 | 
			
		||||
@@ -388,7 +388,7 @@
 | 
			
		||||
				PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests;
 | 
			
		||||
				PRODUCT_NAME = "$(TARGET_NAME)";
 | 
			
		||||
				SWIFT_VERSION = 5.0;
 | 
			
		||||
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fast_network_navigation.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fast_network_navigation";
 | 
			
		||||
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/anyway.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/anyway";
 | 
			
		||||
			};
 | 
			
		||||
			name = Debug;
 | 
			
		||||
		};
 | 
			
		||||
@@ -402,7 +402,7 @@
 | 
			
		||||
				PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests;
 | 
			
		||||
				PRODUCT_NAME = "$(TARGET_NAME)";
 | 
			
		||||
				SWIFT_VERSION = 5.0;
 | 
			
		||||
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fast_network_navigation.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fast_network_navigation";
 | 
			
		||||
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/anyway.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/anyway";
 | 
			
		||||
			};
 | 
			
		||||
			name = Release;
 | 
			
		||||
		};
 | 
			
		||||
@@ -416,7 +416,7 @@
 | 
			
		||||
				PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests;
 | 
			
		||||
				PRODUCT_NAME = "$(TARGET_NAME)";
 | 
			
		||||
				SWIFT_VERSION = 5.0;
 | 
			
		||||
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/fast_network_navigation.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/fast_network_navigation";
 | 
			
		||||
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/anyway.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/anyway";
 | 
			
		||||
			};
 | 
			
		||||
			name = Profile;
 | 
			
		||||
		};
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
            <BuildableReference
 | 
			
		||||
               BuildableIdentifier = "primary"
 | 
			
		||||
               BlueprintIdentifier = "33CC10EC2044A3C60003C045"
 | 
			
		||||
               BuildableName = "fast_network_navigation.app"
 | 
			
		||||
               BuildableName = "anyway.app"
 | 
			
		||||
               BlueprintName = "Runner"
 | 
			
		||||
               ReferencedContainer = "container:Runner.xcodeproj">
 | 
			
		||||
            </BuildableReference>
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
         <BuildableReference
 | 
			
		||||
            BuildableIdentifier = "primary"
 | 
			
		||||
            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
 | 
			
		||||
            BuildableName = "fast_network_navigation.app"
 | 
			
		||||
            BuildableName = "anyway.app"
 | 
			
		||||
            BlueprintName = "Runner"
 | 
			
		||||
            ReferencedContainer = "container:Runner.xcodeproj">
 | 
			
		||||
         </BuildableReference>
 | 
			
		||||
@@ -65,7 +65,7 @@
 | 
			
		||||
         <BuildableReference
 | 
			
		||||
            BuildableIdentifier = "primary"
 | 
			
		||||
            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
 | 
			
		||||
            BuildableName = "fast_network_navigation.app"
 | 
			
		||||
            BuildableName = "anyway.app"
 | 
			
		||||
            BlueprintName = "Runner"
 | 
			
		||||
            ReferencedContainer = "container:Runner.xcodeproj">
 | 
			
		||||
         </BuildableReference>
 | 
			
		||||
@@ -82,7 +82,7 @@
 | 
			
		||||
         <BuildableReference
 | 
			
		||||
            BuildableIdentifier = "primary"
 | 
			
		||||
            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
 | 
			
		||||
            BuildableName = "fast_network_navigation.app"
 | 
			
		||||
            BuildableName = "anyway.app"
 | 
			
		||||
            BlueprintName = "Runner"
 | 
			
		||||
            ReferencedContainer = "container:Runner.xcodeproj">
 | 
			
		||||
         </BuildableReference>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
// 'flutter create' template.
 | 
			
		||||
 | 
			
		||||
// The application's name. By default this is also the title of the Flutter window.
 | 
			
		||||
PRODUCT_NAME = fast_network_navigation
 | 
			
		||||
PRODUCT_NAME = anyway
 | 
			
		||||
 | 
			
		||||
// The application's bundle identifier
 | 
			
		||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.11.0"
 | 
			
		||||
  auto_size_text:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: auto_size_text
 | 
			
		||||
      sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.0"
 | 
			
		||||
  boolean_selector:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -17,6 +25,30 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.1"
 | 
			
		||||
  cached_network_image:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: cached_network_image
 | 
			
		||||
      sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.4.0"
 | 
			
		||||
  cached_network_image_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: cached_network_image_platform_interface
 | 
			
		||||
      sha256: ff0c949e323d2a1b52be73acce5b4a7b04063e61414c8ca542dbba47281630a7
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "4.1.0"
 | 
			
		||||
  cached_network_image_web:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: cached_network_image_web
 | 
			
		||||
      sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.3.0"
 | 
			
		||||
  characters:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -41,6 +73,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.18.0"
 | 
			
		||||
  crypto:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: crypto
 | 
			
		||||
      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.3"
 | 
			
		||||
  csslib:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -57,6 +97,22 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.8"
 | 
			
		||||
  dio:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: dio
 | 
			
		||||
      sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "5.5.0+1"
 | 
			
		||||
  dio_web_adapter:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: dio_web_adapter
 | 
			
		||||
      sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.1"
 | 
			
		||||
  fake_async:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -81,11 +137,27 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "7.0.0"
 | 
			
		||||
  fixnum:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: fixnum
 | 
			
		||||
      sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.1.0"
 | 
			
		||||
  flutter:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description: flutter
 | 
			
		||||
    source: sdk
 | 
			
		||||
    version: "0.0.0"
 | 
			
		||||
  flutter_cache_manager:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: flutter_cache_manager
 | 
			
		||||
      sha256: a77f77806a790eb9ba0118a5a3a936e81c4fea2b61533033b2b0c3d50bbde5ea
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.4.0"
 | 
			
		||||
  flutter_lints:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -98,10 +170,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: flutter_plugin_android_lifecycle
 | 
			
		||||
      sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
 | 
			
		||||
      sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.19"
 | 
			
		||||
    version: "2.0.21"
 | 
			
		||||
  flutter_test:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description: flutter
 | 
			
		||||
@@ -112,54 +184,86 @@ packages:
 | 
			
		||||
    description: flutter
 | 
			
		||||
    source: sdk
 | 
			
		||||
    version: "0.0.0"
 | 
			
		||||
  geocoding:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: geocoding
 | 
			
		||||
      sha256: d580c801cba9386b4fac5047c4c785a4e19554f46be42f4f5e5b7deacd088a66
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.0"
 | 
			
		||||
  geocoding_android:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: geocoding_android
 | 
			
		||||
      sha256: "1b13eca79b11c497c434678fed109c2be020b158cec7512c848c102bc7232603"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.3.1"
 | 
			
		||||
  geocoding_ios:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: geocoding_ios
 | 
			
		||||
      sha256: "94ddba60387501bd1c11e18dca7c5a9e8c645d6e3da9c38b9762434941870c24"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.1"
 | 
			
		||||
  geocoding_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: geocoding_platform_interface
 | 
			
		||||
      sha256: "8c2c8226e5c276594c2e18bfe88b19110ed770aeb7c1ab50ede570be8b92229b"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.2.0"
 | 
			
		||||
  google_maps:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: google_maps
 | 
			
		||||
      sha256: "47eef3836b49bb030d5cb3afc60b8451408bf34cf753e571b645d6529eb4251a"
 | 
			
		||||
      sha256: "463b38e5a92a05cde41220a11fd5eef3847031fef3e8cf295ac76ec453246907"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "7.1.0"
 | 
			
		||||
    version: "8.0.0"
 | 
			
		||||
  google_maps_flutter:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: google_maps_flutter
 | 
			
		||||
      sha256: c1972cbad779bc5346c49045f26ae45550a0958b1cbca5b524dd3c8954995d28
 | 
			
		||||
      sha256: acf0ec482d86b2ac55ade80597ce7f797a47971f5210ebfd030f0d58130e0a94
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.6.1"
 | 
			
		||||
    version: "2.7.0"
 | 
			
		||||
  google_maps_flutter_android:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: google_maps_flutter_android
 | 
			
		||||
      sha256: "0bcadb80eba39afda77dede89a6caafd3b68f2786b90491eceea4a01c3db181c"
 | 
			
		||||
      sha256: "5d444f4135559488d7ea325eae710ae3284e6951b1b61729a0ac026456fe1548"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.8.0"
 | 
			
		||||
    version: "2.12.1"
 | 
			
		||||
  google_maps_flutter_ios:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: google_maps_flutter_ios
 | 
			
		||||
      sha256: e5132d17f051600d90d79d9f574b177c24231da702453a036db2490f9ced4646
 | 
			
		||||
      sha256: a6e3c6ecdda6c985053f944be13a0645ebb919da2ef0f5bc579c5e1670a5b2a8
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.6.0"
 | 
			
		||||
    version: "2.10.0"
 | 
			
		||||
  google_maps_flutter_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: google_maps_flutter_platform_interface
 | 
			
		||||
      sha256: "167af879da4d004cd58771f1469b91dcc3b9b0a2c5334cc6bf71fd41d4b35403"
 | 
			
		||||
      sha256: bd60ca330e3c7763b95b477054adec338a522d982af73ecc520b232474063ac5
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.6.0"
 | 
			
		||||
    version: "2.8.0"
 | 
			
		||||
  google_maps_flutter_web:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: google_maps_flutter_web
 | 
			
		||||
      sha256: "0c0d5c723d94b295cf86dd1c45ff91d2ac1fff7c05ddca4f01bef9fa0a014690"
 | 
			
		||||
      sha256: "8d5d0f58bfc4afac0bbe3d399f2018fcea691e3ea3d35254b7aae56df5827659"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.5.7"
 | 
			
		||||
    version: "0.5.9+1"
 | 
			
		||||
  html:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -172,10 +276,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: http
 | 
			
		||||
      sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
 | 
			
		||||
      sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.2.1"
 | 
			
		||||
    version: "1.2.2"
 | 
			
		||||
  http_parser:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -184,38 +288,22 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "4.0.2"
 | 
			
		||||
  js:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: js
 | 
			
		||||
      sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.6.7"
 | 
			
		||||
  js_wrapping:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: js_wrapping
 | 
			
		||||
      sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.7.4"
 | 
			
		||||
  leak_tracker:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: leak_tracker
 | 
			
		||||
      sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
 | 
			
		||||
      sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "10.0.4"
 | 
			
		||||
    version: "10.0.5"
 | 
			
		||||
  leak_tracker_flutter_testing:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: leak_tracker_flutter_testing
 | 
			
		||||
      sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
 | 
			
		||||
      sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.3"
 | 
			
		||||
    version: "3.0.5"
 | 
			
		||||
  leak_tracker_testing:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -244,18 +332,34 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: material_color_utilities
 | 
			
		||||
      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
 | 
			
		||||
      sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.8.0"
 | 
			
		||||
    version: "0.11.1"
 | 
			
		||||
  meta:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: meta
 | 
			
		||||
      sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
 | 
			
		||||
      sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.12.0"
 | 
			
		||||
    version: "1.15.0"
 | 
			
		||||
  nested:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: nested
 | 
			
		||||
      sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.0"
 | 
			
		||||
  octo_image:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: octo_image
 | 
			
		||||
      sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.0"
 | 
			
		||||
  path:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -264,6 +368,30 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.9.0"
 | 
			
		||||
  path_provider:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: path_provider
 | 
			
		||||
      sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.4"
 | 
			
		||||
  path_provider_android:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: path_provider_android
 | 
			
		||||
      sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.9"
 | 
			
		||||
  path_provider_foundation:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: path_provider_foundation
 | 
			
		||||
      sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.4.0"
 | 
			
		||||
  path_provider_linux:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -284,18 +412,18 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: path_provider_windows
 | 
			
		||||
      sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
 | 
			
		||||
      sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.1"
 | 
			
		||||
    version: "2.3.0"
 | 
			
		||||
  platform:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: platform
 | 
			
		||||
      sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
 | 
			
		||||
      sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.1.4"
 | 
			
		||||
    version: "3.1.5"
 | 
			
		||||
  plugin_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -304,6 +432,22 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.8"
 | 
			
		||||
  provider:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: provider
 | 
			
		||||
      sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.1.2"
 | 
			
		||||
  rxdart:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: rxdart
 | 
			
		||||
      sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.28.0"
 | 
			
		||||
  sanitize_html:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -316,58 +460,58 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences
 | 
			
		||||
      sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
 | 
			
		||||
      sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.3"
 | 
			
		||||
    version: "2.3.0"
 | 
			
		||||
  shared_preferences_android:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences_android
 | 
			
		||||
      sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
 | 
			
		||||
      sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.2"
 | 
			
		||||
    version: "2.3.0"
 | 
			
		||||
  shared_preferences_foundation:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences_foundation
 | 
			
		||||
      sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
 | 
			
		||||
      sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.4.0"
 | 
			
		||||
    version: "2.5.0"
 | 
			
		||||
  shared_preferences_linux:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences_linux
 | 
			
		||||
      sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
 | 
			
		||||
      sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.3.2"
 | 
			
		||||
    version: "2.4.0"
 | 
			
		||||
  shared_preferences_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences_platform_interface
 | 
			
		||||
      sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
 | 
			
		||||
      sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.3.2"
 | 
			
		||||
    version: "2.4.1"
 | 
			
		||||
  shared_preferences_web:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences_web
 | 
			
		||||
      sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
 | 
			
		||||
      sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.3.0"
 | 
			
		||||
    version: "2.4.0"
 | 
			
		||||
  shared_preferences_windows:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences_windows
 | 
			
		||||
      sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
 | 
			
		||||
      sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.3.2"
 | 
			
		||||
    version: "2.4.0"
 | 
			
		||||
  sky_engine:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description: flutter
 | 
			
		||||
@@ -389,6 +533,30 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.10.0"
 | 
			
		||||
  sprintf:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: sprintf
 | 
			
		||||
      sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "7.0.0"
 | 
			
		||||
  sqflite:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: sqflite
 | 
			
		||||
      sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.3.3+1"
 | 
			
		||||
  sqflite_common:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: sqflite_common
 | 
			
		||||
      sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.5.4"
 | 
			
		||||
  stack_trace:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -421,6 +589,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.2.0"
 | 
			
		||||
  synchronized:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: synchronized
 | 
			
		||||
      sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.1.0+1"
 | 
			
		||||
  term_glyph:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -433,10 +609,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: test_api
 | 
			
		||||
      sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
 | 
			
		||||
      sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.7.0"
 | 
			
		||||
    version: "0.7.2"
 | 
			
		||||
  typed_data:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -445,6 +621,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.3.2"
 | 
			
		||||
  uuid:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: uuid
 | 
			
		||||
      sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "4.4.2"
 | 
			
		||||
  vector_math:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -457,10 +641,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: vm_service
 | 
			
		||||
      sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
 | 
			
		||||
      sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "14.2.1"
 | 
			
		||||
    version: "14.2.4"
 | 
			
		||||
  web:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -469,14 +653,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.5.1"
 | 
			
		||||
  win32:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
  widget_to_marker:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: win32
 | 
			
		||||
      sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
 | 
			
		||||
      name: widget_to_marker
 | 
			
		||||
      sha256: badc36f23c76f3ca9d43d7780058096be774adf0f661bdb6eb6f6b893f648ab9
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "5.5.1"
 | 
			
		||||
    version: "1.0.6"
 | 
			
		||||
  xdg_directories:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -487,4 +671,4 @@ packages:
 | 
			
		||||
    version: "1.0.4"
 | 
			
		||||
sdks:
 | 
			
		||||
  dart: ">=3.4.0 <4.0.0"
 | 
			
		||||
  flutter: ">=3.19.0"
 | 
			
		||||
  flutter: ">=3.22.0"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
name: "fast_network_navigation"
 | 
			
		||||
description: "An interactive city navigator."
 | 
			
		||||
name: "anyway"
 | 
			
		||||
description: "A customizable, agile city navigator for your trips."
 | 
			
		||||
# The following line prevents the package from being accidentally published to
 | 
			
		||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
 | 
			
		||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 | 
			
		||||
@@ -36,9 +36,15 @@ dependencies:
 | 
			
		||||
  # Use with the CupertinoIcons class for iOS style icons.
 | 
			
		||||
  cupertino_icons: ^1.0.6
 | 
			
		||||
  sliding_up_panel: ^2.0.0+1
 | 
			
		||||
  google_maps_flutter: ^2.6.1
 | 
			
		||||
  http: ^1.2.1
 | 
			
		||||
  shared_preferences: ^2.2.3
 | 
			
		||||
  dio: ^5.5.0+1
 | 
			
		||||
  google_maps_flutter: ^2.7.0
 | 
			
		||||
  cached_network_image: ^3.4.0
 | 
			
		||||
  geocoding: ^3.0.0
 | 
			
		||||
  widget_to_marker: ^1.0.6
 | 
			
		||||
  provider: ^6.1.2
 | 
			
		||||
  auto_size_text: ^3.0.0
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
 | 
			
		||||
// import 'package:fast_network_navigation/main.dart';
 | 
			
		||||
import 'package:fast_network_navigation/layout.dart';
 | 
			
		||||
// import 'package:anyway/main.dart';
 | 
			
		||||
import 'package:anyway/layout.dart';
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,13 +24,13 @@
 | 
			
		||||
  <!-- iOS meta tags & icons -->
 | 
			
		||||
  <meta name="apple-mobile-web-app-capable" content="yes">
 | 
			
		||||
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
 | 
			
		||||
  <meta name="apple-mobile-web-app-title" content="fast_network_navigation">
 | 
			
		||||
  <meta name="apple-mobile-web-app-title" content="anyway">
 | 
			
		||||
  <link rel="apple-touch-icon" href="icons/Icon-192.png">
 | 
			
		||||
 | 
			
		||||
  <!-- Favicon -->
 | 
			
		||||
  <link rel="icon" type="image/png" href="favicon.png"/>
 | 
			
		||||
 | 
			
		||||
  <title>fast_network_navigation</title>
 | 
			
		||||
  <title>anyway</title>
 | 
			
		||||
  <link rel="manifest" href="manifest.json">
 | 
			
		||||
 | 
			
		||||
  <script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "fast_network_navigation",
 | 
			
		||||
    "short_name": "fast_network_navigation",
 | 
			
		||||
    "name": "anyway",
 | 
			
		||||
    "short_name": "anyway",
 | 
			
		||||
    "start_url": ".",
 | 
			
		||||
    "display": "standalone",
 | 
			
		||||
    "background_color": "#0175C2",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
# Project-level configuration.
 | 
			
		||||
cmake_minimum_required(VERSION 3.14)
 | 
			
		||||
project(fast_network_navigation LANGUAGES CXX)
 | 
			
		||||
project(anyway LANGUAGES CXX)
 | 
			
		||||
 | 
			
		||||
# The name of the executable created for the application. Change this to change
 | 
			
		||||
# the on-disk name of your application.
 | 
			
		||||
set(BINARY_NAME "fast_network_navigation")
 | 
			
		||||
set(BINARY_NAME "anyway")
 | 
			
		||||
 | 
			
		||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
 | 
			
		||||
# versions of CMake.
 | 
			
		||||
 
 | 
			
		||||
@@ -90,12 +90,12 @@ BEGIN
 | 
			
		||||
        BLOCK "040904e4"
 | 
			
		||||
        BEGIN
 | 
			
		||||
            VALUE "CompanyName", "com.example" "\0"
 | 
			
		||||
            VALUE "FileDescription", "fast_network_navigation" "\0"
 | 
			
		||||
            VALUE "FileDescription", "anyway" "\0"
 | 
			
		||||
            VALUE "FileVersion", VERSION_AS_STRING "\0"
 | 
			
		||||
            VALUE "InternalName", "fast_network_navigation" "\0"
 | 
			
		||||
            VALUE "InternalName", "anyway" "\0"
 | 
			
		||||
            VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0"
 | 
			
		||||
            VALUE "OriginalFilename", "fast_network_navigation.exe" "\0"
 | 
			
		||||
            VALUE "ProductName", "fast_network_navigation" "\0"
 | 
			
		||||
            VALUE "OriginalFilename", "anyway.exe" "\0"
 | 
			
		||||
            VALUE "ProductName", "anyway" "\0"
 | 
			
		||||
            VALUE "ProductVersion", VERSION_AS_STRING "\0"
 | 
			
		||||
        END
 | 
			
		||||
    END
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
 | 
			
		||||
  FlutterWindow window(project);
 | 
			
		||||
  Win32Window::Point origin(10, 10);
 | 
			
		||||
  Win32Window::Size size(1280, 720);
 | 
			
		||||
  if (!window.Create(L"fast_network_navigation", origin, size)) {
 | 
			
		||||
  if (!window.Create(L"anyway", origin, size)) {
 | 
			
		||||
    return EXIT_FAILURE;
 | 
			
		||||
  }
 | 
			
		||||
  window.SetQuitOnClose(true);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user