15 Commits

Author SHA1 Message Date
003b8d0f9c better time management for optimizer 2024-08-12 18:52:01 +02:00
a1fcc8d23b added duration
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m30s
2024-08-12 16:07:53 +02:00
da921171e9 more balanced scores
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m41s
2024-08-12 15:58:30 +02:00
bea3a65fec Merge pull request 'Tentatively enable communication between front + backend' (#15) from feature/frontend-backend-interoperability into main
Reviewed-on: #15
2024-08-06 13:51:31 +00:00
f71b9b19a6 show correct landmark types when fetching from api
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m42s
Build and release APK / Build APK (pull_request) Successful in 5m22s
2024-08-06 14:34:12 +02:00
89511f39cb better errorhandling, slimmed down optimizer
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m40s
Build and release APK / Build APK (pull_request) Successful in 5m26s
2024-08-05 16:03:29 +02:00
71d9554d97 ui improvements for trips and landmarks
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m48s
Build and release APK / Build APK (pull_request) Successful in 4m51s
2024-08-05 10:18:00 +02:00
c87a01b2e8 overhaul using a trip struct that notifies its ui dependencies
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m54s
Build and release APK / Build APK (pull_request) Successful in 5m32s
2024-08-03 17:17:48 +02:00
5748630b99 bare implementation of comuncation with the api
All checks were successful
Build and push docker image / Build (pull_request) Successful in 2m6s
Build and release APK / Build APK (pull_request) Successful in 4m32s
2024-08-01 22:48:28 +02:00
016622c7af frontend compliant with backend
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m38s
Build and release APK / Build APK (pull_request) Successful in 5m11s
2024-08-01 19:35:25 +02:00
bf129b201d more straightforward logging 2024-08-01 19:34:48 +02:00
86bcec6b29 frontend groundwork
Some checks failed
Build and release APK / Build APK (pull_request) Has been cancelled
Build and push docker image / Build (pull_request) Successful in 1m33s
2024-08-01 17:56:06 +02:00
07dde5ab58 persistence for recurring api calls
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m48s
2024-07-31 12:54:25 +02:00
db82495f11 rename frontend components to anyway 2024-07-30 22:49:28 +02:00
889b6c2096 added wikidata throttle to gitignore 2024-07-27 21:05:13 +02:00
56 changed files with 1477 additions and 732 deletions

View File

@@ -43,8 +43,10 @@ jobs:
working-directory: ./frontend working-directory: ./frontend
- name: Add required secrets - name: Add required secrets
env:
ANDROID_SECRETS_PROPERTIES: ${{ secrets.ANDROID_SECRETS_PROPERTIES }}
run: | run: |
echo ${{ secrets.ANDROID_SECRETS_PROPERTIES }} > ./android/secrets.properties echo "$ANDROID_SECRETS_PROPERTIES" >> ./android/secrets.properties
working-directory: ./frontend working-directory: ./frontend
- name: Sanity check - name: Sanity check

6
backend/.gitignore vendored
View File

@@ -1,6 +1,10 @@
# osm-cache # osm-cache and wikidata cache
cache/ cache/
apicache/ apicache/
# wikidata throttle
*.ctrl
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

View File

@@ -13,5 +13,6 @@ EXPOSE 8000
# Set environment variables used by the deployment. These can be overridden by the user using this image. # Set environment variables used by the deployment. These can be overridden by the user using this image.
ENV NUM_WORKERS=1 ENV NUM_WORKERS=1
ENV OSM_CACHE_DIR=/cache ENV OSM_CACHE_DIR=/cache
ENV MEMCACHED_HOST_PATH=none
CMD fastapi run src/main.py --port 8000 --workers $NUM_WORKERS CMD fastapi run src/main.py --port 8000 --workers $NUM_WORKERS

View File

@@ -14,3 +14,4 @@ shapely = "*"
scipy = "*" scipy = "*"
osmpythontools = "*" osmpythontools = "*"
pywikibot = "*" pywikibot = "*"
pymemcache = "*"

25
backend/Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "f0de801038593d42d8b780d14c2c72bb4f5f5e66df02f72244917ede5d5ebce6" "sha256": "4f8b3f0395b4e5352330616870da13acf41e16d1b69ba31b15fd688e90b8b628"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@@ -1102,6 +1102,15 @@
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==2.18.0" "version": "==2.18.0"
}, },
"pymemcache": {
"hashes": [
"sha256:27bf9bd1bbc1e20f83633208620d56de50f14185055e49504f4f5e94e94aff94",
"sha256:f507bc20e0dc8d562f8df9d872107a278df049fa496805c1431b926f3ddd0eab"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==4.0.0"
},
"pyparsing": { "pyparsing": {
"hashes": [ "hashes": [
"sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad",
@@ -1142,12 +1151,12 @@
}, },
"pywikibot": { "pywikibot": {
"hashes": [ "hashes": [
"sha256:3f4fbc57f1765aa0fa1ccf84125bcfa475cae95b9cc0291867b751f3d4ac8fa2", "sha256:0dd8291f1a26abb9fce2c2108a90dc338274988e60d21723aec1d3b0de321b5e",
"sha256:a26d918cf88ef56fdb1421b65b09def200cc28031cdc922d72a4198fbfddd225" "sha256:7953fc4a6c498057e6eb7d9b762bbccb61348af0a599b89d7e246d5175b20a9b"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_full_version >= '3.7.0'", "markers": "python_full_version >= '3.7.0'",
"version": "==9.2.1" "version": "==9.3.0"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
@@ -1349,7 +1358,7 @@
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version < '3.13'",
"version": "==4.12.2" "version": "==4.12.2"
}, },
"tzdata": { "tzdata": {
@@ -1658,11 +1667,11 @@
}, },
"xarray": { "xarray": {
"hashes": [ "hashes": [
"sha256:0b91e0bc4dc0296947947640fe31ec6e867ce258d2f7cbc10bedf4a6d68340c7", "sha256:1b0fd51ec408474aa1f4a355d75c00cc1c02bd425d97b2c2e551fd21810e7f64",
"sha256:721a7394e8ec3d592b2d8ebe21eed074ac077dc1bb1bd777ce00e41700b4866c" "sha256:4cae512d121a8522d41e66d942fb06c526bc1fd32c2c181d5fe62fe65b671638"
], ],
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==2024.6.0" "version": "==2024.7.0"
} }
}, },
"develop": {} "develop": {}

View File

@@ -15,13 +15,21 @@ OSM_CACHE_DIR = Path(cache_dir_string)
import logging import logging
import yaml # if we are in a debug session, set verbose and rich logging
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 os.getenv('DEBUG', False): 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

View File

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

View File

@@ -1,12 +1,14 @@
import logging import logging
from fastapi import FastAPI, Query, Body from fastapi import FastAPI, Query, Body, HTTPException
from structs.landmark import Landmark from structs.landmark import Landmark
from structs.preferences import Preferences from structs.preferences import Preferences
from structs.linked_landmarks import LinkedLandmarks from structs.linked_landmarks import LinkedLandmarks
from structs.trip import Trip
from utils.landmarks_manager import LandmarkManager from utils.landmarks_manager import LandmarkManager
from utils.optimizer import Optimizer from utils.optimizer import Optimizer
from utils.refiner import Refiner from utils.refiner import Refiner
from persistence import client as cache_client
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -17,8 +19,8 @@ optimizer = Optimizer()
refiner = Refiner(optimizer=optimizer) refiner = Refiner(optimizer=optimizer)
@app.post("/route/new") @app.post("/trip/new")
def get_route(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] | None = None) -> str: def new_trip(preferences: Preferences, start: tuple[float, float], end: tuple[float, float] | None = None) -> Trip:
''' '''
Main function to call the optimizer. Main function to call the optimizer.
:param preferences: the preferences specified by the user as the post body :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 :return: the uuid of the first landmark in the optimized route
''' '''
if preferences is None: if preferences is None:
raise ValueError("Please provide preferences in the form of a 'Preference' BaseModel class.") raise HTTPException(status_code=406, detail="Preferences not provided")
if preferences.shopping.score == 0 and preferences.sightseeing.score == 0 and preferences.nature.score == 0:
raise HTTPException(status_code=406, detail="All preferences are 0.")
if start is None: if start is None:
raise ValueError("Please provide the starting coordinates as a tuple of floats.") raise HTTPException(status_code=406, detail="Start coordinates not provided")
if end is None: if end is None:
end = start end = start
logger.info("No end coordinates provided. Using start=end.") logger.info("No end coordinates provided. Using start=end.")
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) 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 # Generate the landmarks from the start location
landmarks, landmarks_short = manager.generate_landmarks_list( 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.insert(0, start_landmark)
landmarks_short.append(end_landmark) landmarks_short.append(end_landmark)
# TODO infer these parameters from the preferences
max_walking_time = 4 # hours
detour = 30 # minutes
# First stage optimization # 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 # 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) 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}") @app.get("/landmark/{landmark_uuid}")
def get_landmark(landmark_uuid: str) -> Landmark: def get_landmark(landmark_uuid: str) -> Landmark:
#cherche dans linked_tour et retourne le landmark correspondant try:
pass landmark = cache_client.get(f"landmark_{landmark_uuid}")
return landmark
except KeyError:
raise HTTPException(status_code=404, detail="Landmark not found")

View File

@@ -1,6 +1,11 @@
city_bbox_side: 5000 #m city_bbox_side: 7500 #m
radius_close_to: 50 radius_close_to: 50
church_coeff: 0.8 church_coeff: 0.75
park_coeff: 1.2 nature_coeff: 1.25
tag_coeff: 10 overall_coeff: 10
tag_exponent: 1.15
image_bonus: 10
viewpoint_bonus: 15
wikipedia_bonus: 6
N_important: 40 N_important: 40
pay_bonus: -1

View File

@@ -1,4 +1,6 @@
detour_factor: 1.4 detour_factor: 1.4
detour_corridor_width: 200 detour_corridor_width: 300
average_walking_speed: 4.8 average_walking_speed: 4.8
max_landmarks: 7 max_landmarks: 10
max_landmarks_refiner: 20
overshoot: 1.3

View File

@@ -0,0 +1,26 @@
from pymemcache.client.base import Client
import constants
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 constants.MEMCACHED_HOST_PATH is None:
client = DummyClient()
else:
client = Client(
constants.MEMCACHED_HOST_PATH,
timeout=1,
allow_unicode_keys=True,
encoding='utf-8'
)

View File

@@ -33,6 +33,7 @@ class Landmark(BaseModel) :
return self.uuid.int return self.uuid.int
def __str__(self) -> str: def __str__(self) -> str:
time_to_next_str = f", time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else "" time_to_next_str = f"time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else ""
return f'Landmark({self.type}): [{self.name} @{self.location}, score={self.attractiveness}{time_to_next_str}]' # return f'Landmark({self.type}): [{self.name} @{self.location}, score={self.attractiveness}{time_to_next_str}]'
return f'({self.type[:4]}), score={self.attractiveness}\tmain:{not self.is_secondary}\tduration={self.duration}\t{time_to_next_str}\t{self.name}'

View File

@@ -1,4 +1,3 @@
import uuid
from .landmark import Landmark from .landmark import Landmark
from utils.get_time_separation import get_time from utils.get_time_separation import get_time
@@ -9,8 +8,7 @@ class LinkedLandmarks:
""" """
_landmarks = list[Landmark] _landmarks = list[Landmark]
total_time = int total_time: int = 0
uuid = str
def __init__(self, data: list[Landmark] = None) -> None: def __init__(self, data: list[Landmark] = None) -> None:
""" """
@@ -19,7 +17,6 @@ class LinkedLandmarks:
Args: Args:
data (list[Landmark], optional): The list of landmarks that are linked together. Defaults to None. 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._landmarks = data if data else []
self._link_landmarks() self._link_landmarks()
@@ -28,7 +25,11 @@ class LinkedLandmarks:
""" """
Create the links between the landmarks in the list by setting their .next_uuid and the .time_to_next attributes. Create the links between the landmarks in the list by setting their .next_uuid and the .time_to_next attributes.
""" """
self.total_time = 0
# Mark secondary landmarks as such
self.update_secondary_landmarks()
for i, landmark in enumerate(self._landmarks[:-1]): for i, landmark in enumerate(self._landmarks[:-1]):
landmark.next_uuid = self._landmarks[i + 1].uuid landmark.next_uuid = self._landmarks[i + 1].uuid
time_to_next = get_time(landmark.location, self._landmarks[i + 1].location) time_to_next = get_time(landmark.location, self._landmarks[i + 1].location)
@@ -38,24 +39,26 @@ class LinkedLandmarks:
self._landmarks[-1].next_uuid = None self._landmarks[-1].next_uuid = None
self._landmarks[-1].time_to_reach_next = 0 self._landmarks[-1].time_to_reach_next = 0
def update_secondary_landmarks(self) -> None:
# Extract the attractiveness scores and sort them in descending order
scores = sorted([landmark.attractiveness for landmark in self._landmarks], reverse=True)
# Determine the 10th highest score
if len(scores) >= 10:
threshold_score = scores[9]
else:
# If there are fewer than 10 landmarks, use the lowest score in the list as the threshold
threshold_score = min(scores) if scores else 0
# Update 'is_secondary' for landmarks with attractiveness below the threshold score
for landmark in self._landmarks:
if landmark.attractiveness < threshold_score:
landmark.is_secondary = True
def __getitem__(self, index: int) -> Landmark: def __getitem__(self, index: int) -> Landmark:
return self._landmarks[index] return self._landmarks[index]
def __str__(self) -> str: 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])}]" return f"LinkedLandmarks [{' ->'.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]
}

