Compare commits
1 Commits
v0.0.22
...
ed50eba483
Author | SHA1 | Date | |
---|---|---|---|
ed50eba483 |
@@ -18,7 +18,7 @@ jobs:
|
||||
name: Deploy to production
|
||||
uses: ./.gitea/workflows/workflow_deploy-container.yaml
|
||||
with:
|
||||
overlay: prod
|
||||
environment: prod
|
||||
secrets:
|
||||
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
|
||||
PACKAGE_REGISTRY_ACCESS: ${{ secrets.KUBE_CONFIG }}
|
||||
needs: build-and-push
|
||||
|
@@ -20,7 +20,7 @@ jobs:
|
||||
name: Deploy to staging
|
||||
uses: ./.gitea/workflows/workflow_deploy-container.yaml
|
||||
with:
|
||||
overlay: stg
|
||||
environment: stg
|
||||
secrets:
|
||||
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
|
||||
PACKAGE_REGISTRY_ACCESS: ${{ secrets.KUBE_CONFIG }}
|
||||
needs: build-and-push
|
||||
|
@@ -34,5 +34,5 @@ jobs:
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: backend
|
||||
tags: git.kluster.moll.re/anydev/anyway-backend:${{ inputs.tag }}
|
||||
tags: git.kluster.moll.re/anydev/anyway-backend:${{inputs.tag}}
|
||||
push: true
|
||||
|
@@ -32,4 +32,3 @@ jobs:
|
||||
- name: Deploy to k8s
|
||||
run: |
|
||||
kubectl apply -k backend/deployment/overlays/${{ inputs.overlay }} --kubeconfig=kubeconfig
|
||||
kubectl -n anyway-backend rollout restart deployment/anyway-backend-${{ inputs.overlay }}
|
||||
|
@@ -9,11 +9,9 @@ This repository contains the backend code for the application. It utilizes FastA
|
||||
- Since the application is aimed to be deployed in a container, the `Dockerfile` is provided to build the image.
|
||||
|
||||
### Deployment
|
||||
To deploy the backend docker container, we use kubernetes. Modifications to the backend are automatically pushed to a two-stage environment through the CI pipeline. See [deployment/README](deployment/README.md] for further information.
|
||||
|
||||
The deployment configuration is included as a submodule in the `deployment` directory. The standalone repository is under [https://git.kluster.moll.re/anydev/anyway-backend-deployment/](https://git.kluster.moll.re/anydev/anyway-backend-deployment/).
|
||||
To deploy the backend docker container, we use kubernetes. The deployment configuration is located under [https://git.kluster.moll.re/anydev/deployment-backend/](https://git.kluster.moll.re/anydev/deployment-backend/).
|
||||
|
||||
|
||||
## Development
|
||||
TBD
|
||||
|
||||
Test for pull request
|
@@ -2,5 +2,5 @@ detour_factor: 1.4
|
||||
detour_corridor_width: 300
|
||||
average_walking_speed: 4.8
|
||||
max_landmarks: 10
|
||||
max_landmarks_refiner: 30
|
||||
overshoot: 1.8
|
||||
max_landmarks_refiner: 20
|
||||
overshoot: 1.3
|
||||
|
@@ -15,11 +15,8 @@ class Landmark(BaseModel) :
|
||||
attractiveness : int
|
||||
n_tags : int
|
||||
image_url : Optional[str] = None # TODO future
|
||||
website_url : Optional[str] = None
|
||||
wikipedia_url : Optional[str] = None
|
||||
description : Optional[str] = None # TODO future
|
||||
duration : Optional[int] = 0 # TODO future
|
||||
name_en : Optional[str] = None
|
||||
|
||||
# Unique ID of a given landmark
|
||||
uuid: str = Field(default_factory=uuid4) # TODO implement this ASAP
|
||||
@@ -36,9 +33,7 @@ class Landmark(BaseModel) :
|
||||
return self.uuid.int
|
||||
|
||||
def __str__(self) -> str:
|
||||
time_to_next_str = f", time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else ""
|
||||
is_secondary_str = f", secondary" if self.is_secondary else ""
|
||||
type_str = '(' + self.type + ')'
|
||||
if self.type in ["start", "finish", "nature", "shopping"] : type_str += '\t '
|
||||
return f'Landmark{type_str}: [{self.name} @{self.location}, score={self.attractiveness}{time_to_next_str}{is_secondary_str}]'
|
||||
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'({self.type[:4]}), score={self.attractiveness}\tmain:{not self.is_secondary}\tduration={self.duration}\t{time_to_next_str}\t{self.name}'
|
||||
|
||||
|
@@ -52,7 +52,7 @@ class LinkedLandmarks:
|
||||
|
||||
# Update 'is_secondary' for landmarks with attractiveness below the threshold score
|
||||
for landmark in self._landmarks:
|
||||
if landmark.attractiveness < threshold_score and landmark.type not in ["start", "finish"]:
|
||||
if landmark.attractiveness < threshold_score:
|
||||
landmark.is_secondary = True
|
||||
|
||||
|
||||
|
@@ -23,8 +23,9 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
|
||||
sightseeing=Preference(type='sightseeing', score = 5),
|
||||
nature=Preference(type='nature', score = 5),
|
||||
shopping=Preference(type='shopping', score = 5),
|
||||
max_time_minute=100,
|
||||
detour_tolerance_minute=0
|
||||
|
||||
max_time_minute=300,
|
||||
detour_tolerance_minute=15
|
||||
)
|
||||
|
||||
# Create start and finish
|
||||
@@ -63,7 +64,10 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
|
||||
logger.info("Optimized route : ")
|
||||
for l in linked_tour :
|
||||
logger.info(f"{l}")
|
||||
logger.info(f"Estimated length of tour : {linked_tour.total_time} mintutes and visiting {len(linked_tour._landmarks)} landmarks.")
|
||||
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:
|
||||
# yaml.dump(linked_tour.asdict(), f)
|
||||
@@ -74,6 +78,6 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
|
||||
# test(tuple((48.8344400, 2.3220540))) # Café Chez César
|
||||
# test(tuple((48.8375946, 2.2949904))) # Point random
|
||||
# test(tuple((47.377859, 8.540585))) # Zurich HB
|
||||
# test(tuple((45.758217, 4.831814))) # Lyon Bellecour
|
||||
test(tuple((48.5848435, 7.7332974))) # Strasbourg Gare
|
||||
test(tuple((45.7576485, 4.8330241))) # Lyon Bellecour
|
||||
# test(tuple((48.5848435, 7.7332974))) # Strasbourg Gare
|
||||
# test(tuple((48.2067858, 16.3692340))) # Vienne
|
||||
|
@@ -81,19 +81,19 @@ class LandmarkManager:
|
||||
bbox = self.create_bbox(center_coordinates, reachable_bbox_side)
|
||||
# list for sightseeing
|
||||
if preferences.sightseeing.score != 0:
|
||||
score_function = lambda score: int(score*10*preferences.sightseeing.score/5) # self.count_elements_close_to(loc) +
|
||||
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'], preferences.sightseeing.type, score_function)
|
||||
L += L1
|
||||
|
||||
# list for nature
|
||||
if preferences.nature.score != 0:
|
||||
score_function = lambda score: int(score*10*self.nature_coeff*preferences.nature.score/5) # self.count_elements_close_to(loc) +
|
||||
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'], preferences.nature.type, score_function)
|
||||
L += L2
|
||||
|
||||
# list for shopping
|
||||
if preferences.shopping.score != 0:
|
||||
score_function = lambda score: int(score*10*preferences.shopping.score/5) # self.count_elements_close_to(loc) +
|
||||
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'], preferences.shopping.type, score_function)
|
||||
L += L3
|
||||
|
||||
@@ -290,10 +290,7 @@ class LandmarkManager:
|
||||
elem_type = landmarktype # Add the landmark type as 'sightseeing,
|
||||
n_tags = len(elem.tags().keys()) # Add number of tags
|
||||
score = n_tags**self.tag_exponent # Add score
|
||||
website_url = None
|
||||
wikpedia_url = None
|
||||
image_url = None
|
||||
name_en = None
|
||||
|
||||
|
||||
# remove specific tags
|
||||
skip = False
|
||||
@@ -307,7 +304,7 @@ class LandmarkManager:
|
||||
|
||||
if "wiki" in tag:
|
||||
score += self.wikipedia_bonus # wikipedia entries count more
|
||||
|
||||
|
||||
# if tag == "wikidata":
|
||||
# Q = elem.tag('wikidata')
|
||||
# site = Site("wikidata", "wikidata")
|
||||
@@ -318,7 +315,6 @@ class LandmarkManager:
|
||||
|
||||
if "viewpoint" in tag:
|
||||
score += self.viewpoint_bonus
|
||||
duration = 10
|
||||
|
||||
if "image" in tag:
|
||||
score += self.image_bonus
|
||||
@@ -335,31 +331,26 @@ class LandmarkManager:
|
||||
if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']:
|
||||
skip = True
|
||||
break
|
||||
|
||||
# Get additional information
|
||||
# if tag == 'wikipedia' :
|
||||
# wikpedia_url = elem.tag('wikipedia')
|
||||
if tag in ['website', 'contact:website'] :
|
||||
website_url = elem.tag(tag)
|
||||
if tag == 'image' :
|
||||
image_url = elem.tag('image')
|
||||
if tag =='name:en' :
|
||||
name_en = elem.tag('name:en')
|
||||
|
||||
if skip:
|
||||
continue
|
||||
|
||||
score = score_function(score)
|
||||
if "place_of_worship" in elem.tags().values() :
|
||||
score = int(score*self.church_coeff)
|
||||
duration = 15
|
||||
duration = 20
|
||||
|
||||
elif "museum" in elem.tags().values() :
|
||||
score = int(score*self.church_coeff)
|
||||
duration = 60
|
||||
|
||||
else :
|
||||
elif "fountain" in elem.tags().values() :
|
||||
duration = 5
|
||||
|
||||
elif "park" in elem.tags().values() :
|
||||
duration = 30
|
||||
|
||||
else :
|
||||
duration = 15
|
||||
|
||||
# Generate the landmark and append it to the list
|
||||
landmark = Landmark(
|
||||
@@ -371,11 +362,7 @@ class LandmarkManager:
|
||||
attractiveness=score,
|
||||
must_do=False,
|
||||
n_tags=int(n_tags),
|
||||
duration = duration,
|
||||
name_en=name_en,
|
||||
image_url=image_url,
|
||||
# wikipedia_url=wikpedia_url,
|
||||
website_url=website_url
|
||||
duration = duration
|
||||
)
|
||||
return_list.append(landmark)
|
||||
|
||||
|
@@ -21,7 +21,7 @@ class Optimizer:
|
||||
detour_factor: float # detour factor of straight line vs real distance in cities
|
||||
average_walking_speed: float # average walking speed of adult
|
||||
max_landmarks: int # max number of landmarks to visit
|
||||
overshoot: float # overshoot to allow maxtime to overflow. Optimizer is a bit restrictive
|
||||
overshoot: float # experimentally determined overshoot possibility to return long enough tours
|
||||
|
||||
|
||||
def __init__(self) :
|
||||
@@ -178,7 +178,7 @@ class Optimizer:
|
||||
|
||||
Args:
|
||||
landmarks (list[Landmark]): List of landmarks.
|
||||
max_time (int): Maximum time of visit allowed.
|
||||
max_time (int): Maximum time allowed for tour.
|
||||
|
||||
Returns:
|
||||
Tuple[list[float], list[float], list[int]]: Objective function coefficients, inequality constraint coefficients, and the right-hand side of the inequality constraint.
|
||||
@@ -195,7 +195,7 @@ class Optimizer:
|
||||
for j, spot2 in enumerate(landmarks) :
|
||||
t = get_time(spot1.location, spot2.location) + spot1.duration
|
||||
dist_table[j] = t
|
||||
closest = sorted(dist_table)[:25]
|
||||
closest = sorted(dist_table)[:22]
|
||||
for i, dist in enumerate(dist_table) :
|
||||
if dist not in closest :
|
||||
dist_table[i] = 32700
|
||||
@@ -476,7 +476,7 @@ class Optimizer:
|
||||
A, b = self.respect_start_finish(L) # Force start and finish positions
|
||||
A_eq = np.vstack((A_eq, A), dtype=np.int8)
|
||||
b_eq += b
|
||||
A, b = self.respect_order(L) # Respect order of visit (only works when max_time is limiting factor)
|
||||
A, b = self.respect_order(L) # Respect order of visit (only works when max_steps is limiting factor)
|
||||
A_eq = np.vstack((A_eq, A), dtype=np.int8)
|
||||
b_eq += b
|
||||
|
||||
|
@@ -214,7 +214,7 @@ class Refiner :
|
||||
if self.is_in_area(area, landmark.location) and landmark.name not in visited_names:
|
||||
second_order_landmarks.append(landmark)
|
||||
|
||||
return take_most_important.take_most_important(second_order_landmarks, int(self.max_landmarks_refiner*0.75))
|
||||
return take_most_important.take_most_important(second_order_landmarks, len(visited_landmarks))
|
||||
|
||||
|
||||
# Try fix the shortest path using shapely
|
||||
|
@@ -1,7 +1,7 @@
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -23,13 +23,6 @@ jobs:
|
||||
|
||||
- name: Setup android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: stable
|
||||
flutter-version: 3.22.0
|
||||
cache: true
|
||||
|
||||
- name: Infer version number from git tag
|
||||
id: version
|
||||
@@ -37,14 +30,13 @@ jobs:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run:
|
||||
# remove the 'v' prefix from the tag name
|
||||
echo "VERSION_NAME=${REF_NAME//v}" >> $GITHUB_ENV
|
||||
echo "VERSION=${REF_NAME//v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Load secrets from github
|
||||
run: |
|
||||
echo "${{ secrets.ANDROID_SECRET_PROPERTIES }}" > secrets.properties
|
||||
echo "${{ secrets.ANDROID_KEYSTORE }}" > release.keystore
|
||||
echo "${{ secrets.ANDROID_GOOGLE_PLAY_JSON }}" > google-key.json
|
||||
# decode the base64 encoded google key
|
||||
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d - > release.keystore
|
||||
working-directory: android
|
||||
|
||||
- name: Install fastlane
|
||||
@@ -52,6 +44,7 @@ jobs:
|
||||
working-directory: android
|
||||
|
||||
- name: Run fastlane lane
|
||||
run: bundle exec fastlane deploy_testing
|
||||
run: bundle exec fastlane deploy_release
|
||||
working-directory: android
|
||||
# the environment variable VERSION_NAME is implicitly available
|
||||
env:
|
||||
VERSION_NAME: ${{ steps.version.VERSION }}
|
||||
|
4
frontend/android/.gitignore
vendored
@@ -1,6 +1,4 @@
|
||||
gradlew
|
||||
gradlew.bat
|
||||
gradle/
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/local.properties
|
||||
|
@@ -1,9 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Required to fetch data from the internet. -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<!-- Required to show user location -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<application
|
||||
android:label="anyway"
|
||||
|
@@ -4,19 +4,18 @@
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
# desc "Runs all the tests"
|
||||
# lane :test do
|
||||
# gradle(task: "test")
|
||||
# end
|
||||
|
||||
desc "Deploy a new version as a preview version"
|
||||
lane :deploy_testing do
|
||||
version_name = ENV["VERSION_NAME"]
|
||||
|
||||
sh(
|
||||
"flutter",
|
||||
"build",
|
||||
"appbundle",
|
||||
"--release",
|
||||
"--build-name=#{version_name}",
|
||||
gradle(
|
||||
task: "bundle",
|
||||
# flavor: "staging",
|
||||
)
|
||||
|
||||
|
||||
upload_to_play_store(
|
||||
track: 'alpha',
|
||||
skip_upload_apk: true,
|
||||
|
After Width: | Height: | Size: 638 KiB |
After Width: | Height: | Size: 893 KiB |
After Width: | Height: | Size: 876 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 637 KiB |
Before Width: | Height: | Size: 573 KiB |
Before Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 360 KiB |
After Width: | Height: | Size: 638 KiB |
After Width: | Height: | Size: 893 KiB |
After Width: | Height: | Size: 876 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 637 KiB |
Before Width: | Height: | Size: 573 KiB |
Before Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 360 KiB |
After Width: | Height: | Size: 638 KiB |
After Width: | Height: | Size: 893 KiB |
After Width: | Height: | Size: 876 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 637 KiB |
Before Width: | Height: | Size: 573 KiB |
Before Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 360 KiB |
5
frontend/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
160
frontend/android/gradlew
vendored
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
@@ -20,7 +20,7 @@ pluginManagement {
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
@@ -1,85 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const String APP_NAME = 'AnyWay';
|
||||
|
||||
String API_URL_BASE = 'https://anyway.anydev.info';
|
||||
String PRIVACY_URL = 'https://anydev.info/privacy';
|
||||
|
||||
const String MAP_ID = '41c21ac9b81dbfd8';
|
||||
|
||||
|
||||
const Color GRADIENT_START = Color(0xFFF9B208);
|
||||
const Color GRADIENT_END = Color(0xFFE72E77);
|
||||
|
||||
const Color PRIMARY_COLOR = Color(0xFFF38F1A);
|
||||
|
||||
|
||||
|
||||
const double TRIP_PANEL_MAX_HEIGHT = 0.8;
|
||||
const double TRIP_PANEL_MIN_HEIGHT = 0.12;
|
||||
|
||||
ThemeData APP_THEME = ThemeData(
|
||||
primaryColor: PRIMARY_COLOR,
|
||||
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
cardColor: Colors.white,
|
||||
useMaterial3: true,
|
||||
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: PRIMARY_COLOR,
|
||||
secondary: GRADIENT_END,
|
||||
surface: Colors.white,
|
||||
error: Colors.red,
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: const Color.fromARGB(255, 30, 22, 22),
|
||||
onSurface: Colors.black,
|
||||
onError: Colors.white,
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
|
||||
|
||||
textButtonTheme: const TextButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
|
||||
side: WidgetStatePropertyAll(
|
||||
BorderSide(
|
||||
color: PRIMARY_COLOR,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
|
||||
elevatedButtonTheme: const ElevatedButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
|
||||
)
|
||||
),
|
||||
|
||||
outlinedButtonTheme: const OutlinedButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
cardTheme: const CardTheme(
|
||||
shadowColor: Colors.grey,
|
||||
elevation: 2,
|
||||
margin: EdgeInsets.all(10),
|
||||
),
|
||||
|
||||
sliderTheme: const SliderThemeData(
|
||||
trackHeight: 15,
|
||||
inactiveTrackColor: Colors.grey,
|
||||
thumbColor: PRIMARY_COLOR,
|
||||
activeTrackColor: GRADIENT_END
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
const Gradient APP_GRADIENT = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [GRADIENT_START, GRADIENT_END],
|
||||
);
|
@@ -1,4 +1,3 @@
|
||||
import 'package:anyway/pages/settings.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:anyway/constants.dart';
|
||||
@@ -7,9 +6,10 @@ import 'package:anyway/structs/trip.dart';
|
||||
import 'package:anyway/modules/trips_saved_list.dart';
|
||||
import 'package:anyway/utils/load_trips.dart';
|
||||
|
||||
import 'package:anyway/pages/new_trip_location.dart';
|
||||
import 'package:anyway/pages/new_trip.dart';
|
||||
import 'package:anyway/pages/current_trip.dart';
|
||||
import 'package:anyway/pages/onboarding.dart';
|
||||
import 'package:anyway/pages/profile.dart';
|
||||
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class _BasePageState extends State<BasePage> {
|
||||
)
|
||||
);
|
||||
},
|
||||
label: Text("Plan a trip"),
|
||||
label: Text("Plan a trip now"),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -74,33 +74,33 @@ class _BasePageState extends State<BasePage> {
|
||||
}
|
||||
} else if (widget.mainScreen == "tutorial") {
|
||||
currentView = OnboardingPage();
|
||||
} else if (widget.mainScreen == "settings") {
|
||||
currentView = SettingsPage();
|
||||
} else if (widget.mainScreen == "profile") {
|
||||
currentView = ProfilePage();
|
||||
}
|
||||
|
||||
final ThemeData theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(APP_NAME)),
|
||||
body: Center(child: currentView),
|
||||
drawer: Drawer(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
DrawerHeader(
|
||||
decoration: BoxDecoration(
|
||||
gradient: APP_GRADIENT,
|
||||
gradient: LinearGradient(colors: [Colors.red, Colors.yellow])
|
||||
),
|
||||
height: 150,
|
||||
child: Center(
|
||||
child: Text(
|
||||
APP_NAME,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
color: Colors.grey[800],
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
ListTile(
|
||||
title: const Text('Your Trips'),
|
||||
leading: const Icon(Icons.map),
|
||||
@@ -130,7 +130,7 @@ class _BasePageState extends State<BasePage> {
|
||||
},
|
||||
child: const Text('Clear trips'),
|
||||
),
|
||||
const Divider(indent: 10, endIndent: 10),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: const Text('How to use'),
|
||||
leading: Icon(Icons.help),
|
||||
@@ -148,11 +148,11 @@ class _BasePageState extends State<BasePage> {
|
||||
ListTile(
|
||||
title: const Text('Settings'),
|
||||
leading: const Icon(Icons.settings),
|
||||
selected: widget.mainScreen == "settings",
|
||||
selected: widget.mainScreen == "profile",
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BasePage(mainScreen: "settings")
|
||||
builder: (context) => BasePage(mainScreen: "profile")
|
||||
)
|
||||
);
|
||||
},
|
||||
|
@@ -15,7 +15,7 @@ class App extends StatelessWidget {
|
||||
return MaterialApp(
|
||||
title: APP_NAME,
|
||||
home: BasePage(mainScreen: "map"),
|
||||
theme: APP_THEME,
|
||||
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]),
|
||||
scaffoldMessengerKey: rootScaffoldMessengerKey
|
||||
);
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
|
||||
@@ -21,12 +20,8 @@ class Greeter extends StatefulWidget {
|
||||
class _GreeterState extends State<Greeter> {
|
||||
|
||||
Widget greeterBuilder (BuildContext context, Widget? child) {
|
||||
final Shader textGradient = APP_GRADIENT.createShader(Rect.fromLTWH(0.0, 0.0, 200.0, 70.0));
|
||||
TextStyle greeterStyle = TextStyle(
|
||||
foreground: Paint()..shader = textGradient,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 26
|
||||
);
|
||||
ThemeData theme = Theme.of(context);
|
||||
TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24);
|
||||
|
||||
Widget topGreeter;
|
||||
|
||||
@@ -96,10 +91,26 @@ class _GreeterState extends State<Greeter> {
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: topGreeter,
|
||||
child: Column(
|
||||
children: [
|
||||
// Padding(padding: EdgeInsets.only(top: 20)),
|
||||
topGreeter,
|
||||
Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: bottomGreeter
|
||||
),
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomGreeter = const Text(
|
||||
"Busy day ahead? Here is how to make the most of it!",
|
||||
style: TextStyle(color: Colors.black, fontSize: 18),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@@ -16,7 +16,7 @@ List<Widget> landmarksList(Trip trip) {
|
||||
|
||||
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
|
||||
|
||||
if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == typeStart ) {
|
||||
if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == start ) {
|
||||
children.add(
|
||||
const Text("No landmarks in this trip"),
|
||||
);
|
||||
@@ -32,6 +32,7 @@ List<Widget> landmarksList(Trip trip) {
|
||||
onDismissed: (direction) {
|
||||
log('Removing ${landmark.name}');
|
||||
trip.removeLandmark(landmark);
|
||||
// Then show a snackbar
|
||||
|
||||
rootScaffoldMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(content: Text("We won't show ${landmark.name} again"))
|
||||
|
@@ -1,28 +1,27 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/modules/landmark_map_marker.dart';
|
||||
import 'package:anyway/modules/themed_marker.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:shared_preferences/shared_preferences.dart';
|
||||
import 'package:widget_to_marker/widget_to_marker.dart';
|
||||
|
||||
|
||||
class CurrentTripMap extends StatefulWidget {
|
||||
class MapWidget extends StatefulWidget {
|
||||
|
||||
final Trip? trip;
|
||||
|
||||
CurrentTripMap({
|
||||
MapWidget({
|
||||
this.trip
|
||||
});
|
||||
|
||||
@override
|
||||
State<CurrentTripMap> createState() => _CurrentTripMapState();
|
||||
State<MapWidget> createState() => _MapWidgetState();
|
||||
}
|
||||
|
||||
class _CurrentTripMapState extends State<CurrentTripMap> {
|
||||
class _MapWidgetState extends State<MapWidget> {
|
||||
late GoogleMapController mapController;
|
||||
|
||||
CameraPosition _cameraPosition = CameraPosition(
|
||||
@@ -68,27 +67,9 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
widget.trip?.addListener(setMapMarkers);
|
||||
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
|
||||
|
||||
return FutureBuilder(
|
||||
future: preferences,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
SharedPreferences prefs = snapshot.data as SharedPreferences;
|
||||
bool useLocation = prefs.getBool('useLocation') ?? true;
|
||||
return _buildMap(useLocation);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMap(bool useLocation) {
|
||||
return GoogleMap(
|
||||
onMapCreated: _onMapCreated,
|
||||
initialCameraPosition: _cameraPosition,
|
||||
@@ -98,9 +79,7 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
|
||||
cloudMapId: MAP_ID,
|
||||
mapToolbarEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
myLocationEnabled: useLocation,
|
||||
myLocationButtonEnabled: false,
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,82 +0,0 @@
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:anyway/modules/current_trip_summary.dart';
|
||||
import 'package:anyway/modules/current_trip_save_button.dart';
|
||||
import 'package:anyway/modules/current_trip_landmarks_list.dart';
|
||||
import 'package:anyway/modules/current_trip_greeter.dart';
|
||||
|
||||
|
||||
class CurrentTripPanel extends StatefulWidget {
|
||||
final ScrollController controller;
|
||||
final Trip trip;
|
||||
|
||||
const CurrentTripPanel({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.trip,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CurrentTripPanel> createState() => _CurrentTripPanelState();
|
||||
}
|
||||
|
||||
class _CurrentTripPanelState extends State<CurrentTripPanel> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.trip,
|
||||
builder: (context, child) {
|
||||
if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') {
|
||||
return ListView(
|
||||
controller: widget.controller,
|
||||
padding: const EdgeInsets.only(bottom: 30, left: 5, right: 5),
|
||||
children: [
|
||||
SizedBox(
|
||||
// reuse the exact same height as the panel has when collapsed
|
||||
// this way the greeter will be centered when the panel is collapsed
|
||||
height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20,
|
||||
child: Greeter(trip: widget.trip),
|
||||
),
|
||||
|
||||
const Padding(padding: EdgeInsets.only(top: 10)),
|
||||
|
||||
// CurrentTripSummary(trip: widget.trip),
|
||||
|
||||
// const Divider(),
|
||||
|
||||
...landmarksList(widget.trip),
|
||||
|
||||
const Padding(padding: EdgeInsets.only(top: 10)),
|
||||
|
||||
Center(child: saveButton(widget.trip)),
|
||||
],
|
||||
);
|
||||
} else if(widget.trip.uuid == 'pending') {
|
||||
return SizedBox(
|
||||
// reuse the exact same height as the panel has when collapsed
|
||||
// this way the greeter will be centered when the panel is collapsed
|
||||
height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20,
|
||||
child: Greeter(trip: widget.trip)
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 50,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text('Error: ${widget.trip.errorDescription}'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
|
||||
import 'package:anyway/main.dart';
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -9,13 +8,6 @@ Widget saveButton(Trip trip) => ElevatedButton(
|
||||
onPressed: () async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
trip.toPrefs(prefs);
|
||||
rootScaffoldMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Trip saved'),
|
||||
duration: Duration(seconds: 2),
|
||||
dismissDirection: DismissDirection.horizontal
|
||||
)
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
|
@@ -1,31 +0,0 @@
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CurrentTripSummary extends StatefulWidget {
|
||||
final Trip trip;
|
||||
const CurrentTripSummary({
|
||||
super.key,
|
||||
required this.trip,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CurrentTripSummary> createState() => _CurrentTripSummaryState();
|
||||
}
|
||||
|
||||
class _CurrentTripSummaryState extends State<CurrentTripSummary> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text('Summary'),
|
||||
// Text('Start: ${widget.trip.start}'),
|
||||
// Text('End: ${widget.trip.end}'),
|
||||
Text('Total duration: ${widget.trip.totalTime}'),
|
||||
Text('Total distance: ${widget.trip.totalTime}'),
|
||||
// Text('Fuel: ${widget.trip.fuel}'),
|
||||
// Text('Cost: ${widget.trip.cost}'),
|
||||
],
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@@ -18,6 +18,10 @@ class _LandmarkCardState extends State<LandmarkCard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeData theme = Theme.of(context);
|
||||
ButtonStyle buttonStyle = TextButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
fixedSize: Size.fromHeight(20)
|
||||
);
|
||||
return Container(
|
||||
height: 160,
|
||||
child: Card(
|
||||
@@ -36,7 +40,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
|
||||
width: 160,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: widget.landmark.imageURL ?? '',
|
||||
placeholder: (context, url) => Center(child: CircularProgressIndicator()),
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined),
|
||||
// TODO: make this a switch statement to load a placeholder if null
|
||||
// cover the whole container meaning the image will be cropped
|
||||
@@ -84,18 +88,21 @@ class _LandmarkCardState extends State<LandmarkCard> {
|
||||
// show the type, the website, and the wikipedia link as buttons/labels in a row
|
||||
children: [
|
||||
TextButton.icon(
|
||||
style: buttonStyle,
|
||||
onPressed: () {},
|
||||
icon: widget.landmark.type.icon,
|
||||
label: Text(widget.landmark.type.name),
|
||||
),
|
||||
if (widget.landmark.duration != null && widget.landmark.duration!.inMinutes > 0)
|
||||
TextButton.icon(
|
||||
style: buttonStyle,
|
||||
onPressed: () {},
|
||||
icon: Icon(Icons.hourglass_bottom),
|
||||
label: Text('${widget.landmark.duration!.inMinutes} minutes'),
|
||||
),
|
||||
if (widget.landmark.websiteURL != null)
|
||||
TextButton.icon(
|
||||
style: buttonStyle,
|
||||
onPressed: () async {
|
||||
// open a browser with the website link
|
||||
await launchUrl(Uri.parse(widget.landmark.websiteURL!));
|
||||
@@ -105,6 +112,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
|
||||
),
|
||||
if (widget.landmark.wikipediaURL != null)
|
||||
TextButton.icon(
|
||||
style: buttonStyle,
|
||||
onPressed: () async {
|
||||
// open a browser with the wikipedia link
|
||||
await launchUrl(Uri.parse(widget.landmark.wikipediaURL!));
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import 'package:anyway/layout.dart';
|
||||
import 'package:anyway/main.dart';
|
||||
import 'package:anyway/structs/preferences.dart';
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:anyway/utils/fetch_trip.dart';
|
||||
@@ -9,12 +8,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class NewTripButton extends StatefulWidget {
|
||||
final Trip trip;
|
||||
final UserPreferences preferences;
|
||||
|
||||
const NewTripButton({
|
||||
required this.trip,
|
||||
required this.preferences,
|
||||
});
|
||||
const NewTripButton({required this.trip});
|
||||
|
||||
@override
|
||||
State<NewTripButton> createState() => _NewTripButtonState();
|
||||
@@ -28,39 +23,42 @@ class _NewTripButtonState extends State<NewTripButton> {
|
||||
listenable: widget.trip,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
if (widget.trip.landmarks.isEmpty){
|
||||
// Fallback if the trip setup is lagging behind
|
||||
// This should in theory never happen
|
||||
return Container();
|
||||
}
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(Icons.add),
|
||||
label: AutoSizeText('Start planning!'),
|
||||
);
|
||||
}
|
||||
onPressed: () async {
|
||||
Future<UserPreferences> preferences = loadUserPreferences();
|
||||
Trip trip = widget.trip;
|
||||
fetchTrip(trip, preferences);
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BasePage(mainScreen: "map", trip: trip)
|
||||
)
|
||||
);
|
||||
},
|
||||
icon: Icon(Icons.add),
|
||||
label: FutureBuilder(
|
||||
future: widget.trip.cityName,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
return AutoSizeText(
|
||||
'New trip to ${snapshot.data.toString()}',
|
||||
style: TextStyle(fontSize: 18),
|
||||
maxLines: 2,
|
||||
);
|
||||
} else {
|
||||
return AutoSizeText(
|
||||
'New trip to ...',
|
||||
style: TextStyle(fontSize: 18),
|
||||
maxLines: 2,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void onPressed() async {
|
||||
// Check that the preferences are valid
|
||||
UserPreferences preferences = widget.preferences;
|
||||
if (preferences.nature.value == 0 && preferences.shopping.value == 0 && preferences.sightseeing.value == 0){
|
||||
rootScaffoldMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(content: Text("Please specify at least one preference"))
|
||||
);
|
||||
} else if (preferences.maxTime.value == 0){
|
||||
rootScaffoldMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(content: Text("Please choose a longer duration"))
|
||||
);
|
||||
} else {
|
||||
Trip trip = widget.trip;
|
||||
fetchTrip(trip, widget.preferences);
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BasePage(mainScreen: "map", trip: trip)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,13 +6,9 @@ import 'dart:developer';
|
||||
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class NewTripLocationSearch extends StatefulWidget {
|
||||
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
|
||||
Trip trip;
|
||||
|
||||
NewTripLocationSearch(
|
||||
this.trip,
|
||||
);
|
||||
@@ -43,66 +39,26 @@ class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
|
||||
uuid: 'pending',
|
||||
name: query,
|
||||
location: [location.latitude, location.longitude],
|
||||
type: typeStart
|
||||
type: start
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
late Widget locationSearchBar = SearchBar(
|
||||
hintText: 'Enter a city name or long press on the map.',
|
||||
onSubmitted: setTripLocation,
|
||||
controller: _controller,
|
||||
leading: Icon(Icons.search),
|
||||
trailing: [
|
||||
ElevatedButton(
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SearchBar(
|
||||
hintText: 'Enter a city name or long press on the map.',
|
||||
onSubmitted: setTripLocation,
|
||||
controller: _controller,
|
||||
leading: Icon(Icons.search),
|
||||
trailing: [ElevatedButton(
|
||||
onPressed: () {
|
||||
setTripLocation(_controller.text);
|
||||
},
|
||||
child: Text('Search'),
|
||||
)
|
||||
]
|
||||
);
|
||||
),]
|
||||
|
||||
|
||||
late Widget useCurrentLocationButton = ElevatedButton(
|
||||
onPressed: () async {
|
||||
// this widget is only shown if the user has already granted location permissions
|
||||
Position position = await Geolocator.getCurrentPosition();
|
||||
widget.trip.landmarks.clear();
|
||||
widget.trip.addLandmark(
|
||||
Landmark(
|
||||
uuid: 'pending',
|
||||
name: 'start',
|
||||
location: [position.latitude, position.longitude],
|
||||
type: typeStart
|
||||
)
|
||||
);
|
||||
},
|
||||
child: Text('Use current location'),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: widget.prefs,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final useLocation = snapshot.data!.getBool('useLocation') ?? false;
|
||||
if (useLocation) {
|
||||
return Column(
|
||||
children: [
|
||||
locationSearchBar,
|
||||
useCurrentLocationButton,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return locationSearchBar;
|
||||
}
|
||||
} else {
|
||||
return locationSearchBar;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,13 +1,13 @@
|
||||
|
||||
// A map that allows the user to select a location for a new trip.
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/modules/landmark_map_marker.dart';
|
||||
import 'package:anyway/modules/themed_marker.dart';
|
||||
import 'package:anyway/structs/landmark.dart';
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:widget_to_marker/widget_to_marker.dart';
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class NewTripMap extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _NewTripMapState extends State<NewTripMap> {
|
||||
final CameraPosition _cameraPosition = const CameraPosition(
|
||||
final CameraPosition _cameraPosition = CameraPosition(
|
||||
target: LatLng(48.8566, 2.3522),
|
||||
zoom: 11.0,
|
||||
);
|
||||
@@ -37,7 +37,7 @@ class _NewTripMapState extends State<NewTripMap> {
|
||||
uuid: 'pending',
|
||||
name: 'start',
|
||||
location: [location.latitude, location.longitude],
|
||||
type: typeStart
|
||||
type: start
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -70,26 +70,10 @@ class _NewTripMapState extends State<NewTripMap> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
widget.trip.addListener(updateTripDetails);
|
||||
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
|
||||
|
||||
return FutureBuilder(
|
||||
future: preferences,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
SharedPreferences prefs = snapshot.data as SharedPreferences;
|
||||
bool useLocation = prefs.getBool('useLocation') ?? true;
|
||||
return _buildMap(useLocation);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMap(bool useLocation) {
|
||||
return GoogleMap(
|
||||
onMapCreated: _onMapCreated,
|
||||
initialCameraPosition: _cameraPosition,
|
||||
@@ -98,8 +82,6 @@ class _NewTripMapState extends State<NewTripMap> {
|
||||
cloudMapId: MAP_ID,
|
||||
mapToolbarEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
myLocationEnabled: useLocation,
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
import 'package:anyway/pages/new_trip_preferences.dart';
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class NewTripOptionsButton extends StatefulWidget {
|
||||
final Trip trip;
|
||||
|
||||
const NewTripOptionsButton({required this.trip});
|
||||
|
||||
@override
|
||||
State<NewTripOptionsButton> createState() => _NewTripOptionsButtonState();
|
||||
}
|
||||
|
||||
class _NewTripOptionsButtonState extends State<NewTripOptionsButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.trip,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
if (widget.trip.landmarks.isEmpty){
|
||||
return Container();
|
||||
}
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NewTripPreferencesPage(trip: widget.trip)
|
||||
)
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const AutoSizeText('Set preferences')
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -16,23 +16,20 @@ class OnboardingCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color baseColor = Theme.of(context).colorScheme.secondary;
|
||||
Color baseColor = Theme.of(context).primaryColor;
|
||||
// have a different color for each card, incrementing the hue
|
||||
Color currentColor = baseColor.withAlpha(baseColor.alpha - index * 30);
|
||||
return Container(
|
||||
color: currentColor,
|
||||
alignment: Alignment.center,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 20)),
|
||||
@@ -47,8 +44,7 @@ class OnboardingCard extends StatelessWidget {
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
|
||||
]
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/structs/landmark.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -17,9 +16,21 @@ class ThemedMarker extends StatelessWidget {
|
||||
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 != typeStart && landmark.type != typeFinish) {
|
||||
if (landmark.type != start && landmark.type != finish) {
|
||||
positionIndicator = Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
@@ -40,14 +51,14 @@ class ThemedMarker extends StatelessWidget {
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: APP_GRADIENT,
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.red, Colors.yellow]
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black, width: 5),
|
||||
),
|
||||
width: 70,
|
||||
height: 70,
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Icon(landmark.type.icon.icon, size: 50),
|
||||
padding: EdgeInsets.all(5),
|
||||
child: icon
|
||||
),
|
||||
if (positionIndicator != null) positionIndicator,
|
||||
],
|
@@ -1,10 +1,11 @@
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/modules/current_trip_save_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
||||
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:anyway/modules/current_trip_landmarks_list.dart';
|
||||
import 'package:anyway/modules/current_trip_greeter.dart';
|
||||
import 'package:anyway/modules/current_trip_map.dart';
|
||||
import 'package:anyway/modules/current_trip_panel.dart';
|
||||
|
||||
|
||||
|
||||
@@ -26,21 +27,16 @@ class _TripPageState extends State<TripPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SlidingUpPanel(
|
||||
// use panelBuilder instead of panel so that we can reuse the scrollcontroller for the listview
|
||||
panelBuilder: (scrollcontroller) => CurrentTripPanel(controller: scrollcontroller, trip: widget.trip),
|
||||
// using collapsed and panelBuilder seems to show both at the same time, so we include the greeter in the panelBuilder
|
||||
// collapsed: Greeter(trip: widget.trip),
|
||||
body: CurrentTripMap(trip: widget.trip),
|
||||
minHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT,
|
||||
maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT,
|
||||
// padding in this context is annoying: it offsets the notion of vertical alignment.
|
||||
// children that want to be centered vertically need to have their size adjusted by 2x the padding
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
// Panel snapping should not be disabled because it significantly improves the user experience
|
||||
// panelSnapping: false
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
|
||||
parallaxEnabled: true,
|
||||
boxShadow: const [
|
||||
panelBuilder: (sc) => _panelFull(sc),
|
||||
// collapsed: _floatingCollapsed(),
|
||||
body: MapWidget(trip: widget.trip),
|
||||
// renderPanelSheet: false,
|
||||
// backdropEnabled: true,
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
padding: EdgeInsets.only(left: 10, right: 10, top: 25, bottom: 10),
|
||||
// panelSnapping: false,
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 20.0,
|
||||
color: Colors.black,
|
||||
@@ -48,4 +44,41 @@ class _TripPageState extends State<TripPage> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _panelFull(ScrollController sc) {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.trip,
|
||||
builder: (context, child) {
|
||||
if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') {
|
||||
return ListView(
|
||||
controller: sc,
|
||||
padding: EdgeInsets.only(bottom: 35),
|
||||
children: [
|
||||
Greeter(trip: widget.trip),
|
||||
...landmarksList(widget.trip),
|
||||
Padding(padding: EdgeInsets.only(top: 10)),
|
||||
Center(child: saveButton(widget.trip)),
|
||||
],
|
||||
);
|
||||
} else if(widget.trip.uuid == 'pending') {
|
||||
return Greeter(trip: widget.trip);
|
||||
} else {
|
||||
return Column(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 60,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: Text('Error: ${widget.trip.errorDescription}'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import 'package:anyway/modules/new_trip_button.dart';
|
||||
import 'package:anyway/modules/new_trip_options_button.dart';
|
||||
import 'package:anyway/structs/landmark.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
|
||||
import 'package:anyway/layout.dart';
|
||||
import 'package:anyway/utils/fetch_trip.dart';
|
||||
import 'package:anyway/structs/preferences.dart';
|
||||
import "package:anyway/structs/trip.dart";
|
||||
import 'package:anyway/modules/new_trip_location_search.dart';
|
||||
import 'package:anyway/modules/new_trip_map.dart';
|
||||
@@ -15,6 +19,7 @@ class NewTripPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _NewTripPageState extends State<NewTripPage> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController latController = TextEditingController();
|
||||
final TextEditingController lonController = TextEditingController();
|
||||
Trip trip = Trip();
|
||||
@@ -35,7 +40,7 @@ class _NewTripPageState extends State<NewTripPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: NewTripOptionsButton(trip: trip),
|
||||
floatingActionButton: NewTripButton(trip: trip),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,113 +0,0 @@
|
||||
import 'package:anyway/modules/new_trip_button.dart';
|
||||
import 'package:anyway/structs/preferences.dart';
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
|
||||
class NewTripPreferencesPage extends StatefulWidget {
|
||||
final Trip trip;
|
||||
const NewTripPreferencesPage({required this.trip});
|
||||
|
||||
@override
|
||||
_NewTripPreferencesPageState createState() => _NewTripPreferencesPageState();
|
||||
}
|
||||
|
||||
class _NewTripPreferencesPageState extends State<NewTripPreferencesPage> {
|
||||
UserPreferences preferences = UserPreferences();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ListView(
|
||||
children: [
|
||||
// Center(
|
||||
// child: CircleAvatar(
|
||||
// radius: 100,
|
||||
// child: Icon(Icons.person, size: 100),
|
||||
// )
|
||||
// ),
|
||||
Padding(padding: EdgeInsets.only(top: 30)),
|
||||
Center(
|
||||
child: FutureBuilder(
|
||||
future: widget.trip.cityName,
|
||||
builder: (context, snapshot) => Text(
|
||||
'Your trip to ${snapshot.hasData ? snapshot.data! : "..."}',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 0),
|
||||
child: Text('Tell us about your ideal trip.', style: TextStyle(fontSize: 18))
|
||||
),
|
||||
),
|
||||
|
||||
Divider(indent: 25, endIndent: 25, height: 50),
|
||||
|
||||
durationPicker(preferences.maxTime),
|
||||
|
||||
preferenceSliders([preferences.sightseeing, preferences.shopping, preferences.nature]),
|
||||
]
|
||||
),
|
||||
floatingActionButton: NewTripButton(trip: widget.trip, preferences: preferences),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget durationPicker(SinglePreference maxTime) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
|
||||
shadowColor: Colors.grey,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.timer),
|
||||
title: Text(preferences.maxTime.description),
|
||||
subtitle: CupertinoTimerPicker(
|
||||
mode: CupertinoTimerPickerMode.hm,
|
||||
initialTimerDuration: Duration(minutes: 90),
|
||||
minuteInterval: 15,
|
||||
onTimerDurationChanged: (Duration newDuration) {
|
||||
setState(() {
|
||||
preferences.maxTime.value = newDuration.inMinutes;
|
||||
});
|
||||
},
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget preferenceSliders(List<SinglePreference> prefs) {
|
||||
List<Card> sliders = [];
|
||||
for (SinglePreference pref in 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();
|
||||
});
|
||||
},
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: sliders
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import 'package:anyway/modules/onboarding_card.dart';
|
||||
import 'package:anyway/pages/new_trip_location.dart';
|
||||
import 'package:anyway/pages/new_trip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class OnboardingPage extends StatefulWidget {
|
||||
|
177
frontend/lib/pages/profile.dart
Normal file
@@ -0,0 +1,177 @@
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/structs/preferences.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
bool debugMode = false;
|
||||
|
||||
class ProfilePage extends StatefulWidget {
|
||||
@override
|
||||
_ProfilePageState createState() => _ProfilePageState();
|
||||
}
|
||||
|
||||
class _ProfilePageState extends State<ProfilePage> {
|
||||
Future<UserPreferences> _prefs = loadUserPreferences();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
children: [
|
||||
// First a round, centered image
|
||||
Center(
|
||||
child: CircleAvatar(
|
||||
radius: 100,
|
||||
child: Icon(Icons.person, size: 100),
|
||||
)
|
||||
),
|
||||
Center(
|
||||
child: Text('Curious traveler', style: TextStyle(fontSize: 24))
|
||||
),
|
||||
|
||||
Divider(indent: 25, endIndent: 25, height: 50),
|
||||
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10),
|
||||
child: Text('For a tailored experience, please rate your discovery preferences.', style: TextStyle(fontSize: 18))
|
||||
),
|
||||
),
|
||||
|
||||
FutureBuilder(future: _prefs, builder: futureSliders),
|
||||
|
||||
Divider(indent: 25, endIndent: 25, height: 50),
|
||||
|
||||
privacyInfo(),
|
||||
|
||||
debugButton(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Widget privacyInfo() {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('Privacy policy is available at '),
|
||||
TextButton.icon(
|
||||
icon: Icon(Icons.info),
|
||||
label: Text(PRIVACY_URL),
|
||||
onPressed: () {
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PreferenceSliders extends StatefulWidget {
|
||||
final List<SinglePreference> prefs;
|
||||
|
||||
PreferenceSliders({required this.prefs});
|
||||
|
||||
@override
|
||||
State<PreferenceSliders> createState() => _PreferenceSlidersState();
|
||||
}
|
||||
|
||||
|
||||
class _PreferenceSlidersState extends State<PreferenceSliders> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Card> sliders = [];
|
||||
for (SinglePreference pref in widget.prefs) {
|
||||
sliders.add(
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: pref.icon,
|
||||
title: Text(pref.name),
|
||||
subtitle: Slider(
|
||||
value: pref.value.toDouble(),
|
||||
min: pref.minVal.toDouble(),
|
||||
max: pref.maxVal.toDouble(),
|
||||
divisions: pref.maxVal - pref.minVal,
|
||||
label: pref.value.toString(),
|
||||
onChanged: (double newValue) {
|
||||
setState(() {
|
||||
pref.value = newValue.toInt();
|
||||
pref.save();
|
||||
});
|
||||
},
|
||||
)
|
||||
),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 0),
|
||||
shadowColor: Colors.grey,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: sliders);
|
||||
}
|
||||
}
|
||||
|
@@ -1,188 +0,0 @@
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
||||
bool debugMode = false;
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
@override
|
||||
_SettingsPageState createState() => _SettingsPageState();
|
||||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
padding: EdgeInsets.all(15),
|
||||
children: [
|
||||
// First a round, centered image
|
||||
Center(
|
||||
child: CircleAvatar(
|
||||
radius: 75,
|
||||
child: Icon(Icons.settings, size: 100),
|
||||
)
|
||||
),
|
||||
Center(
|
||||
child: Text('Global settings', style: TextStyle(fontSize: 24))
|
||||
),
|
||||
|
||||
Divider(indent: 25, endIndent: 25, height: 50),
|
||||
|
||||
darkMode(),
|
||||
setLocationUsage(),
|
||||
setDebugMode(),
|
||||
|
||||
Divider(indent: 25, endIndent: 25, height: 50),
|
||||
|
||||
privacyInfo(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget setDebugMode() {
|
||||
return Row(
|
||||
children: [
|
||||
Text('Debugging: use a custom API URL'),
|
||||
// white space
|
||||
Spacer(),
|
||||
Switch(
|
||||
value: debugMode,
|
||||
onChanged: (bool? newValue) {
|
||||
setState(() {
|
||||
debugMode = newValue!;
|
||||
if (debugMode) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Debug mode - use a custom API endpoint'),
|
||||
content: TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'https://anyway-stg.anydev.info'
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
API_URL_BASE = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget darkMode() {
|
||||
return Row(
|
||||
children: [
|
||||
Text('Dark mode'),
|
||||
Spacer(),
|
||||
Switch(
|
||||
value: Theme.of(context).brightness == Brightness.dark,
|
||||
onChanged: (bool? newValue) {
|
||||
setState(() {
|
||||
rootScaffoldMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(content: Text('Dark mode is not implemented yet'))
|
||||
);
|
||||
// if (newValue!) {
|
||||
// // Dark mode
|
||||
// Theme.of(context).brightness = Brightness.dark;
|
||||
// } else {
|
||||
// // Light mode
|
||||
// Theme.of(context).brightness = Brightness.light;
|
||||
// }
|
||||
});
|
||||
}
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget setLocationUsage() {
|
||||
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
|
||||
return Row(
|
||||
children: [
|
||||
Text('Use location services'),
|
||||
// white space
|
||||
Spacer(),
|
||||
FutureBuilder(
|
||||
future: preferences,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
bool useLocation = snapshot.data!.getBool('useLocation') ?? false;
|
||||
return Switch(
|
||||
value: useLocation,
|
||||
onChanged: setUseLocation,
|
||||
);
|
||||
} else {
|
||||
return CircularProgressIndicator();
|
||||
}
|
||||
}
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void setUseLocation(bool newValue) async {
|
||||
await Permission.locationWhenInUse
|
||||
.onDeniedCallback(() {
|
||||
rootScaffoldMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(content: Text('Location services are required for this feature'))
|
||||
);
|
||||
})
|
||||
.onGrantedCallback(() {
|
||||
rootScaffoldMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(content: Text('Location services are now enabled'))
|
||||
);
|
||||
SharedPreferences.getInstance().then(
|
||||
(SharedPreferences prefs) {
|
||||
setState(() {
|
||||
prefs.setBool('useLocation', newValue);
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.onPermanentlyDeniedCallback(() {
|
||||
rootScaffoldMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(content: Text('Location services are required for this feature'))
|
||||
);
|
||||
})
|
||||
.request();
|
||||
}
|
||||
|
||||
Widget privacyInfo() {
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Our privacy policy is available under:'),
|
||||
|
||||
TextButton.icon(
|
||||
icon: Icon(Icons.info),
|
||||
label: Text(PRIVACY_URL),
|
||||
onPressed: () async{
|
||||
await launchUrl(Uri.parse(PRIVACY_URL));
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -4,13 +4,13 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
LandmarkType typeSightseeing = LandmarkType(name: 'sightseeing');
|
||||
LandmarkType typeNature = LandmarkType(name: 'nature');
|
||||
LandmarkType typeShopping = LandmarkType(name: 'shopping');
|
||||
LandmarkType sightseeing = LandmarkType(name: 'sightseeing');
|
||||
LandmarkType nature = LandmarkType(name: 'nature');
|
||||
LandmarkType shopping = LandmarkType(name: 'shopping');
|
||||
// LandmarkType museum = LandmarkType(name: 'Museum');
|
||||
// LandmarkType restaurant = LandmarkType(name: 'Restaurant');
|
||||
LandmarkType typeStart = LandmarkType(name: 'start');
|
||||
LandmarkType typeFinish = LandmarkType(name: 'finish');
|
||||
LandmarkType start = LandmarkType(name: 'start');
|
||||
LandmarkType finish = LandmarkType(name: 'finish');
|
||||
|
||||
|
||||
final class Landmark extends LinkedListEntry<Landmark>{
|
||||
@@ -130,22 +130,22 @@ class LandmarkType {
|
||||
LandmarkType({required this.name, this.icon = const Icon(Icons.location_on)}) {
|
||||
switch (name) {
|
||||
case 'sightseeing':
|
||||
icon = const Icon(Icons.church);
|
||||
icon = Icon(Icons.church);
|
||||
break;
|
||||
case 'nature':
|
||||
icon = const Icon(Icons.eco);
|
||||
icon = Icon(Icons.eco);
|
||||
break;
|
||||
case 'shopping':
|
||||
icon = const Icon(Icons.shopping_cart);
|
||||
icon = Icon(Icons.shopping_cart);
|
||||
break;
|
||||
case 'start':
|
||||
icon = const Icon(Icons.play_arrow);
|
||||
icon = Icon(Icons.play_arrow);
|
||||
break;
|
||||
case 'finish':
|
||||
icon = const Icon(Icons.flag);
|
||||
icon = Icon(Icons.flag);
|
||||
break;
|
||||
default:
|
||||
icon = const Icon(Icons.location_on);
|
||||
icon = Icon(Icons.location_on);
|
||||
}
|
||||
}
|
||||
@override
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import 'package:anyway/structs/landmark.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
||||
class SinglePreference {
|
||||
@@ -20,6 +20,16 @@ class SinglePreference {
|
||||
this.minVal = 0,
|
||||
this.maxVal = 5,
|
||||
});
|
||||
|
||||
void save() async {
|
||||
SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
|
||||
sharedPrefs.setInt('pref_$slug', value);
|
||||
}
|
||||
|
||||
void load() async {
|
||||
SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
|
||||
value = sharedPrefs.getInt('pref_$slug') ?? minVal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,41 +39,64 @@ class UserPreferences {
|
||||
slug: "sightseeing",
|
||||
description: "How much do you like sightseeing?",
|
||||
value: 0,
|
||||
icon: typeSightseeing.icon,
|
||||
icon: Icon(Icons.church),
|
||||
);
|
||||
SinglePreference shopping = SinglePreference(
|
||||
name: "Shopping",
|
||||
slug: "shopping",
|
||||
description: "How much do you like shopping?",
|
||||
value: 0,
|
||||
icon: typeShopping.icon,
|
||||
icon: Icon(Icons.shopping_bag),
|
||||
);
|
||||
SinglePreference nature = SinglePreference(
|
||||
name: "Nature",
|
||||
slug: "nature",
|
||||
description: "How much do you like nature?",
|
||||
value: 0,
|
||||
icon: typeNature.icon,
|
||||
icon: Icon(Icons.landscape),
|
||||
);
|
||||
|
||||
SinglePreference maxTime = SinglePreference(
|
||||
name: "Trip duration",
|
||||
slug: "duration",
|
||||
description: "How long should your trip be?",
|
||||
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
|
||||
"max_time_minute": maxTime.value,
|
||||
"detour_tolerance_minute": maxDetour.value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<UserPreferences> loadUserPreferences() async {
|
||||
UserPreferences prefs = UserPreferences();
|
||||
await prefs.load();
|
||||
return prefs;
|
||||
}
|
@@ -29,10 +29,11 @@ Dio dio = Dio(
|
||||
|
||||
fetchTrip(
|
||||
Trip trip,
|
||||
UserPreferences preferences,
|
||||
Future<UserPreferences> preferences,
|
||||
) async {
|
||||
UserPreferences prefs = await preferences;
|
||||
Map<String, dynamic> data = {
|
||||
"preferences": preferences.toJson(),
|
||||
"preferences": prefs.toJson(),
|
||||
"start": trip.landmarks!.first.location,
|
||||
};
|
||||
String dataString = jsonEncode(data);
|
||||
@@ -46,16 +47,11 @@ fetchTrip(
|
||||
// handle errors
|
||||
if (response.statusCode != 200) {
|
||||
trip.updateUUID("error");
|
||||
String errorDetail;
|
||||
if (response.data.runtimeType == String) {
|
||||
errorDetail = response.data;
|
||||
} else {
|
||||
errorDetail = response.data["detail"] ?? "Unknown error";
|
||||
if (response.data["detail"] != null) {
|
||||
trip.updateError(response.data["detail"]);
|
||||
log(response.data["detail"]);
|
||||
// throw Exception(response.data["detail"]);
|
||||
}
|
||||
trip.updateError(errorDetail);
|
||||
log(errorDetail);
|
||||
// Actualy no need to throw an exception, we can just log the error and let the user retry
|
||||
// throw Exception(errorDetail);
|
||||
} else {
|
||||
Map<String, dynamic> json = response.data;
|
||||
|
||||
|
@@ -5,14 +5,12 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import geolocator_apple
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import sqflite
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
|
@@ -45,26 +45,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cached_network_image
|
||||
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
|
||||
sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
version: "3.4.0"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_platform_interface
|
||||
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
||||
sha256: ff0c949e323d2a1b52be73acce5b4a7b04063e61414c8ca542dbba47281630a7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
version: "4.1.0"
|
||||
cached_network_image_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
|
||||
sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -109,10 +109,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.3"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -133,18 +133,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
|
||||
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.0"
|
||||
version: "5.5.0+1"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
||||
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "1.0.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -157,10 +157,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -186,10 +186,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_cache_manager
|
||||
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
|
||||
sha256: a77f77806a790eb9ba0118a5a3a936e81c4fea2b61533033b2b0c3d50bbde5ea
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
version: "3.4.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -210,10 +210,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
|
||||
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.22"
|
||||
version: "2.0.21"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -264,54 +264,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: "0ec58b731776bc43097fcf751f79681b6a8f6d3bc737c94779fe9f1ad73c1a81"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.1"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.1"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: "2ed69328e05cd94e7eb48bb0535f5fc0c0c44d1c4fa1e9737267484d05c29b5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
google_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -324,42 +276,42 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_maps_flutter
|
||||
sha256: "2e302fa3aaf4e2a297f0342d83ebc5e8e9f826e9a716aef473fe7f404ec630a7"
|
||||
sha256: acf0ec482d86b2ac55ade80597ce7f797a47971f5210ebfd030f0d58130e0a94
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
version: "2.7.0"
|
||||
google_maps_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_android
|
||||
sha256: "10cf27bee8c560f8e69992b3a0f27ddf1d7acbea622ddb13ef3f587848a73f26"
|
||||
sha256: "5d444f4135559488d7ea325eae710ae3284e6951b1b61729a0ac026456fe1548"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.14.7"
|
||||
version: "2.12.1"
|
||||
google_maps_flutter_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_ios
|
||||
sha256: "3a484846fc56f15e47e3de1f5ea80a7ff2b31721d2faa88f390f3b3cf580c953"
|
||||
sha256: a6e3c6ecdda6c985053f944be13a0645ebb919da2ef0f5bc579c5e1670a5b2a8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
version: "2.10.0"
|
||||
google_maps_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_platform_interface
|
||||
sha256: "099874463dc4c9bff04fe4b2b8cf7284d2455c2deead8f9a59a87e1b9f028c69"
|
||||
sha256: bd60ca330e3c7763b95b477054adec338a522d982af73ecc520b232474063ac5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.2"
|
||||
version: "2.8.0"
|
||||
google_maps_flutter_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_maps_flutter_web
|
||||
sha256: ff39211bd25d7fad125d19f757eba85bd154460907cd4d135e07e3d0f98a4130
|
||||
sha256: "8d5d0f58bfc4afac0bbe3d399f2018fcea691e3ea3d35254b7aae56df5827659"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.10"
|
||||
version: "0.5.9+1"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -436,10 +388,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: map_launcher
|
||||
sha256: "7436d6ef9ae57ff15beafcedafe0a8f0604006cbecd2d26024c4cfb0158c2b9a"
|
||||
sha256: af59b9f79f641022e06761c9d4217c6c57b9ef9020af2fdb23155ec87af79e61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.0"
|
||||
version: "3.3.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -508,10 +460,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
|
||||
sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.10"
|
||||
version: "2.2.9"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -544,54 +496,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.12"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.5"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+2"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -644,34 +548,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
|
||||
sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.3.0"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
|
||||
sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.3.0"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
|
||||
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
version: "2.5.0"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -684,18 +588,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
||||
sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -737,10 +641,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "4058172e418eb7e7f2058dcb7657d451a8fc264afa0dea4dbd0f304a57131611"
|
||||
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4+3"
|
||||
version: "2.5.4"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -777,10 +681,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "51b08572b9f091f8c3eb4d9d4be253f196ff0075d5ec9b10a884026d5b55d7bc"
|
||||
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0+2"
|
||||
version: "3.1.0+1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -841,10 +745,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
|
||||
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.0"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -873,10 +777,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77
|
||||
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.0"
|
||||
version: "4.4.2"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -921,10 +825,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "0.5.1"
|
||||
widget_to_marker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@@ -49,8 +49,6 @@ dependencies:
|
||||
flutter_svg: ^2.0.10+1
|
||||
url_launcher: ^6.3.0
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
permission_handler: ^11.3.1
|
||||
geolocator: ^13.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@@ -6,15 +6,9 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <geolocator_windows/geolocator_windows.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
GeolocatorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
@@ -3,8 +3,6 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
geolocator_windows
|
||||
permission_handler_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|