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 | ||||
|  | ||||
| @@ -17,5 +16,5 @@ class Preferences(BaseModel) : | ||||
|     # Shopping (diriger plutôt vers des zones / rues commerçantes) | ||||
|     shopping : Preference | ||||
|  | ||||
|     max_time_minute: Optional[int] = 6*60  | ||||
|     max_time_minute: Optional[int] = 6*60 | ||||
|     detour_tolerance_minute: Optional[int] = 0 | ||||
|   | ||||
							
								
								
									
										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,22 +20,13 @@ 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 | ||||
|                     ) | ||||
|         max_time_minute=180, | ||||
|         detour_tolerance_minute=30 | ||||
|     ) | ||||
|  | ||||
|     # Create start and finish  | ||||
|     if finish_coords is None : | ||||
|   | ||||
							
								
								
									
										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 elem_type == "nature": | ||||
|                         n_tags += 1  | ||||
|  | ||||
|                     if landmarktype != SHOPPING: | ||||
|                     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,43 +17,91 @@ 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 = ""; | ||||
|     if (snapshot.hasData) { | ||||
|       cityName = snapshot.data?.cityName ?? '...'; | ||||
|     } else if (snapshot.hasError) { | ||||
|       cityName = "error"; | ||||
|     } else { // still awaiting the cityname | ||||
|       cityName = "..."; | ||||
|     } | ||||
|     TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24); | ||||
|  | ||||
|     Widget topGreeter = Text( | ||||
|       'Welcome to $cityName!', | ||||
|       style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24), | ||||
|     ); | ||||
|     Widget topGreeter; | ||||
|  | ||||
|     if (widget.standalone) { | ||||
|       return Center( | ||||
|         child: Padding( | ||||
|           padding: EdgeInsets.only(top: 24.0), | ||||
|           child: topGreeter, | ||||
|         ), | ||||
|     if (widget.trip.uuid != 'pending') { | ||||
|       topGreeter = FutureBuilder( | ||||
|         future: widget.trip.cityName, | ||||
|         builder: (BuildContext context, AsyncSnapshot<String> snapshot) { | ||||
|           if (snapshot.hasData) { | ||||
|             return AutoSizeText( | ||||
|               maxLines: 1, | ||||
|               'Welcome to ${snapshot.data}!', | ||||
|               style: greeterStyle | ||||
|             ); | ||||
|           } else if (snapshot.hasError) { | ||||
|             log('Error while fetching city name'); | ||||
|             return AutoSizeText( | ||||
|               maxLines: 1, | ||||
|               'Welcome to your trip!', | ||||
|               style: greeterStyle | ||||
|             ); | ||||
|           } else { | ||||
|             return AutoSizeText( | ||||
|               maxLines: 1, | ||||
|               'Welcome to ...', | ||||
|               style: greeterStyle | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|       ); | ||||
|     } else { | ||||
|       return Center( | ||||
|         child: Column( | ||||
|           children: [ | ||||
|             Padding(padding: EdgeInsets.only(top: 24.0)), | ||||
|             topGreeter, | ||||
|             bottomGreeter, | ||||
|             Padding(padding: EdgeInsets.only(bottom: 24.0)), | ||||
|           ], | ||||
|         ) | ||||
|       // 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: 20)), | ||||
|           topGreeter, | ||||
|           Padding( | ||||
|             padding: EdgeInsets.all(20), | ||||
|             child: bottomGreeter | ||||
|           ), | ||||
|         ], | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget bottomGreeter = const Text( | ||||
| @@ -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) { | ||||
|           List<Widget> children; | ||||
|           if (snapshot.hasData) { | ||||
|             children = [landmarksWithSteps(snapshot.data!), saveButton()]; | ||||
|           } else if (snapshot.hasError) { | ||||
|             children = <Widget>[ | ||||
|     return ListenableBuilder( | ||||
|       listenable: widget.trip!, | ||||
|       builder: (BuildContext context, Widget? child) { | ||||
|         Trip trip = widget.trip!; | ||||
|         log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
|          | ||||
|         List<Widget> children; | ||||
|          | ||||
|         if (trip.uuid != 'pending' && 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, | ||||
|               children: children, | ||||
|             ), | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|         } | ||||
|  | ||||
|         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) { | ||||
|   List<Widget> children = []; | ||||
|   for (Landmark landmark in landmarks) { | ||||
|     children.add(LandmarkCard(landmark)); | ||||
|     if (landmark.next != null) { | ||||
|       Widget step = stepBetweenLandmarks(landmark, landmark.next!); | ||||
|       children.add(step); | ||||
|     } | ||||
|   Widget landmarksWithSteps() { | ||||
|     return ListenableBuilder( | ||||
|       listenable: widget.trip!, | ||||
|       builder: (BuildContext context, Widget? child) { | ||||
|         List<Widget> children = []; | ||||
|         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   | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   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( | ||||
|         child: Column( | ||||
|           mainAxisAlignment: MainAxisAlignment.center, | ||||
|           children: <Widget>[ | ||||
|             const Text( | ||||
|               'Create a new trip', | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|       body: Form( | ||||
|         key: _formKey, | ||||
|         child: Padding( | ||||
|           padding: const EdgeInsets.all(15.0), | ||||
|           child: Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|              | ||||
|             children: <Widget>[ | ||||
|               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( | ||||
|       decoration: BoxDecoration( | ||||
|         color: Colors.white, | ||||
|         borderRadius: BorderRadius.all(Radius.circular(24.0)), | ||||
|         boxShadow: [ | ||||
|           BoxShadow( | ||||
|             blurRadius: 20.0, | ||||
|             color: theme.shadowColor, | ||||
|           ), | ||||
|         ] | ||||
|       ), | ||||
|       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), | ||||
|             ], | ||||
|           ), | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Padding( | ||||
|           padding: const EdgeInsets.all(15), | ||||
|           child:  | ||||
|             Center( | ||||
|               child: Container( | ||||
|                 width: 40, | ||||
|                 height: 5, | ||||
|                 decoration: BoxDecoration( | ||||
|                   color: Colors.grey[300], | ||||
|                   borderRadius: BorderRadius.all(Radius.circular(12.0)), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|         ), | ||||
|       ), | ||||
|     ), | ||||
|         Expanded( | ||||
|           child: ListView( | ||||
|             children: [ | ||||
|               Greeter(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( | ||||
|           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)) | ||||
|         Center( | ||||
|           child: Padding( | ||||
|            padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10), | ||||
|             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> { | ||||
|  | ||||
|   UserPreferences _prefs = UserPreferences(); | ||||
|  | ||||
|   List<Card> _createSliders() { | ||||
|     List<Card> sliders = []; | ||||
|     for (SinglePreference pref in _prefs.preferences) { | ||||
|       sliders.add(Card( | ||||
|         child: ListTile( | ||||
|           leading: pref.icon, | ||||
|           title: Text(pref.name), | ||||
|           subtitle: Slider( | ||||
|             value: pref.value.toDouble(), | ||||
|             min: 0, | ||||
|             max: 10, | ||||
|             divisions: 10, | ||||
|             label: pref.value.toString(), | ||||
|             onChanged: (double newValue) { | ||||
|               setState(() { | ||||
|                 pref.value = newValue.toInt(); | ||||
|                 _prefs.save(); | ||||
|               }); | ||||
|             }, | ||||
|           ) | ||||
|         ), | ||||
|         margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0), | ||||
|         shadowColor: Colors.grey, | ||||
|       )); | ||||
|     } | ||||
|     return sliders; | ||||
|   } | ||||
| class _PreferenceSlidersState extends State<PreferenceSliders> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     List<Card> sliders = []; | ||||
|       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: pref.minVal.toDouble(), | ||||
|               max: pref.maxVal.toDouble(), | ||||
|               divisions: pref.maxVal - pref.minVal, | ||||
|               label: pref.value.toString(), | ||||
|               onChanged: (double newValue) { | ||||
|                 setState(() { | ||||
|                   pref.value = newValue.toInt(); | ||||
|                   pref.save(); | ||||
|                 }); | ||||
|               }, | ||||
|             ) | ||||
|           ), | ||||
|           margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0), | ||||
|           shadowColor: Colors.grey, | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return Column(children: _createSliders()); | ||||
|     return Column( | ||||
|       children: sliders); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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