View File

@@ -2,7 +2,6 @@ from pydantic import BaseModel
from typing import Optional, Literal from typing import Optional, Literal
class Preference(BaseModel) : class Preference(BaseModel) :
name: str
type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish'] type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish']
score: int # score could be from 1 to 5 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 (diriger plutôt vers des zones / rues commerçantes)
shopping : Preference shopping : Preference
max_time_minute: Optional[int] = 6*60 max_time_minute: Optional[int] = 6*60
detour_tolerance_minute: Optional[int] = 0 detour_tolerance_minute: Optional[int] = 0

View 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

View File

@@ -20,22 +20,13 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
preferences = Preferences( preferences = Preferences(
sightseeing=Preference( sightseeing=Preference(type='sightseeing', score = 5),
name='sightseeing', nature=Preference(type='nature', score = 5),
type='sightseeing', shopping=Preference(type='shopping', score = 5),
score = 5),
nature=Preference(
name='nature',
type='nature',
score = 5),
shopping=Preference(
name='shopping',
type='shopping',
score = 5),
max_time_minute=180, max_time_minute=300,
detour_tolerance_minute=30 detour_tolerance_minute=15
) )
# Create start and finish # Create start and finish
if finish_coords is None : if finish_coords is None :
@@ -69,7 +60,14 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
refined_tour = refiner.refine_optimization(all_landmarks=landmarks, base_tour=base_tour, max_time = preferences.max_time_minute, detour = preferences.detour_tolerance_minute) refined_tour = refiner.refine_optimization(all_landmarks=landmarks, base_tour=base_tour, max_time = preferences.max_time_minute, detour = preferences.detour_tolerance_minute)
linked_tour = LinkedLandmarks(refined_tour) linked_tour = LinkedLandmarks(refined_tour)
logger.info(f"Optimized route: {linked_tour}") total_time = 0
logger.info("Optimized route : ")
for l in linked_tour :
logger.info(f"{l}")
total_time += l.duration
total_time += l.time_to_reach_next
logger.info(f"Total time: {total_time}")
# with open('linked_tour.yaml', 'w') as f: # with open('linked_tour.yaml', 'w') as f:
# yaml.dump(linked_tour.asdict(), f) # yaml.dump(linked_tour.asdict(), f)
@@ -77,9 +75,9 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
return linked_tour return linked_tour
#test(tuple((48.8344400, 2.3220540))) # Café Chez César # test(tuple((48.8344400, 2.3220540))) # Café Chez César
#test(tuple((48.8375946, 2.2949904))) # Point random # test(tuple((48.8375946, 2.2949904))) # Point random
#test(tuple((47.377859, 8.540585))) # Zurich HB # test(tuple((47.377859, 8.540585))) # Zurich HB
#test(tuple((45.7576485, 4.8330241))) # Lyon Bellecour test(tuple((45.7576485, 4.8330241))) # Lyon Bellecour
test(tuple((48.5848435, 7.7332974))) # Strasbourg Gare # test(tuple((48.5848435, 7.7332974))) # Strasbourg Gare
#test(tuple((48.2067858, 16.3692340))) # Vienne # test(tuple((48.2067858, 16.3692340))) # Vienne

View File

@@ -15,21 +15,16 @@ from .take_most_important import take_most_important
import constants import constants
SIGHTSEEING = 'sightseeing'
NATURE = 'nature'
SHOPPING = 'shopping'
class LandmarkManager: class LandmarkManager:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
city_bbox_side: int # bbox side in meters
radius_close_to: int # radius in meters radius_close_to: int # radius in meters
church_coeff: float # coeff to adjsut score of churches church_coeff: float # coeff to adjsut score of churches
park_coeff: float # coeff to adjust score of parks nature_coeff: float # coeff to adjust score of parks
tag_coeff: float # coeff to adjust weight of tags overall_coeff: float # coeff to adjust weight of tags
N_important: int # number of important landmarks to consider N_important: int # number of important landmarks to consider
@@ -40,12 +35,22 @@ class LandmarkManager:
with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: with constants.LANDMARK_PARAMETERS_PATH.open('r') as f:
parameters = yaml.safe_load(f) parameters = yaml.safe_load(f)
self.city_bbox_side = parameters['city_bbox_side'] self.max_bbox_side = parameters['city_bbox_side']
self.radius_close_to = parameters['radius_close_to'] self.radius_close_to = parameters['radius_close_to']
self.church_coeff = parameters['church_coeff'] self.church_coeff = parameters['church_coeff']
self.park_coeff = parameters['park_coeff'] self.nature_coeff = parameters['nature_coeff']
self.tag_coeff = parameters['tag_coeff'] self.overall_coeff = parameters['overall_coeff']
self.tag_exponent = parameters['tag_exponent']
self.image_bonus = parameters['image_bonus']
self.wikipedia_bonus = parameters['wikipedia_bonus']
self.viewpoint_bonus = parameters['viewpoint_bonus']
self.pay_bonus = parameters['pay_bonus']
self.N_important = parameters['N_important'] self.N_important = parameters['N_important']
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
parameters = yaml.safe_load(f)
self.walking_speed = parameters['average_walking_speed']
self.detour_factor = parameters['detour_factor']
self.overpass = Overpass() self.overpass = Overpass()
CachingStrategy.use(JSON, cacheDir=constants.OSM_CACHE_DIR) CachingStrategy.use(JSON, cacheDir=constants.OSM_CACHE_DIR)
@@ -69,30 +74,33 @@ class LandmarkManager:
- A list of the most important landmarks based on the user's preferences. - A list of the most important landmarks based on the user's preferences.
""" """
max_walk_dist = (preferences.max_time_minute/2)/60*self.walking_speed*1000/self.detour_factor
reachable_bbox_side = min(max_walk_dist, self.max_bbox_side)
L = [] L = []
bbox = self.create_bbox(center_coordinates) bbox = self.create_bbox(center_coordinates, reachable_bbox_side)
# list for sightseeing # list for sightseeing
if preferences.sightseeing.score != 0: if preferences.sightseeing.score != 0:
score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.church_coeff) score_function = lambda score: int(score*10)*preferences.sightseeing.score/5 # self.count_elements_close_to(loc) +
L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], SIGHTSEEING, score_function) L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], preferences.sightseeing.type, score_function)
self.correct_score(L1, preferences.sightseeing)
L += L1 L += L1
# list for nature # list for nature
if preferences.nature.score != 0: if preferences.nature.score != 0:
score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.park_coeff) score_function = lambda score: int(score*10*self.nature_coeff)*preferences.nature.score/5 # self.count_elements_close_to(loc) +
L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], NATURE, score_function) L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], preferences.nature.type, score_function)
self.correct_score(L2, preferences.nature)
L += L2 L += L2
# list for shopping # list for shopping
if preferences.shopping.score != 0: if preferences.shopping.score != 0:
score_function = lambda loc, n_tags: int(self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff)) score_function = lambda score: int(score*10)*preferences.shopping.score/5 # self.count_elements_close_to(loc) +
L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], SHOPPING, score_function) L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], preferences.shopping.type, score_function)
self.correct_score(L3, preferences.shopping)
L += L3 L += L3
L = self.remove_duplicates(L) L = self.remove_duplicates(L)
# self.correct_score(L, preferences)
L_constrained = take_most_important(L, self.N_important) 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.') self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.')
@@ -123,7 +131,7 @@ class LandmarkManager:
return L_clean 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. Adjust the attractiveness score of each landmark in the list based on user preferences.
@@ -132,20 +140,16 @@ class LandmarkManager:
Args: Args:
landmarks (list[Landmark]): A list of landmarks whose scores need to be corrected. 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. preferences (Preferences): 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.
""" """
if len(landmarks) == 0: score_dict = {
return preferences.sightseeing.type: preferences.sightseeing.score,
preferences.nature.type: preferences.nature.score,
if landmarks[0].type != preference.type: preferences.shopping.type: preferences.shopping.score
raise TypeError(f"LandmarkType {preference.type} does not match the type of Landmark {landmarks[0].name}") }
for landmark in landmarks:
for elem in landmarks: landmark.attractiveness = int(landmark.attractiveness * score_dict[landmark.type] / 5)
elem.attractiveness = int(elem.attractiveness*preference.score/5) # arbitrary computation
def count_elements_close_to(self, coordinates: tuple[float, float]) -> int: def count_elements_close_to(self, coordinates: tuple[float, float]) -> int:
@@ -191,12 +195,13 @@ class LandmarkManager:
return 0 return 0
def create_bbox(self, coordinates: tuple[float, float]) -> tuple[float, float, float, float]: def create_bbox(self, coordinates: tuple[float, float], reachable_bbox_side: int) -> tuple[float, float, float, float]:
""" """
Create a bounding box around the given coordinates. Create a bounding box around the given coordinates.
Args: Args:
coordinates (tuple[float, float]): The latitude and longitude of the center of the bounding box. coordinates (tuple[float, float]): The latitude and longitude of the center of the bounding box.
reachable_bbox_side (int): The side length of the bounding box in meters.
Returns: Returns:
tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude
@@ -207,7 +212,7 @@ class LandmarkManager:
lon = coordinates[1] lon = coordinates[1]
# Half the side length in km (since it's a square bbox) # Half the side length in km (since it's a square bbox)
half_side_length_km = self.city_bbox_side / 2 / 1000 half_side_length_km = reachable_bbox_side / 2 / 1000
# Convert distance to degrees # Convert distance to degrees
lat_diff = half_side_length_km / 111 # 1 degree latitude is approximately 111 km lat_diff = half_side_length_km / 111 # 1 degree latitude is approximately 111 km
@@ -273,8 +278,8 @@ class LandmarkManager:
continue continue
# skip if unused # skip if unused
if 'disused:leisure' in elem.tags().keys(): # if 'disused:leisure' in elem.tags().keys():
continue # continue
# skip if part of another building # skip if part of another building
if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes':
@@ -284,33 +289,41 @@ class LandmarkManager:
osm_id = elem.id() # Add OSM id osm_id = elem.id() # Add OSM id
elem_type = landmarktype # Add the landmark type as 'sightseeing, elem_type = landmarktype # Add the landmark type as 'sightseeing,
n_tags = len(elem.tags().keys()) # Add number of tags n_tags = len(elem.tags().keys()) # Add number of tags
score = n_tags**self.tag_exponent # Add score
# remove specific tags # remove specific tags
skip = False skip = False
for tag in elem.tags().keys(): for tag in elem.tags().keys():
if "pay" in tag: if "pay" in tag:
n_tags -= 1 # discard payment options for tags score += self.pay_bonus # discard payment options for tags
if "disused" in tag: if "disused" in tag:
skip = True # skip disused amenities skip = True # skip disused amenities
break break
if "wikipedia" in tag: if "wiki" in tag:
n_tags += 3 # wikipedia entries count more score += self.wikipedia_bonus # wikipedia entries count more
if tag == "wikidata": # if tag == "wikidata":
Q = elem.tag('wikidata') # Q = elem.tag('wikidata')
site = Site("wikidata", "wikidata") # site = Site("wikidata", "wikidata")
item = ItemPage(site, Q) # item = ItemPage(site, Q)
item.get() # item.get()
n_languages = len(item.labels) # n_languages = len(item.labels)
n_tags += n_languages/10 # n_tags += n_languages/10
if "viewpoint" in tag:
score += self.viewpoint_bonus
if "image" in tag:
score += self.image_bonus
if elem_type != "nature": if elem_type != "nature":
if "leisure" in tag and elem.tag('leisure') == "park": if "leisure" in tag and elem.tag('leisure') == "park":
elem_type = "nature" elem_type = "nature"
if landmarktype != SHOPPING: if landmarktype != "shopping":
if "shop" in tag: if "shop" in tag:
skip = True skip = True
break break
@@ -318,24 +331,40 @@ class LandmarkManager:
if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']: if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']:
skip = True skip = True
break break
if skip: if skip:
continue continue
score = score_function(location, n_tags) score = score_function(score)
if score != 0: if "place_of_worship" in elem.tags().values() :
# Generate the landmark and append it to the list score = int(score*self.church_coeff)
landmark = Landmark( duration = 20
name=name,
type=elem_type, elif "museum" in elem.tags().values() :
location=location, score = int(score*self.church_coeff)
osm_type=osm_type, duration = 60
osm_id=osm_id,
attractiveness=score, elif "fountain" in elem.tags().values() :
must_do=False, duration = 5
n_tags=int(n_tags)
) elif "park" in elem.tags().values() :
return_list.append(landmark) duration = 30
else :
duration = 15
# Generate the landmark and append it to the list
landmark = Landmark(
name=name,
type=elem_type,
location=location,
osm_type=osm_type,
osm_id=osm_id,
attractiveness=score,
must_do=False,
n_tags=int(n_tags),
duration = duration
)
return_list.append(landmark)
self.logger.debug(f"Fetched {len(return_list)} landmarks of type {landmarktype} in {bbox}") self.logger.debug(f"Fetched {len(return_list)} landmarks of type {landmarktype} in {bbox}")

View File

@@ -17,10 +17,11 @@ class Optimizer:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
detour: int = None # accepted max detour time (in minutes) detour: int = None # accepted max detour time (in minutes)
detour_factor: float # detour factor of straight line vs real distance in cities detour_factor: float # detour factor of straight line vs real distance in cities
average_walking_speed: float # average walking speed of adult average_walking_speed: float # average walking speed of adult
max_landmarks: int # max number of landmarks to visit max_landmarks: int # max number of landmarks to visit
overshoot: float # experimentally determined overshoot possibility to return long enough tours
def __init__(self) : def __init__(self) :
@@ -31,6 +32,7 @@ class Optimizer:
self.detour_factor = parameters['detour_factor'] self.detour_factor = parameters['detour_factor']
self.average_walking_speed = parameters['average_walking_speed'] self.average_walking_speed = parameters['average_walking_speed']
self.max_landmarks = parameters['max_landmarks'] self.max_landmarks = parameters['max_landmarks']
self.overshoot = parameters['overshoot']
@@ -167,7 +169,7 @@ class Optimizer:
def init_ub_dist(self, landmarks: list[Landmark], max_steps: int): def init_ub_dist(self, landmarks: list[Landmark], max_time: int):
""" """
Initialize the objective function coefficients and inequality constraints for the optimization problem. Initialize the objective function coefficients and inequality constraints for the optimization problem.
@@ -176,7 +178,7 @@ class Optimizer:
Args: Args:
landmarks (list[Landmark]): List of landmarks. landmarks (list[Landmark]): List of landmarks.
max_steps (int): Maximum number of steps allowed. max_time (int): Maximum time allowed for tour.
Returns: Returns:
Tuple[list[float], list[float], list[int]]: Objective function coefficients, inequality constraint coefficients, and the right-hand side of the inequality constraint. Tuple[list[float], list[float], list[int]]: Objective function coefficients, inequality constraint coefficients, and the right-hand side of the inequality constraint.
@@ -191,7 +193,7 @@ class Optimizer:
dist_table = [0]*len(landmarks) dist_table = [0]*len(landmarks)
c.append(-spot1.attractiveness) c.append(-spot1.attractiveness)
for j, spot2 in enumerate(landmarks) : for j, spot2 in enumerate(landmarks) :
t = get_time(spot1.location, spot2.location) t = get_time(spot1.location, spot2.location) + spot1.duration
dist_table[j] = t dist_table[j] = t
closest = sorted(dist_table)[:22] closest = sorted(dist_table)[:22]
for i, dist in enumerate(dist_table) : for i, dist in enumerate(dist_table) :
@@ -200,10 +202,10 @@ class Optimizer:
A_ub += dist_table A_ub += dist_table
c = c*len(landmarks) c = c*len(landmarks)
return c, A_ub, [max_steps] return c, A_ub, [max_time*self.overshoot]
def respect_number(self, L: int): def respect_number(self, L, max_landmarks: int):
""" """
Generate constraints to ensure each landmark is visited only once and cap the total number of visited landmarks. Generate constraints to ensure each landmark is visited only once and cap the total number of visited landmarks.
@@ -224,7 +226,7 @@ class Optimizer:
b.append(1) b.append(1)
A = np.vstack((A, ones*L)) A = np.vstack((A, ones*L))
b.append(self.max_landmarks+1) b.append(max_landmarks+1)
return A, b return A, b
@@ -433,6 +435,7 @@ class Optimizer:
self, self,
max_time: int, max_time: int,
landmarks: list[Landmark], landmarks: list[Landmark],
max_landmarks: int = None
) -> list[Landmark]: ) -> list[Landmark]:
""" """
Main optimization pipeline to solve the landmark visiting problem. Main optimization pipeline to solve the landmark visiting problem.
@@ -443,15 +446,18 @@ class Optimizer:
Args: Args:
max_time (int): Maximum time allowed for the tour in minutes. max_time (int): Maximum time allowed for the tour in minutes.
landmarks (list[Landmark]): List of landmarks to visit. landmarks (list[Landmark]): List of landmarks to visit.
max_landmarks (int): Maximum number of landmarks visited
Returns: Returns:
list[Landmark]: The optimized tour of landmarks with updated travel times, or None if no valid solution is found. list[Landmark]: The optimized tour of landmarks with updated travel times, or None if no valid solution is found.
""" """
if max_landmarks is None :
max_landmarks = self.max_landmarks
L = len(landmarks) L = len(landmarks)
# SET CONSTRAINTS FOR INEQUALITY # SET CONSTRAINTS FOR INEQUALITY
c, A_ub, b_ub = self.init_ub_dist(landmarks, max_time) # Add the distances from each landmark to the other c, A_ub, b_ub = self.init_ub_dist(landmarks, max_time) # Add the distances from each landmark to the other
A, b = self.respect_number(L) # Respect max number of visits (no more possible stops than landmarks). A, b = self.respect_number(L, max_landmarks) # Respect max number of visits (no more possible stops than landmarks).
A_ub = np.vstack((A_ub, A), dtype=np.int16) A_ub = np.vstack((A_ub, A), dtype=np.int16)
b_ub += b b_ub += b
A, b = self.break_sym(L) # break the 'zig-zag' symmetry A, b = self.break_sym(L) # break the 'zig-zag' symmetry

View File

@@ -17,7 +17,7 @@ class Refiner :
detour_factor: float # detour factor of straight line vs real distance in cities detour_factor: float # detour factor of straight line vs real distance in cities
detour_corridor_width: float # width of the corridor around the path detour_corridor_width: float # width of the corridor around the path
average_walking_speed: float # average walking speed of adult average_walking_speed: float # average walking speed of adult
max_landmarks: int # max number of landmarks to visit max_landmarks_refiner: int # max number of landmarks to visit
optimizer: Optimizer # optimizer object optimizer: Optimizer # optimizer object
def __init__(self, optimizer: Optimizer) : def __init__(self, optimizer: Optimizer) :
@@ -29,7 +29,7 @@ class Refiner :
self.detour_factor = parameters['detour_factor'] self.detour_factor = parameters['detour_factor']
self.detour_corridor_width = parameters['detour_corridor_width'] self.detour_corridor_width = parameters['detour_corridor_width']
self.average_walking_speed = parameters['average_walking_speed'] self.average_walking_speed = parameters['average_walking_speed']
self.max_landmarks = parameters['max_landmarks'] + 4 self.max_landmarks_refiner = parameters['max_landmarks_refiner']
def create_corridor(self, landmarks: list[Landmark], width: float) : def create_corridor(self, landmarks: list[Landmark], width: float) :
@@ -308,8 +308,8 @@ class Refiner :
""" """
# No need to refine if no detour is taken # No need to refine if no detour is taken
if detour == 0: # if detour == 0:
return base_tour # return base_tour
minor_landmarks = self.get_minor_landmarks(all_landmarks, base_tour, self.detour_corridor_width) minor_landmarks = self.get_minor_landmarks(all_landmarks, base_tour, self.detour_corridor_width)
@@ -322,7 +322,8 @@ class Refiner :
# get a new tour # get a new tour
new_tour = self.optimizer.solve_optimization( new_tour = self.optimizer.solve_optimization(
max_time = max_time + detour, max_time = max_time + detour,
landmarks = full_set landmarks = full_set,
max_landmarks = self.max_landmarks_refiner
) )
if new_tour is None: if new_tour is None:

View File

@@ -42,7 +42,7 @@ if (secretPropertiesFile.exists()) {
android { android {
namespace "com.example.fast_network_navigation" namespace "com.anydev.anyway"
compileSdk flutter.compileSdkVersion compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
@@ -61,7 +61,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // 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. // 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. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
// Minimum Android version for Google Maps SDK // Minimum Android version for Google Maps SDK

View File

@@ -3,7 +3,7 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="fast_network_navigation" android:label="anyway"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

View File

@@ -1,4 +1,4 @@
package com.example.fast_network_navigation package com.anydev.anyway
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

View File

@@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>fast_network_navigation</string> <string>anyway</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View File

@@ -0,0 +1,4 @@
const String APP_NAME = 'AnyWay';
String API_URL_BASE = 'https://anyway.kluster.moll.re';

View File

@@ -1,23 +1,29 @@
import 'package:fast_network_navigation/modules/trips_overview.dart'; import 'dart:collection';
import 'package:fast_network_navigation/pages/new_trip.dart';
import 'package:fast_network_navigation/pages/tutorial.dart'; import 'package:anyway/structs/landmark.dart';
import 'package:fast_network_navigation/structs/trip.dart';
import 'package:fast_network_navigation/utils/load_trips.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fast_network_navigation/pages/overview.dart'; import 'package:anyway/constants.dart';
import 'package:fast_network_navigation/pages/profile.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 // BasePage is the scaffold that holds all other pages
// A side drawer is used to switch between pages // A side drawer is used to switch between pages
class BasePage extends StatefulWidget { class BasePage extends StatefulWidget {
final String mainScreen; final String mainScreen;
final Future<Trip>? trip; final Trip? trip;
const BasePage({ const BasePage({
super.key, super.key,
required this.mainScreen, required this.mainScreen,
this.trip this.trip,
}); });
@override @override
@@ -43,20 +49,20 @@ class _BasePageState extends State<BasePage> {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
return Scaffold( return Scaffold(
appBar: AppBar(title: Text("City Nav")), appBar: AppBar(title: Text(APP_NAME)),
body: Center(child: currentView), body: Center(child: currentView),
drawer: Drawer( drawer: Drawer(
child: Column( child: Column(
children: [ children: [
DrawerHeader( DrawerHeader(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient(colors: [Colors.cyan, theme.primaryColor]) gradient: LinearGradient(colors: [Colors.red, Colors.yellow])
), ),
child: Center( child: Center(
child: Text( child: Text(
'City Nav', APP_NAME,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.grey[800],
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -126,9 +132,71 @@ class _BasePageState extends State<BasePage> {
} }
} }
// This function is used to get the first trip from a list of trips
// TODO: Implement this function
Future<Trip> getFirstTrip (Future<List<Trip>> trips) async { Trip getFirstTrip(Future<List<Trip>> trips) {
List<Trip> tripsf = await trips; Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>());
return tripsf[0]; 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;
} }

View File

@@ -1,19 +1,19 @@
import 'package:flutter/material.dart'; 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()); void main() => runApp(const App());
class App extends StatelessWidget { class App extends StatelessWidget {
const App({super.key}); const App({super.key});
static const appTitle = 'City Nav';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: appTitle, title: APP_NAME,
home: BasePage(mainScreen: "map"), home: BasePage(mainScreen: "map"),
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.green), theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]),
); );
} }
} }

View File

@@ -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'; import 'package:flutter/material.dart';
class Greeter extends StatefulWidget { class Greeter extends StatefulWidget {
final Future<Trip> trip; final Trip trip;
final bool standalone;
Greeter({ Greeter({
required this.standalone, required this.trip,
required this.trip
}); });
@override @override
@@ -16,43 +17,91 @@ class Greeter extends StatefulWidget {
} }
class _GreeterState extends State<Greeter> { class _GreeterState extends State<Greeter> {
Widget greeterBuild (BuildContext context, AsyncSnapshot<Trip> snapshot) {
Widget greeterBuilder (BuildContext context, Widget? child) {
ThemeData theme = Theme.of(context); ThemeData theme = Theme.of(context);
String cityName = ""; TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24);
if (snapshot.hasData) {
cityName = snapshot.data?.cityName ?? '...';
} else if (snapshot.hasError) {
cityName = "error";
} else { // still awaiting the cityname
cityName = "...";
}
Widget topGreeter = Text( Widget topGreeter;
'Welcome to $cityName!',
style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
);
if (widget.standalone) { if (widget.trip.uuid != 'pending') {
return Center( topGreeter = FutureBuilder(
child: Padding( future: widget.trip.cityName,
padding: EdgeInsets.only(top: 24.0), builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
child: topGreeter, 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 { } else {
return Center( // still awaiting the trip
child: Column( // We can hopefully infer the city name from the cityName future
children: [ // Show a linear loader at the bottom and an info message above
Padding(padding: EdgeInsets.only(top: 24.0)), topGreeter = Column(
topGreeter, mainAxisAlignment: MainAxisAlignment.end,
bottomGreeter, children: [
Padding(padding: EdgeInsets.only(bottom: 24.0)), 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( Widget bottomGreeter = const Text(
@@ -65,9 +114,9 @@ class _GreeterState extends State<Greeter> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return ListenableBuilder(
future: widget.trip, listenable: widget.trip,
builder: greeterBuild, builder: greeterBuilder,
); );
} }
} }

View File

@@ -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'; import 'package:flutter/material.dart';
@@ -31,9 +32,10 @@ class _LandmarkCardState extends State<LandmarkCard> {
height: double.infinity, height: double.infinity,
// force a fixed width // force a fixed width
width: 160, width: 160,
child: Image.network( child: CachedNetworkImage(
widget.landmark.imageURL ?? '', imageUrl: widget.landmark.imageURL ?? '',
errorBuilder: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), 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 // TODO: make this a switch statement to load a placeholder if null
// cover the whole container meaning the image will be cropped // cover the whole container meaning the image will be cropped
fit: BoxFit.cover, fit: BoxFit.cover,

View File

@@ -1,17 +1,16 @@
import 'dart:collection'; import 'dart:developer';
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 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.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 { class LandmarksOverview extends StatefulWidget {
final Future<Trip>? trip; final Trip? trip;
const LandmarksOverview({super.key, this.trip}); const LandmarksOverview({super.key, this.trip});
@override @override
@@ -19,22 +18,37 @@ class LandmarksOverview extends StatefulWidget {
} }
class _LandmarksOverviewState extends State<LandmarksOverview> { class _LandmarksOverviewState extends State<LandmarksOverview> {
// final Future<List<Landmark>> _landmarks = fetchLandmarks();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Future<LinkedList<Landmark>> _landmarks = getLandmarks(widget.trip); return ListenableBuilder(
return DefaultTextStyle( listenable: widget.trip!,
style: Theme.of(context).textTheme.displayMedium!, builder: (BuildContext context, Widget? child) {
textAlign: TextAlign.center, Trip trip = widget.trip!;
child: FutureBuilder<LinkedList<Landmark>>( log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
future: _landmarks,
builder: (BuildContext context, AsyncSnapshot<LinkedList<Landmark>> snapshot) { List<Widget> children;
List<Widget> children;
if (snapshot.hasData) { if (trip.uuid != 'pending' && trip.uuid != 'error') {
children = [landmarksWithSteps(snapshot.data!), saveButton()]; log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
} else if (snapshot.hasError) { if (trip.landmarks.length <= 1) {
children = <Widget>[ 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( const Icon(
Icons.error_outline, Icons.error_outline,
color: Colors.red, color: Colors.red,
@@ -42,20 +56,15 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 16), 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 Column(
return Center( children: children,
child: Column( );
mainAxisAlignment: MainAxisAlignment.center, },
children: children,
),
);
},
),
); );
} }
Widget saveButton() => ElevatedButton( Widget saveButton() => ElevatedButton(
@@ -67,29 +76,56 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
child: const Text('Save'), child: const Text('Save'),
); );
} Widget landmarksWithSteps() {
return ListenableBuilder(
Widget landmarksWithSteps(LinkedList<Landmark> landmarks) { listenable: widget.trip!,
List<Widget> children = []; builder: (BuildContext context, Widget? child) {
for (Landmark landmark in landmarks) { List<Widget> children = [];
children.add(LandmarkCard(landmark)); for (Landmark landmark in widget.trip!.landmarks) {
if (landmark.next != null) { children.add(
Widget step = stepBetweenLandmarks(landmark, landmark.next!); Dismissible(
children.add(step); 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) { Widget stepBetweenLandmarks(Landmark current, Landmark next) {
// This is a simple widget that draws a line between landmark-cards int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5;
// It's a vertical dotted line // ~/ is integer division (rounding)
// 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
return Container( return Container(
margin: EdgeInsets.all(10), margin: EdgeInsets.all(10),
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
@@ -108,7 +144,7 @@ Widget stepBetweenLandmarks(Landmark before, Landmark after) {
Column( Column(
children: [ children: [
Icon(Icons.directions_walk), Icon(Icons.directions_walk),
Text("5 min", style: TextStyle(fontSize: 10)), Text("~$timeRounded min", style: TextStyle(fontSize: 10)),
], ],
), ),
Spacer(), Spacer(),
@@ -116,15 +152,17 @@ Widget stepBetweenLandmarks(Landmark before, Landmark after) {
onPressed: () { onPressed: () {
// Open navigation instructions // 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;
}

View File

@@ -1,13 +1,16 @@
import 'dart:collection'; 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: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:google_maps_flutter/google_maps_flutter.dart';
import 'package:widget_to_marker/widget_to_marker.dart';
class MapWidget extends StatefulWidget { class MapWidget extends StatefulWidget {
final Future<Trip>? trip; final Trip? trip;
MapWidget({ MapWidget({
this.trip this.trip
@@ -19,58 +22,130 @@ class MapWidget extends StatefulWidget {
class _MapWidgetState extends State<MapWidget> { class _MapWidgetState extends State<MapWidget> {
late GoogleMapController mapController; late GoogleMapController mapController;
// coordinates of Paris
CameraPosition _cameraPosition = CameraPosition( CameraPosition _cameraPosition = CameraPosition(
target: LatLng(48.8566, 2.3522), target: LatLng(48.8566, 2.3522),
zoom: 11.0, zoom: 11.0,
); );
Set<Marker> markers = <Marker>{}; Set<Marker> mapMarkers = <Marker>{};
void _onMapCreated(GoogleMapController controller) async { void _onMapCreated(GoogleMapController controller) async {
mapController = controller; mapController = controller;
Trip? trip = await widget.trip; List<double>? newLocation = widget.trip?.landmarks.firstOrNull?.location;
List<double>? newLocation = trip?.landmarks.first.location;
if (newLocation != null) { if (newLocation != null) {
CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1])); CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1]));
controller.moveCamera(update); controller.moveCamera(update);
} }
drawLandmarks(); setMapMarkers();
} }
void _onCameraIdle() { void _onCameraIdle() {
// print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0))); // print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0)));
} }
void drawLandmarks() async { void setMapMarkers() async {
// (re)draws landmarks on the map List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? [];
Trip? trip = await widget.trip; Set<Marker> newMarkers = <Marker>{};
LinkedList<Landmark>? landmarks = trip?.landmarks; for (int i = 0; i < landmarks.length; i++) {
if (landmarks != null){ Landmark landmark = landmarks[i];
setState(() { List<double> location = landmark.location;
for (Landmark landmark in landmarks) { Marker marker = Marker(
markers.add(Marker( markerId: MarkerId(landmark.uuid),
markerId: MarkerId(landmark.name), position: LatLng(location[0], location[1]),
position: LatLng(landmark.location[0], landmark.location[1]), icon: await CustomMarker(landmark: landmark, position: i).toBitmapDescriptor(
infoWindow: InfoWindow(title: landmark.name, snippet: landmark.type.name), logicalSize: const Size(150, 150),
)); imageSize: const Size(150, 150)
} ),
}); );
newMarkers.add(marker);
} }
setState(() {
mapMarkers = newMarkers;
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
widget.trip?.addListener(setMapMarkers);
return GoogleMap( return GoogleMap(
onMapCreated: _onMapCreated, onMapCreated: _onMapCreated,
initialCameraPosition: _cameraPosition, initialCameraPosition: _cameraPosition,
onCameraIdle: _onCameraIdle, onCameraIdle: _onCameraIdle,
// onLongPress: , // onLongPress: ,
markers: markers, markers: mapMarkers,
cloudMapId: '41c21ac9b81dbfd8', 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(),
],
),
);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fast_network_navigation/layout.dart'; import 'package:anyway/layout.dart';
import 'package:fast_network_navigation/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
class TripsOverview extends StatefulWidget { class TripsOverview extends StatefulWidget {
@@ -25,12 +25,23 @@ class _TripsOverviewState extends State<TripsOverview> {
children = List<Widget>.generate(snapshot.data!.length, (index) { children = List<Widget>.generate(snapshot.data!.length, (index) {
Trip trip = snapshot.data![index]; Trip trip = snapshot.data![index];
return ListTile( 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), leading: Icon(Icons.pin_drop),
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BasePage(mainScreen: "map", trip: Future.value(trip)) builder: (context) => BasePage(mainScreen: "map", trip: trip)
) )
); );
}, },

View File

@@ -1,5 +1,13 @@
import 'package:anyway/structs/landmark.dart';
import 'package:flutter/material.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 { class NewTripPage extends StatefulWidget {
const NewTripPage({Key? key}) : super(key: key); const NewTripPage({Key? key}) : super(key: key);
@@ -9,22 +17,77 @@ class NewTripPage extends StatefulWidget {
} }
class _NewTripPageState extends State<NewTripPage> { class _NewTripPageState extends State<NewTripPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController latController = TextEditingController();
final TextEditingController lonController = TextEditingController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('New Trip'), title: const Text('New Trip'),
), ),
body: Center( body: Form(
child: Column( key: _formKey,
mainAxisAlignment: MainAxisAlignment.center, child: Padding(
children: <Widget>[ padding: const EdgeInsets.all(15.0),
const Text( child: Column(
'Create a new trip', 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)
)
);
}
},
),
],
),
)
)
); );
} }
} }

View File

@@ -1,19 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.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:anyway/modules/landmarks_overview.dart';
import 'package:fast_network_navigation/modules/map.dart'; import 'package:anyway/modules/map.dart';
import 'package:fast_network_navigation/modules/greeter.dart'; import 'package:anyway/modules/greeter.dart';
class NavigationOverview extends StatefulWidget { class NavigationOverview extends StatefulWidget {
final Future<Trip> trip; final Trip trip;
NavigationOverview({ NavigationOverview({
required this.trip required this.trip,
}); });
@override @override
@@ -27,53 +27,56 @@ class _NavigationOverviewState extends State<NavigationOverview> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SlidingUpPanel( return SlidingUpPanel(
renderPanelSheet: false,
panel: _floatingPanel(), panel: _floatingPanel(),
collapsed: _floatingCollapsed(), // collapsed: _floatingCollapsed(),
body: MapWidget(trip: widget.trip) 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(){ Widget _floatingCollapsed(){
final ThemeData theme = Theme.of(context); return Greeter(
return Container( trip: widget.trip
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)
); );
} }
Widget _floatingPanel(){ Widget _floatingPanel(){
final ThemeData theme = Theme.of(context); return Column(
return Container( children: [
decoration: BoxDecoration( Padding(
color: Colors.white, padding: const EdgeInsets.all(15),
borderRadius: BorderRadius.all(Radius.circular(24.0)), child:
boxShadow: [ Center(
BoxShadow( child: Container(
blurRadius: 20.0, width: 40,
color: theme.shadowColor, height: 5,
), decoration: BoxDecoration(
] color: Colors.grey[300],
), borderRadius: BorderRadius.all(Radius.circular(12.0)),
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),
],
),
), ),
), Expanded(
), child: ListView(
children: [
Greeter(trip: widget.trip),
LandmarksOverview(trip: widget.trip)
]
)
)
],
); );
} }
} }

View File

@@ -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'; import 'package:flutter/material.dart';
bool debugMode = false;
class ProfilePage extends StatefulWidget { class ProfilePage extends StatefulWidget {
@override @override
@@ -9,6 +11,56 @@ class ProfilePage extends StatefulWidget {
} }
class _ProfilePageState extends State<ProfilePage> { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return ListView(
@@ -24,66 +76,82 @@ class _ProfilePageState extends State<ProfilePage> {
child: Text('Curious traveler', style: TextStyle(fontSize: 24)) child: Text('Curious traveler', style: TextStyle(fontSize: 24))
), ),
Padding(padding: EdgeInsets.all(10)), Divider(indent: 25, endIndent: 25, height: 50),
Divider(indent: 25, endIndent: 25),
Padding(padding: EdgeInsets.all(10)),
Padding( Center(
padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10), child: Padding(
child: Text('Please rate your personal preferences so that we can taylor your experience.', style: TextStyle(fontSize: 18)) 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 FutureBuilder(future: _prefs, builder: futureSliders),
ImportanceSliders() 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 @override
State<ImportanceSliders> createState() => _ImportanceSlidersState(); State<PreferenceSliders> createState() => _PreferenceSlidersState();
} }
class _ImportanceSlidersState extends State<ImportanceSliders> { class _PreferenceSlidersState extends State<PreferenceSliders> {
UserPreferences _prefs = UserPreferences();
List<Card> _createSliders() {
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;
}
@override @override
Widget build(BuildContext context) { 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);
} }
} }

View File

@@ -3,6 +3,15 @@ import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; 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>{ final class Landmark extends LinkedListEntry<Landmark>{
// A linked node of a list of Landmarks // A linked node of a list of Landmarks
final String uuid; final String uuid;
@@ -47,7 +56,7 @@ final class Landmark extends LinkedListEntry<Landmark>{
'location': List<dynamic> location, 'location': List<dynamic> location,
'type': String type, 'type': String type,
}) { }) {
// refine the parsing on a few // refine the parsing on a few fields
List<double> locationFixed = List<double>.from(location); List<double> locationFixed = List<double>.from(location);
// parse the rest separately, they could be missing // parse the rest separately, they could be missing
LandmarkType typeFixed = LandmarkType(name: type); LandmarkType typeFixed = LandmarkType(name: type);
@@ -55,11 +64,12 @@ final class Landmark extends LinkedListEntry<Landmark>{
final imageURL = json['image_url'] as String?; final imageURL = json['image_url'] as String?;
final description = json['description'] as String?; final description = json['description'] as String?;
var duration = Duration(minutes: json['duration'] ?? 0) as Duration?; 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?; final visited = json['visited'] as bool?;
var tripTime = Duration(minutes: json['time_to_reach_next'] ?? 0) as Duration?;
return Landmark( 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 { } else {
throw FormatException('Invalid JSON: $json'); throw FormatException('Invalid JSON: $json');
} }
@@ -81,7 +91,8 @@ final class Landmark extends LinkedListEntry<Landmark>{
'image_url': imageURL, 'image_url': imageURL,
'description': description, 'description': description,
'duration': duration?.inMinutes, 'duration': duration?.inMinutes,
'visited': visited 'visited': visited,
'trip_time': tripTime?.inMinutes,
}; };
} }
@@ -96,6 +107,14 @@ class LandmarkType {
// required this.description, // required this.description,
// required this.icon, // required this.icon,
}); });
@override
bool operator ==(Object other) {
if (other is LandmarkType) {
return name == other.name;
} else {
return false;
}
}
} }

View File

@@ -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;
// }
// }
// }

View File

@@ -3,80 +3,100 @@ import 'package:shared_preferences/shared_preferences.dart';
class SinglePreference { class SinglePreference {
String slug;
String name; String name;
String description; String description;
int value; int value;
int minVal;
int maxVal;
Icon icon; Icon icon;
String key;
SinglePreference({ SinglePreference({
required this.slug,
required this.name, required this.name,
required this.description, required this.description,
required this.value, required this.value,
required this.icon, 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 { void save() async {
SharedPreferences sharedPrefs = await SharedPreferences.getInstance(); SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
for (SinglePreference pref in preferences) { sharedPrefs.setInt('pref_$slug', value);
sharedPrefs.setInt(pref.key, pref.value);
}
} }
void load() async { void load() async {
SharedPreferences sharedPrefs = await SharedPreferences.getInstance(); SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
for (SinglePreference pref in preferences) { value = sharedPrefs.getInt('pref_$slug') ?? minVal;
pref.value = sharedPrefs.getInt(pref.key) ?? 0; }
}
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;
} }

View File

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

View File

@@ -4,32 +4,72 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:convert'; 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'; import 'package:shared_preferences/shared_preferences.dart';
class Trip { class Trip with ChangeNotifier {
final String uuid; String uuid;
final String cityName; int totalTime;
// TODO: cityName should be inferred from coordinates of the Landmarks LinkedList<Landmark> landmarks;
final LinkedList<Landmark> landmarks;
// could be empty as well // 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({ Trip({
required this.uuid, this.uuid = 'pending',
required this.cityName, this.totalTime = 0,
required this.landmarks, 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) { factory Trip.fromJson(Map<String, dynamic> json) {
return Trip( Trip trip = Trip(
uuid: json['uuid'], uuid: json['uuid'],
cityName: json['city_name'], totalTime: json['total_time'],
landmarks: LinkedList()
); );
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) { factory Trip.fromPrefs(SharedPreferences prefs, String uuid) {
String? content = prefs.getString('trip_$uuid'); String? content = prefs.getString('trip_$uuid');
@@ -43,8 +83,8 @@ class Trip {
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'uuid': uuid, 'uuid': uuid,
'city_name': cityName, 'total_time': totalTime,
'entry_uuid': landmarks.first?.uuid ?? '' 'first_landmark_uuid': landmarks.first.uuid
}; };

View File

@@ -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°5129.6″N 2°1740.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');
// // }
// }

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

View File

@@ -1,7 +1,7 @@
import 'dart:collection'; import 'dart:collection';
import 'package:fast_network_navigation/structs/trip.dart'; import 'package:anyway/structs/trip.dart';
import 'package:fast_network_navigation/structs/landmark.dart'; import 'package:anyway/structs/landmark.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
Future<List<Trip>> loadTrips() async { Future<List<Trip>> loadTrips() async {
@@ -17,7 +17,7 @@ Future<List<Trip>> loadTrips() async {
} }
if (trips.isEmpty) { if (trips.isEmpty) {
Trip t1 = Trip(uuid: '1', cityName: 'Paris', landmarks: LinkedList<Landmark>()); Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>());
t1.landmarks.add( t1.landmarks.add(
Landmark( Landmark(
uuid: '1', uuid: '1',
@@ -66,7 +66,7 @@ Future<List<Trip>> loadTrips() async {
trips.add(t1); trips.add(t1);
Trip t2 = Trip(uuid: '2', cityName: 'Vienna', landmarks: LinkedList<Landmark>()); Trip t2 = Trip(uuid: '2', landmarks: LinkedList<Landmark>());
t2.landmarks.add( t2.landmarks.add(
Landmark( Landmark(

View File

@@ -4,10 +4,10 @@ project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change # The name of the executable created for the application. Change this to change
# the on-disk name of your application. # 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: # The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID # 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 # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.

View File

@@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) { if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); 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_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else { } else {
gtk_window_set_title(window, "fast_network_navigation"); gtk_window_set_title(window, "anyway");
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);

View File

@@ -5,8 +5,12 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import path_provider_foundation
import shared_preferences_foundation import shared_preferences_foundation
import sqflite
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
} }

View File

@@ -64,7 +64,7 @@
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@@ -131,7 +131,7 @@
33CC10EE2044A3C60003C045 /* Products */ = { 33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
33CC10ED2044A3C60003C045 /* fast_network_navigation.app */, 33CC10ED2044A3C60003C045 /* anyway.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */, 331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
); );
name = Products; name = Products;
@@ -217,7 +217,7 @@
); );
name = Runner; name = Runner;
productName = Runner; productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* fast_network_navigation.app */; productReference = 33CC10ED2044A3C60003C045 /* anyway.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@@ -388,7 +388,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; 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; name = Debug;
}; };
@@ -402,7 +402,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; 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; name = Release;
}; };
@@ -416,7 +416,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; 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; name = Profile;
}; };

View File

@@ -15,7 +15,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "fast_network_navigation.app" BuildableName = "anyway.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@@ -31,7 +31,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "fast_network_navigation.app" BuildableName = "anyway.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@@ -65,7 +65,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "fast_network_navigation.app" BuildableName = "anyway.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@@ -82,7 +82,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "fast_network_navigation.app" BuildableName = "anyway.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>

View File

@@ -5,7 +5,7 @@
// 'flutter create' template. // 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window. // 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 // The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation PRODUCT_BUNDLE_IDENTIFIER = com.example.fastNetworkNavigation

View File

@@ -9,6 +9,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" 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: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -17,6 +25,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" 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: characters:
dependency: transitive dependency: transitive
description: description:
@@ -41,6 +73,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@@ -57,6 +97,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" 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: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -81,11 +137,27 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -98,10 +170,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.19" version: "2.0.21"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -112,54 +184,86 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: google_maps:
dependency: transitive dependency: transitive
description: description:
name: google_maps name: google_maps
sha256: "47eef3836b49bb030d5cb3afc60b8451408bf34cf753e571b645d6529eb4251a" sha256: "463b38e5a92a05cde41220a11fd5eef3847031fef3e8cf295ac76ec453246907"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.1.0" version: "8.0.0"
google_maps_flutter: google_maps_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: google_maps_flutter name: google_maps_flutter
sha256: c1972cbad779bc5346c49045f26ae45550a0958b1cbca5b524dd3c8954995d28 sha256: acf0ec482d86b2ac55ade80597ce7f797a47971f5210ebfd030f0d58130e0a94
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.1" version: "2.7.0"
google_maps_flutter_android: google_maps_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_android name: google_maps_flutter_android
sha256: "0bcadb80eba39afda77dede89a6caafd3b68f2786b90491eceea4a01c3db181c" sha256: "5d444f4135559488d7ea325eae710ae3284e6951b1b61729a0ac026456fe1548"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.8.0" version: "2.12.1"
google_maps_flutter_ios: google_maps_flutter_ios:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_ios name: google_maps_flutter_ios
sha256: e5132d17f051600d90d79d9f574b177c24231da702453a036db2490f9ced4646 sha256: a6e3c6ecdda6c985053f944be13a0645ebb919da2ef0f5bc579c5e1670a5b2a8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.0" version: "2.10.0"
google_maps_flutter_platform_interface: google_maps_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_platform_interface name: google_maps_flutter_platform_interface
sha256: "167af879da4d004cd58771f1469b91dcc3b9b0a2c5334cc6bf71fd41d4b35403" sha256: bd60ca330e3c7763b95b477054adec338a522d982af73ecc520b232474063ac5
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.0" version: "2.8.0"
google_maps_flutter_web: google_maps_flutter_web:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_web name: google_maps_flutter_web
sha256: "0c0d5c723d94b295cf86dd1c45ff91d2ac1fff7c05ddca4f01bef9fa0a014690" sha256: "8d5d0f58bfc4afac0bbe3d399f2018fcea691e3ea3d35254b7aae56df5827659"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.7" version: "0.5.9+1"
html: html:
dependency: transitive dependency: transitive
description: description:
@@ -172,10 +276,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.2"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@@ -184,22 +288,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" 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: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -256,6 +344,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.12.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: path:
dependency: transitive dependency: transitive
description: description:
@@ -264,6 +368,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" 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: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@@ -284,18 +412,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.3.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.4" version: "3.1.5"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -304,6 +432,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" 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: sanitize_html:
dependency: transitive dependency: transitive
description: description:
@@ -316,58 +460,58 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.3" version: "2.3.0"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.3.0"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.5.0"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.0"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.1"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.4.0"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -389,6 +533,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" 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: stack_trace:
dependency: transitive dependency: transitive
description: description:
@@ -421,6 +589,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" 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: term_glyph:
dependency: transitive dependency: transitive
description: description:
@@ -445,6 +621,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
uuid:
dependency: transitive
description:
name: uuid
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
url: "https://pub.dev"
source: hosted
version: "4.4.2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -469,14 +653,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.5.1"
win32: widget_to_marker:
dependency: transitive dependency: "direct main"
description: description:
name: win32 name: widget_to_marker
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 sha256: badc36f23c76f3ca9d43d7780058096be774adf0f661bdb6eb6f6b893f648ab9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.5.1" version: "1.0.6"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@@ -487,4 +671,4 @@ packages:
version: "1.0.4" version: "1.0.4"
sdks: sdks:
dart: ">=3.4.0 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.19.0" flutter: ">=3.22.0"

View File

@@ -1,5 +1,5 @@
name: "fast_network_navigation" name: "anyway"
description: "An interactive city navigator." description: "A customizable, agile city navigator for your trips."
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # 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 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. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
sliding_up_panel: ^2.0.0+1 sliding_up_panel: ^2.0.0+1
google_maps_flutter: ^2.6.1
http: ^1.2.1 http: ^1.2.1
shared_preferences: ^2.2.3 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: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -8,8 +8,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
// import 'package:fast_network_navigation/main.dart'; // import 'package:anyway/main.dart';
import 'package:fast_network_navigation/layout.dart'; import 'package:anyway/layout.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('Counter increments smoke test', (WidgetTester tester) async {

View File

@@ -24,13 +24,13 @@
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes"> <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-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"> <link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<title>fast_network_navigation</title> <title>anyway</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<script> <script>

View File

@@ -1,6 +1,6 @@
{ {
"name": "fast_network_navigation", "name": "anyway",
"short_name": "fast_network_navigation", "short_name": "anyway",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#0175C2",

View File

@@ -1,10 +1,10 @@
# Project-level configuration. # Project-level configuration.
cmake_minimum_required(VERSION 3.14) 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 name of the executable created for the application. Change this to change
# the on-disk name of your application. # 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 # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.

View File

@@ -90,12 +90,12 @@ BEGIN
BLOCK "040904e4" BLOCK "040904e4"
BEGIN BEGIN
VALUE "CompanyName", "com.example" "\0" VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "fast_network_navigation" "\0" VALUE "FileDescription", "anyway" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\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 "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "fast_network_navigation.exe" "\0" VALUE "OriginalFilename", "anyway.exe" "\0"
VALUE "ProductName", "fast_network_navigation" "\0" VALUE "ProductName", "anyway" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0"
END END
END END

View File

@@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.Create(L"fast_network_navigation", origin, size)) { if (!window.Create(L"anyway", origin, size)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);