16 Commits

Author SHA1 Message Date
014b48591e feat(wip): implement trip persistence through a local repository. Include loaded trips in the start page UI 2025-12-30 00:51:40 +01:00
81ed2fd8c3 feat(wip): Update entities and adopt a proper repository workflow for trip "obtention" 2025-12-29 00:23:10 +01:00
239b63ca81 chore(wip): upgrade dependencies, begin refactor 2025-12-23 21:49:54 +01:00
0070e57aec adding missing const 2025-10-20 17:08:10 +02:00
71c7325370 man what?
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m35s
Build and deploy the backend to staging / Add a comment to the PR to notify about the deployment (pull_request) Successful in 5s
Run linting on the backend code / Build (pull_request) Has been cancelled
Run testing on the backend code / Build (pull_request) Has been cancelled
2025-10-14 17:19:24 +02:00
aeed9c7dc9 maybe like this?
Some checks failed
Run testing on the backend code / Build (pull_request) Has been cancelled
Run linting on the backend code / Build (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m32s
Build and deploy the backend to staging / Add a comment to the PR to notify about the deployment (pull_request) Failing after 4s
2025-10-14 17:15:07 +02:00
89c5fc9370 more tea attempts
Some checks failed
Run linting on the backend code / Build (pull_request) Has been cancelled
Run testing on the backend code / Build (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m56s
Build and deploy the backend to staging / Add a comment to the PR to notify about the deployment (pull_request) Failing after 14s
2025-10-14 17:00:07 +02:00
d8c6bfcda0 try whithout body flag
Some checks failed
Run testing on the backend code / Build (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m25s
Build and deploy the backend to staging / Add a comment to the PR to notify about the deployment (pull_request) Failing after 4s
Run linting on the backend code / Build (pull_request) Successful in 3m31s
2025-10-14 16:18:32 +02:00
ec3ed054fd try using the gitea cli instead
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m15s
Build and deploy the backend to staging / Add a comment to the PR to notify about the deployment (pull_request) Failing after 25s
Run linting on the backend code / Build (pull_request) Successful in 4m7s
Run testing on the backend code / Build (pull_request) Has been cancelled
2025-10-14 16:02:27 +02:00
219cfcf1a6 try with correct url?
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m49s
Build and deploy the backend to staging / Add a comment to the PR to notify about the deployment (pull_request) Failing after 21s
Run linting on the backend code / Build (pull_request) Has been cancelled
Run testing on the backend code / Build (pull_request) Has been cancelled
2025-10-14 14:50:27 +02:00
708c07cf49 add a comment containing additional deployment information
Some checks failed
Run testing on the backend code / Build (pull_request) Has been cancelled
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m7s
Build and deploy the backend to staging / Add a comment to the PR to notify about the deployment (pull_request) Failing after 21s
Run linting on the backend code / Build (pull_request) Successful in 3m35s
2025-10-14 14:31:56 +02:00
e14900e9f0 remove backend deployment submodule
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m12s
Run linting on the backend code / Build (pull_request) Successful in 1m0s
Run testing on the backend code / Build (pull_request) Failing after 23m1s
2025-10-14 11:21:28 +02:00
d1cbf972fe since branch names may contain slashes and other special chars they are not suitable for tag names. only use the hash
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m54s
Run linting on the backend code / Build (pull_request) Successful in 3m19s
Run testing on the backend code / Build (pull_request) Failing after 24m59s
2025-10-13 18:05:27 +02:00
fe2a0cf1d5 add missing dollar sign
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Failing after 36s
Run linting on the backend code / Build (pull_request) Successful in 2m46s
Run testing on the backend code / Build (pull_request) Has been cancelled
2025-10-13 18:03:39 +02:00
c11faee824 towards a better gitops deploy strategy
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Failing after 38s
Run linting on the backend code / Build (pull_request) Successful in 3m20s
Run testing on the backend code / Build (pull_request) Failing after 27m28s
2025-10-13 17:17:02 +02:00
97dacb1189 Fastlane fixes working up to a full release (#68)
Reviewed-on: #68
2025-04-30 11:01:48 +00:00
149 changed files with 6488 additions and 680 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use nix

View File

@@ -3,6 +3,9 @@ on:
tags:
- v*
permissions:
pull-requests: write
name: Build and deploy the backend to production
jobs:
@@ -10,15 +13,7 @@ jobs:
name: Build and push image
uses: ./.gitea/workflows/workflow_build-image.yaml
with:
tag: stable
# sets the tag to the git tag that triggered the workflow - the deployment (configured in a separate repository) will use this tag and be deployed to production by argocd
tag: ${{ github.ref_name }}
secrets:
PACKAGE_REGISTRY_ACCESS: ${{ secrets.PACKAGE_REGISTRY_ACCESS }}
deploy-prod:
name: Deploy to production
uses: ./.gitea/workflows/workflow_deploy-container.yaml
with:
overlay: prod
secrets:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
needs: build-and-push

View File

@@ -12,15 +12,32 @@ jobs:
name: Build and push image
uses: ./.gitea/workflows/workflow_build-image.yaml
with:
tag: unstable
# sets a unique tag for each commit in the PR - this gets deployed to a separate application instance using argocd
tag: sha${{ github.sha }}
secrets:
PACKAGE_REGISTRY_ACCESS: ${{ secrets.PACKAGE_REGISTRY_ACCESS }}
deploy-prod:
name: Deploy to staging
uses: ./.gitea/workflows/workflow_deploy-container.yaml
with:
overlay: stg
secrets:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
needs: build-and-push
notify:
runs-on: ubuntu-latest
name: Add a comment to the PR to notify about the deployment
steps:
- name: Download gitea client
run: |
curl -sSL -o tea https://dl.gitea.com/tea/0.11.0/tea-0.11.0-linux-amd64
chmod +x tea
- name: Login
run: |
./tea login add --url git.kluster.moll.re --name bot --token ${{ secrets.GITEA_TOKEN }}
./tea login default
- name: Post comment
run: |
./tea comment --repo anydev/anyway --login bot ${{ github.event.number }} """
The backend has been deployed to staging with url https://pr-${{ github.event.number }}.anyway-stg.anydev.info. Check the deployment status in ArgoCD:
[![App Status](https://argocd.kluster.moll.re/api/badge?name=anydev-anyway-backend-stg-pr-${{ github.event.number }}&revision=true&showAppName=true)](https://argocd.kluster.moll.re/applications/anydev-anyway-backend-stg-pr-${{ github.event.number }})
"""
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_BASE_URL: ${{ secrets.GITEA_BASE_URL }}
GITEA_REPO: ${{ secrets.GITEA_REPO }}

View File

@@ -5,7 +5,7 @@ on:
paths:
- frontend/**
name: Build and release release apps to production track
name: Build and release apps to beta track
jobs:
get-version:
@@ -13,6 +13,7 @@ jobs:
runs-on: macos
steps:
- uses: https://gitea.com/actions/checkout@v4
- name: Fetch tags from main branch
# since this workflow is triggered by a pull request, we want to match the latest tag of the main branch
id: version
@@ -20,14 +21,21 @@ jobs:
git fetch origin main --tags
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
# remove the 'v' prefix from the tag name
echo "BUILD_NAME=${LATEST_TAG//v}" >> $GITHUB_ENV
echo "BUILD_NAME=${LATEST_TAG//v}" >> $GITHUB_OUTPUT
- name: Output the version that is being used
run: |
echo "Building for version ${{ steps.version.outputs.BUILD_NAME }}"
outputs:
build_name: ${{ steps.version.outputs.BUILD_NAME }}
build-android:
name: Build and upload android app
uses: ./.gitea/workflows/workflow_build-app-android.yaml
with:
build_type: beta
build_name: ${{ env.BUILD_NAME }}
build_name: ${{ needs.get-version.outputs.build_name }}
secrets:
ANDROID_SECRET_PROPERTIES_BASE64: ${{ secrets.ANDROID_SECRET_PROPERTIES_BASE64 }}
ANDROID_GOOGLE_PLAY_JSON_BASE64: ${{ secrets.ANDROID_GOOGLE_PLAY_JSON_BASE64 }}
@@ -40,7 +48,7 @@ jobs:
uses: ./.gitea/workflows/workflow_build-app-ios.yaml
with:
build_type: beta
build_name: ${{ env.BUILD_NAME }}
build_name: ${{ needs.get-version.outputs.build_name }}
secrets:
IOS_ASC_KEY_ID: ${{ secrets.IOS_ASC_KEY_ID }}
IOS_ASC_ISSUER_ID: ${{ secrets.IOS_ASC_ISSUER_ID }}
@@ -48,4 +56,4 @@ jobs:
IOS_MATCH_REPO_SSH_KEY_BASE64: ${{ secrets.IOS_MATCH_REPO_SSH_KEY_BASE64 }}
IOS_MATCH_PASSWORD: ${{ secrets.IOS_MATCH_PASSWORD }}
IOS_GOOGLE_MAPS_API_KEY: ${{ secrets.IOS_GOOGLE_MAPS_API_KEY }}
needs: get-version
needs: build-android # technically not needed, but this prevents the builds from running in parallel

View File

@@ -3,7 +3,7 @@ on:
tags:
- v*
name: Build and release release apps to production track
name: Build and release apps to production track
jobs:
get-version:
@@ -11,21 +11,28 @@ jobs:
runs-on: macos
steps:
- uses: https://gitea.com/actions/checkout@v4
- name: Get version from git tag
id: version
env:
REF_NAME: ${{ gitea.ref_name }}
# remove the 'v' prefix from the tag name
run: |
echo "BUILD_NAME=${REF_NAME//v}" >> $GITHUB_ENV
echo "BUILD_NAME=${REF_NAME//v}" >> $GITHUB_OUTPUT
- name: Output the version that is being used
run: |
echo "Building for version ${{ steps.version.outputs.BUILD_NAME }}"
outputs:
build_name: ${{ steps.version.outputs.BUILD_NAME }}
build-android:
name: Build and upload android app
uses: ./.gitea/workflows/workflow_build-app-android.yaml
with:
build_type: release
build_name: ${{ env.BUILD_NAME }}
build_name: ${{ needs.get-version.outputs.build_name }}
secrets:
ANDROID_SECRET_PROPERTIES_BASE64: ${{ secrets.ANDROID_SECRET_PROPERTIES_BASE64 }}
ANDROID_GOOGLE_PLAY_JSON_BASE64: ${{ secrets.ANDROID_GOOGLE_PLAY_JSON_BASE64 }}
@@ -38,12 +45,12 @@ jobs:
uses: ./.gitea/workflows/workflow_build-app-ios.yaml
with:
build_type: release
build_name: ${{ env.BUILD_NAME }}
build_name: ${{ needs.get-version.outputs.build_name }}
secrets:
IOS_ASC_KEY_ID: ${{ secrets.IOS_ASC_KEY_ID }}
IOS_ASC_ISSUER_ID: ${{ secrets.IOS_ASC_ISSUER_ID }}
IOS_ASC_KEY: ${{ secrets.IOS_ASC_KEY }}
IOS_MATCH_PASSWORD: ${{ secrets.IOS_MATCH_PASSWORD }}
IOS_MATCH_REPO_SSH_KEY_BASE64: ${{ secrets.IOS_MATCH_REPO_SSH_KEY_BASE64 }}
IOS_MATCH_PASSWORD: ${{ secrets.IOS_MATCH_PASSWORD }}
IOS_GOOGLE_MAPS_API_KEY: ${{ secrets.IOS_GOOGLE_MAPS_API_KEY }}
needs: get-version
needs: build-android # technically not needed, but this prevents the builds from running in parallel

View File

@@ -27,7 +27,7 @@ defaults:
jobs:
build:
runs-on: macos
runs-on: macos-14
env:
# $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ gitea.workspace }}/frontend/android/Gemfile
@@ -37,8 +37,8 @@ jobs:
- uses: https://github.com/actions/setup-java@v4
with:
java-version: '17'
distribution: 'zulu'
java-version: '21'
distribution: 'oracle'
- name: Setup Android SDK
uses: https://github.com/android-actions/setup-android@v3

View File

@@ -31,7 +31,7 @@ defaults:
jobs:
build:
runs-on: macos
runs-on: macos-14
env:
# $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ gitea.workspace }}/frontend/ios/Gemfile
@@ -53,9 +53,13 @@ jobs:
ruby-version: 3.3
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: GuillaumeFalourd/setup-rsync@v1.2
# rsync is required by the google maps ios tools
- name: Install dependencies and clean up
run: |
flutter pub get
flutter precache --ios
bundle exec pod install --allow-root
flutter clean
bundle exec pod cache clean --all --allow-root
@@ -69,6 +73,11 @@ jobs:
env:
MATCH_REPO_SSH_KEY: ${{ secrets.IOS_MATCH_REPO_SSH_KEY_BASE64 }}
- name: Replace API Key from secret
# on a macOS runner, sed requires a replacement suffix after the -i flag
run: |
sed -i '' -e "s/IOS_GOOGLE_MAPS_API_KEY/${{ secrets.IOS_GOOGLE_MAPS_API_KEY }}/g" Runner/AppDelegate.swift
- name: Run fastlane lane
run: bundle exec fastlane deploy_${{ inputs.build_type }}
env:

View File

@@ -1,35 +0,0 @@
on:
workflow_call:
inputs:
overlay:
required: true
type: string
secrets:
KUBE_CONFIG:
required: true
name: Deploy the newly built container
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- uses: https://gitea.com/actions/checkout@v4
with:
submodules: true
- name: setup kubectl
uses: https://github.com/azure/setup-kubectl@v4
- name: Set kubeconfig
run: |
echo "${{ secrets.KUBE_CONFIG }}" > kubeconfig
- 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 }} --kubeconfig=kubeconfig

1
.gitignore vendored
View File

@@ -1 +1,2 @@
cache/
.direnv/

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "backend/deployment"]
path = backend/deployment
url = https://git.kluster.moll.re/anydev/anyway-backend-deployment

13
.vscode/launch.json vendored
View File

@@ -9,9 +9,7 @@
"name": "Backend - debug",
"type": "debugpy",
"request": "launch",
"env": {
"DEBUG": "true"
},
"envFile": "${workspaceFolder}/backend/debug.env",
"jinja": true,
"cwd": "${workspaceFolder}/backend",
"module": "fastapi",
@@ -25,9 +23,7 @@
"type": "debugpy",
"request": "launch",
"program": "src/tester.py",
"env": {
"DEBUG": "true"
},
"envFile": "${workspaceFolder}/backend/debug.env",
"cwd": "${workspaceFolder}/backend"
},
// frontend - flutter app
@@ -37,8 +33,11 @@
"request": "launch",
"program": "lib/main.dart",
"cwd": "${workspaceFolder}/frontend",
"args": [
"--dart-define-from-file=secrets.json"
],
"env": {
"GOOGLE_MAPS_API_KEY": "testing"
"ANDROID_GOOGLE_MAPS_API_KEY": "AIzaSyD6RK_pzKFc8T-t1t0jiC3PNRZwNXECFG4"
}
},
{

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"nixEnvSelector.nixFile": "${workspaceFolder}/default.nix"
}

5
backend/.gitignore vendored
View File

@@ -1,3 +1,6 @@
# all .env files
*.env
# osm-cache
cache_XML/
@@ -165,4 +168,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/

20
default.nix Normal file
View File

@@ -0,0 +1,20 @@
{ pkgs ? import <nixpkgs> { config.android_sdk.accept_license = true; config.allowUnfree = true; } }:
pkgs.mkShell {
buildInputs = with pkgs; [
flutter
android-tools # for adb
openjdk # required for Android builds
# pkgs.androidenv.androidPkgs.androidsdk # The customized SDK that we've made above
# androidenv.androidPkgs.ndk-bundle
];
# Setting up android build environments on nix is a bit of a pain - immutable paths, etc.
# I used a hacky workaround by manually downloading the SDK and NDK from the website and simply telling the shell where to find them
ANDROID_HOME = "/scratch/remy/android";
shellHook = ''
echo "Flutter dev environment ready. 'adb' and 'flutter' are available."
'';
}

View File

@@ -1,59 +0,0 @@
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up ruby env
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2.1
bundler-cache: true
- name: Setup java for android build
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'zulu'
- 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
env:
REF_NAME: ${{ github.ref_name }}
run:
# remove the 'v' prefix from the tag name
echo "BUILD_NAME=${REF_NAME//v}" >> $GITHUB_ENV
- name: Put selected secrets into files
run: |
echo "${{ secrets.ANDROID_SECRET_PROPERTIES_BASE64 }}" | base64 -d > secrets.properties
echo "${{ secrets.ANDROID_GOOGLE_PLAY_JSON_BASE64 }}" | base64 -d > google-key.json
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore
working-directory: android
- name: Install fastlane
run: bundle install
working-directory: android
- name: Run fastlane lane
run: bundle exec fastlane deploy_release
working-directory: android
env:
BUILD_NUMBER: ${{ github.run_number }}
# BUILD_NAME is implicitly available
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}

View File

@@ -1,64 +0,0 @@
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: macos-latest
env:
# $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ github.workspace }}/ios/Gemfile
steps:
- uses: actions/checkout@v4
- name: Set up ruby env
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- 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
env:
REF_NAME: ${{ github.ref_name }}
run:
# remove the 'v' prefix from the tag name
echo "BUILD_NAME=${REF_NAME//v}" >> $GITHUB_ENV
- name: Setup SSH key for match git repo
# and mark the host as known
run: |
echo $MATCH_REPO_SSH_KEY | base64 --decode > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -p 2222 git.kluster.moll.re > ~/.ssh/known_hosts
env:
MATCH_REPO_SSH_KEY: ${{ secrets.IOS_MATCH_REPO_SSH_KEY_BASE64 }}
- name: Install dependencies and clean up
run: |
flutter pub get
bundle exec pod install
flutter clean
bundle exec pod cache clean --all
working-directory: ios
- name: Run fastlane lane
run: bundle exec fastlane deploy_release --verbose
working-directory: ios
env:
BUILD_NUMBER: ${{ github.run_number }}
# BUILD_NAME is implicitly available
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
IOS_ASC_KEY_ID: ${{ secrets.IOS_ASC_KEY_ID }}
IOS_ASC_ISSUER_ID: ${{ secrets.IOS_ASC_ISSUER_ID }}
IOS_ASC_KEY: ${{ secrets.IOS_ASC_KEY }}
MATCH_PASSWORD: ${{ secrets.IOS_MATCH_PASSWORD }}
IOS_GOOGLE_MAPS_API_KEY: ${{ secrets.IOS_GOOGLE_MAPS_API_KEY }}

View File

@@ -26,7 +26,7 @@ To truly deploy a new version of the application, i.e. to the official app store
git tag -a v<name> -m "Release <name>"
git push origin v<name>
```
We adhere to the [Semantic Versioning](https://semver.org/) standard, so the tag should be of the form `v0.1.8` for example.
We adhere to the [Semantic Versioning](https://semver.org/) standard, so the tag should be of the form `v0.1.8` for example.
### Icons and logos
The application uses a custom launcher icon and splash screen. These are managed platform-independently using the `flutter_launcher_icons` package.
@@ -66,3 +66,10 @@ These are used by the CI/CD pipeline to deploy the application.
- `IOS_ASC_KEY_ID` as well
- `IOS_MATCH_PASSWORD` is used by fastlane match to download the certificates
- `IOS_MATCH_REPO_SSH_KEY_BASE64` is used to authenticate with the git repository where the certificates are stored
## Android SDK&NDK setup

View File

@@ -24,5 +24,15 @@ linter:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Exclude legacy/experimental code that is not part of the current
# refactor work. This prevents the analyzer from failing on old files
# in `lib/old` while we iterate on the new architecture in `lib/`.
analyzer:
exclude:
- lib/old/**
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
formatter:
page_width: 200

View File

@@ -49,7 +49,8 @@ if (secretPropertiesFile.exists()) {
android {
namespace "com.anydev.anyway"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
ndkVersion = "27.0.12077973"
// TODO - set back to ndkVersion flutter.ndkVersion once https://github.com/flutter/flutter/issues/139427 is resolved
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -65,7 +66,7 @@ android {
}
defaultConfig {
applicationId "com.anydev.anyway"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.

View File

@@ -17,7 +17,7 @@ platform :android do
)
upload_to_play_store(
track: 'alpha',
track: 'beta',
# upload aab files intstead
skip_upload_apk: true,
skip_upload_changelogs: true,

View File

@@ -1,7 +0,0 @@
AnyWay is an application that helps you plan truly unique city trips. When planning a new trip, you can specify <your> preferences and constraints and anyway generates a personalized itinerary just for you.
Anyway follows these core principles:
- **Personalization**: Trips should be match your interests - not just the most popular destinations.
- **Efficiency**: Don't just walk in circles! Anyway creates the most efficient route for you.
- **Flexibility**: Vacations are the time to be spontaneous. Anyway lets you update your plans on the go.
- **Discoverability**: Tourism means exploration. Anyway encourages you to take detours and make spontaneous decisions.

View File

@@ -0,0 +1,7 @@
AnyWay is an application that helps you plan truly unique city trips. When planning a new trip, you can specify your preferences and constraints and anyway generates a personalized itinerary just for you.
Anyway follows these core principles:
- Personalization: Trips should be match your interests - not just the most popular destinations.
- Efficiency: Don't just walk in circles! Anyway creates the most efficient route for you.
- Flexibility: Vacations are the time to be spontaneous. Anyway lets you update your plans on the go.
- Discoverability: Tourism means exploration. Anyway encourages you to take detours and make spontaneous decisions.

View File

@@ -19,7 +19,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false
id "com.android.application" version "8.7.0" apply false
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
}

View File

@@ -1,4 +1,4 @@
app_identifier("info.anydev.testing") # The bundle identifier of your app
app_identifier("info.anydev.anyway") # The bundle identifier of your app
apple_id("me@moll.re") # Your Apple Developer Portal username
itc_team_id("127439860") # App Store Connect Team ID

View File

@@ -28,17 +28,6 @@ platform :ios do
readonly: true,
)
# replace secrets by real values, the stupid way
sh(
"sed",
"-i",
"",
"s/IOS_GOOGLE_MAPS_API_KEY/#{ENV["IOS_GOOGLE_MAPS_API_KEY"]}/g",
"../Runner/AppDelegate.swift"
)
sh(
"flutter",
"build",
@@ -74,15 +63,6 @@ platform :ios do
readonly: true,
)
# replace secrets by real values, the stupid way
sh(
"sed",
"-i",
"",
"s/IOS_GOOGLE_MAPS_API_KEY/#{ENV["IOS_GOOGLE_MAPS_API_KEY"]}/g",
"../Runner/AppDelegate.swift"
)
sh(
"flutter",
"build",
@@ -101,9 +81,9 @@ platform :ios do
upload_to_app_store(
overwrite_screenshots: true,
metadata_path: "fastlane/metadata",
screenshot_path: "fastlane/screenshots",
screenshots_path: "fastlane/screenshots",
precheck_include_in_app_purchases: false,
force: true, # Skip HTMl report verification
submit_for_review: true,
automatic_release: true,
# automatically release the app after review

View File

@@ -1 +1 @@
2025 anydev

View File

@@ -1,7 +1,7 @@
AnyWay is an application that helps you plan truly unique city trips. When planning a new trip, you can specify <our> preferences and constraints and anyway generates a personalized itinerary just for you.
AnyWay is an application that helps you plan truly unique city trips. When planning a new trip, you can specify your preferences and constraints and anyway generates a personalized itinerary just for you.
Anyway follows these core principles:
- **Personalization**: Trips should be match your interests - not just the most popular destinations.
- **Efficiency**: Don't just walk in circles! Anyway creates the most efficient route for you.
- **Flexibility**: Vacations are the time to be spontaneous. Anyway lets you update your plans on the go.
- **Discoverability**: Tourism means exploration. Anyway encourages you to take detours and make spontaneous decisions.
- Personalization: Trips should be match your interests - not just the most popular destinations.
- Efficiency: Don't just walk in circles! Anyway creates the most efficient route for you.
- Flexibility: Vacations are the time to be spontaneous. Anyway lets you update your plans on the go.
- Discoverability: Tourism means exploration. Anyway encourages you to take detours and make spontaneous decisions.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

View File

@@ -1 +1 @@
AnyWay
Any.Way

View File

@@ -1 +1 @@
TRAVEL

View File

@@ -1 +1 @@
anydev.anyway@gmail.com

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 626 KiB

After

Width:  |  Height:  |  Size: 626 KiB

View File

Before

Width:  |  Height:  |  Size: 758 KiB

After

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

Before

Width:  |  Height:  |  Size: 574 KiB

After

Width:  |  Height:  |  Size: 574 KiB

View File

Before

Width:  |  Height:  |  Size: 800 KiB

After

Width:  |  Height:  |  Size: 800 KiB

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
const String APP_NAME = 'AnyWay';
String API_URL_BASE = 'https://anyway.anydev.info';
String API_URL_DEBUG = 'https://anyway-stg.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: const ColorScheme.light(
primary: PRIMARY_COLOR,
secondary: GRADIENT_END,
surface: Colors.white,
error: Colors.red,
onPrimary: Colors.white,
onSecondary: 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),
)
),
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],
);
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

View File

@@ -0,0 +1,16 @@
import 'package:dio/dio.dart';
class DioClient {
final Dio dio;
DioClient({required String baseUrl}): dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 120),
// also accept 500 errors, since we cannot rule out that the server is at fault. We still want to gracefully handle these errors
validateStatus: (status) => status! <= 500,
receiveDataWhenStatusError: true,
contentType: Headers.jsonContentType,
responseType: ResponseType.json,
));
}

View File

View File

@@ -0,0 +1,83 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
/// Defines the contract for persisting trip payloads locally.
abstract class TripLocalDataSource {
/// Returns all saved trip JSON payloads, newest first.
Future<List<Map<String, dynamic>>> loadTrips();
/// Returns a single trip payload by uuid if present.
/// TODO - should directly return Trip?
Future<Map<String, dynamic>?> getTrip(String uuid);
/// Upserts the provided trip payload (also used for editing existing trips).
Future<void> upsertTrip(Map<String, dynamic> tripJson);
/// Removes the trip with the matching uuid.
Future<void> deleteTrip(String uuid);
}
class TripLocalDataSourceImpl implements TripLocalDataSource {
TripLocalDataSourceImpl({Future<SharedPreferences>? preferences})
: _prefsFuture = preferences ?? SharedPreferences.getInstance();
static const String _storageKey = 'savedTrips';
final Future<SharedPreferences> _prefsFuture;
@override
Future<List<Map<String, dynamic>>> loadTrips() async {
final prefs = await _prefsFuture;
final stored = prefs.getStringList(_storageKey);
if (stored == null) return [];
return stored.map(_decodeTrip).toList();
}
@override
Future<Map<String, dynamic>?> getTrip(String uuid) async {
final trips = await loadTrips();
for (final trip in trips) {
if (trip['uuid'] == uuid) {
return Map<String, dynamic>.from(trip);
}
}
return null;
}
@override
Future<void> upsertTrip(Map<String, dynamic> tripJson) async {
final uuid = tripJson['uuid'];
if (uuid is! String || uuid.isEmpty) {
throw ArgumentError('Trip JSON must contain a uuid string');
}
final trips = await loadTrips();
trips.removeWhere((trip) => trip['uuid'] == uuid);
trips.insert(0, Map<String, dynamic>.from(tripJson));
await _persistTrips(trips);
}
@override
Future<void> deleteTrip(String uuid) async {
final trips = await loadTrips();
final updated = trips.where((trip) => trip['uuid'] != uuid).toList();
if (updated.length == trips.length) {
return;
}
await _persistTrips(updated);
}
Future<void> _persistTrips(List<Map<String, dynamic>> trips) async {
final prefs = await _prefsFuture;
final payload = trips.map(jsonEncode).toList();
await prefs.setStringList(_storageKey, payload);
}
Map<String, dynamic> _decodeTrip(String raw) {
final decoded = jsonDecode(raw);
if (decoded is! Map<String, dynamic>) {
throw const FormatException('Saved trip entry is not a JSON object');
}
return Map<String, dynamic>.from(decoded);
}
}

View File

@@ -0,0 +1,142 @@
import 'dart:convert';
import 'dart:developer';
import 'package:dio/dio.dart';
abstract class TripRemoteDataSource {
/// Fetch available landmarks for the provided preferences/start payload.
Future<List<Map<String, dynamic>>> fetchLandmarks(Map<String, dynamic> body);
/// Create a new trip using the optimizer payload (that includes landmarks).
Future<Map<String, dynamic>> createTrip(Map<String, dynamic> body);
/// Fetch an existing trip by UUID
Future<Map<String, dynamic>> fetchTrip(String uuid);
}
class TripRemoteDataSourceImpl implements TripRemoteDataSource {
final Dio dio;
TripRemoteDataSourceImpl({required this.dio});
@override
Future<List<Map<String, dynamic>>> fetchLandmarks(Map<String, dynamic> body) async {
final response = await dio.post('/get/landmarks', data: body);
if (response.statusCode != 200) {
throw Exception('Server error fetching landmarks: ${response.statusCode}');
}
if (response.data is! List) {
throw Exception('Unexpected landmarks response format');
}
return _normalizeLandmarks(List<dynamic>.from(response.data as List));
}
@override
Future<Map<String, dynamic>> createTrip(Map<String, dynamic> body) async {
log('Creating trip with body: $body');
// convert body to actual json
String bodyJson = jsonEncode(body);
log('Trip request JSON: $bodyJson');
final response = await dio.post('/optimize/trip', data: body);
if (response.statusCode != 200) {
throw Exception('Server error: ${response.statusCode}');
}
if (response.data is! Map<String, dynamic>) {
throw Exception('Unexpected response format');
}
final Map<String, dynamic> json = Map<String, dynamic>.from(response.data as Map);
_ensureLandmarks(json);
if (json.containsKey('landmarks') && json['landmarks'] is List) {
return json;
}
final String? firstUuid = json['first_landmark_uuid'] as String?;
if (firstUuid == null) {
return json;
}
final List<Map<String, dynamic>> landmarks = [];
String? next = firstUuid;
while (next != null) {
final lm = await _fetchLandmarkByUuid(next);
landmarks.add(lm);
final dynamic nxt = lm['next_uuid'];
next = (nxt is String) ? nxt : null;
}
json['landmarks'] = landmarks;
return json;
}
@override
Future<Map<String, dynamic>> fetchTrip(String uuid) async {
final response = await dio.get('/trip/$uuid');
if (response.statusCode != 200) {
throw Exception('Server error: ${response.statusCode}');
}
if (response.data is! Map<String, dynamic>) {
throw Exception('Unexpected response format');
}
final Map<String, dynamic> json = Map<String, dynamic>.from(response.data as Map);
// Normalize same as createTrip: if trip contains first_landmark_uuid, expand chain
if (json.containsKey('landmarks') && json['landmarks'] is List) {
_ensureLandmarks(json);
return json;
}
final String? firstUuid = json['first_landmark_uuid'] as String?;
if (firstUuid == null) return json;
final List<Map<String, dynamic>> landmarks = [];
String? next = firstUuid;
while (next != null) {
final lm = await _fetchLandmarkByUuid(next);
landmarks.add(lm);
final dynamic nxt = lm['next_uuid'];
next = (nxt is String) ? nxt : null;
}
json['landmarks'] = landmarks;
return json;
}
// Fetch a single landmark by uuid via the /landmark/{landmark_uuid} endpoint
Future<Map<String, dynamic>> _fetchLandmarkByUuid(String uuid) async {
final response = await dio.get('/landmark/$uuid');
if (response.statusCode != 200) {
throw Exception('Failed to fetch landmark $uuid: ${response.statusCode}');
}
if (response.data is! Map<String, dynamic>) {
throw Exception('Unexpected landmark response format');
}
return _normalizeLandmark(Map<String, dynamic>.from(response.data as Map));
}
static void _ensureLandmarks(Map<String, dynamic> json) {
final raw = json['landmarks'];
if (raw is List) {
json['landmarks'] = _normalizeLandmarks(raw);
}
}
static List<Map<String, dynamic>> _normalizeLandmarks(List<dynamic> rawList) {
return rawList
.map((item) => _normalizeLandmark(Map<String, dynamic>.from(item as Map)))
.toList();
}
static Map<String, dynamic> _normalizeLandmark(Map<String, dynamic> source) {
final normalized = Map<String, dynamic>.from(source);
final typeValue = normalized['type'];
if (typeValue is String) {
normalized['type'] = {'type': typeValue};
}
return normalized;
}
}

View File

@@ -0,0 +1,32 @@
// TODO - I have the feeling this file is outdated and not used anymore
import 'package:anyway/domain/entities/landmark.dart';
import 'package:anyway/domain/entities/landmark_description.dart';
import 'package:anyway/domain/entities/landmark_type.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'landmark_model.freezed.dart';
part 'landmark_model.g.dart';
@freezed
abstract class LandmarkModel with _$LandmarkModel {
const factory LandmarkModel({
required String uuid,
required String name,
required List<double> location,
required String type,
required bool isSecondary,
required String description,
}) = _LandmarkModel;
const LandmarkModel._();
factory LandmarkModel.fromJson(Map<String, dynamic> json) => _$LandmarkModelFromJson(json);
Landmark toEntity() => Landmark(
uuid: uuid,
name: name,
location: location,
type: LandmarkType(type: LandmarkTypeEnum.values.firstWhere((e) => e.value == type)),
isSecondary: isSecondary,
description: LandmarkDescription(description: description, tags: []),
);
}

View File

@@ -0,0 +1,298 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'landmark_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LandmarkModel {
String get uuid; String get name; List<double> get location; String get type; bool get isSecondary; String get description;
/// Create a copy of LandmarkModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LandmarkModelCopyWith<LandmarkModel> get copyWith => _$LandmarkModelCopyWithImpl<LandmarkModel>(this as LandmarkModel, _$identity);
/// Serializes this LandmarkModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LandmarkModel&&(identical(other.uuid, uuid) || other.uuid == uuid)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.location, location)&&(identical(other.type, type) || other.type == type)&&(identical(other.isSecondary, isSecondary) || other.isSecondary == isSecondary)&&(identical(other.description, description) || other.description == description));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,uuid,name,const DeepCollectionEquality().hash(location),type,isSecondary,description);
@override
String toString() {
return 'LandmarkModel(uuid: $uuid, name: $name, location: $location, type: $type, isSecondary: $isSecondary, description: $description)';
}
}
/// @nodoc
abstract mixin class $LandmarkModelCopyWith<$Res> {
factory $LandmarkModelCopyWith(LandmarkModel value, $Res Function(LandmarkModel) _then) = _$LandmarkModelCopyWithImpl;
@useResult
$Res call({
String uuid, String name, List<double> location, String type, bool isSecondary, String description
});
}
/// @nodoc
class _$LandmarkModelCopyWithImpl<$Res>
implements $LandmarkModelCopyWith<$Res> {
_$LandmarkModelCopyWithImpl(this._self, this._then);
final LandmarkModel _self;
final $Res Function(LandmarkModel) _then;
/// Create a copy of LandmarkModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? uuid = null,Object? name = null,Object? location = null,Object? type = null,Object? isSecondary = null,Object? description = null,}) {
return _then(_self.copyWith(
uuid: null == uuid ? _self.uuid : uuid // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as List<double>,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,isSecondary: null == isSecondary ? _self.isSecondary : isSecondary // ignore: cast_nullable_to_non_nullable
as bool,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [LandmarkModel].
extension LandmarkModelPatterns on LandmarkModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LandmarkModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LandmarkModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LandmarkModel value) $default,){
final _that = this;
switch (_that) {
case _LandmarkModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LandmarkModel value)? $default,){
final _that = this;
switch (_that) {
case _LandmarkModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String uuid, String name, List<double> location, String type, bool isSecondary, String description)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LandmarkModel() when $default != null:
return $default(_that.uuid,_that.name,_that.location,_that.type,_that.isSecondary,_that.description);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String uuid, String name, List<double> location, String type, bool isSecondary, String description) $default,) {final _that = this;
switch (_that) {
case _LandmarkModel():
return $default(_that.uuid,_that.name,_that.location,_that.type,_that.isSecondary,_that.description);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String uuid, String name, List<double> location, String type, bool isSecondary, String description)? $default,) {final _that = this;
switch (_that) {
case _LandmarkModel() when $default != null:
return $default(_that.uuid,_that.name,_that.location,_that.type,_that.isSecondary,_that.description);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LandmarkModel extends LandmarkModel {
const _LandmarkModel({required this.uuid, required this.name, required final List<double> location, required this.type, required this.isSecondary, required this.description}): _location = location,super._();
factory _LandmarkModel.fromJson(Map<String, dynamic> json) => _$LandmarkModelFromJson(json);
@override final String uuid;
@override final String name;
final List<double> _location;
@override List<double> get location {
if (_location is EqualUnmodifiableListView) return _location;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_location);
}
@override final String type;
@override final bool isSecondary;
@override final String description;
/// Create a copy of LandmarkModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LandmarkModelCopyWith<_LandmarkModel> get copyWith => __$LandmarkModelCopyWithImpl<_LandmarkModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LandmarkModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LandmarkModel&&(identical(other.uuid, uuid) || other.uuid == uuid)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._location, _location)&&(identical(other.type, type) || other.type == type)&&(identical(other.isSecondary, isSecondary) || other.isSecondary == isSecondary)&&(identical(other.description, description) || other.description == description));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,uuid,name,const DeepCollectionEquality().hash(_location),type,isSecondary,description);
@override
String toString() {
return 'LandmarkModel(uuid: $uuid, name: $name, location: $location, type: $type, isSecondary: $isSecondary, description: $description)';
}
}
/// @nodoc
abstract mixin class _$LandmarkModelCopyWith<$Res> implements $LandmarkModelCopyWith<$Res> {
factory _$LandmarkModelCopyWith(_LandmarkModel value, $Res Function(_LandmarkModel) _then) = __$LandmarkModelCopyWithImpl;
@override @useResult
$Res call({
String uuid, String name, List<double> location, String type, bool isSecondary, String description
});
}
/// @nodoc
class __$LandmarkModelCopyWithImpl<$Res>
implements _$LandmarkModelCopyWith<$Res> {
__$LandmarkModelCopyWithImpl(this._self, this._then);
final _LandmarkModel _self;
final $Res Function(_LandmarkModel) _then;
/// Create a copy of LandmarkModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? uuid = null,Object? name = null,Object? location = null,Object? type = null,Object? isSecondary = null,Object? description = null,}) {
return _then(_LandmarkModel(
uuid: null == uuid ? _self.uuid : uuid // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,location: null == location ? _self._location : location // ignore: cast_nullable_to_non_nullable
as List<double>,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,isSecondary: null == isSecondary ? _self.isSecondary : isSecondary // ignore: cast_nullable_to_non_nullable
as bool,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View File

@@ -0,0 +1,29 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'landmark_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_LandmarkModel _$LandmarkModelFromJson(Map<String, dynamic> json) =>
_LandmarkModel(
uuid: json['uuid'] as String,
name: json['name'] as String,
location: (json['location'] as List<dynamic>)
.map((e) => (e as num).toDouble())
.toList(),
type: json['type'] as String,
isSecondary: json['isSecondary'] as bool,
description: json['description'] as String,
);
Map<String, dynamic> _$LandmarkModelToJson(_LandmarkModel instance) =>
<String, dynamic>{
'uuid': instance.uuid,
'name': instance.name,
'location': instance.location,
'type': instance.type,
'isSecondary': instance.isSecondary,
'description': instance.description,
};

View File

@@ -0,0 +1,114 @@
import 'package:anyway/data/datasources/trip_local_datasource.dart';
import 'package:anyway/data/datasources/trip_remote_datasource.dart';
import 'package:anyway/domain/entities/landmark.dart';
import 'package:anyway/domain/entities/preferences.dart';
import 'package:anyway/domain/entities/trip.dart';
import 'package:anyway/domain/repositories/trip_repository.dart';
class BackendTripRepository implements TripRepository {
final TripRemoteDataSource remote;
final TripLocalDataSource local;
BackendTripRepository({required this.remote, required this.local});
@override
Future<Trip> getTrip({Preferences? preferences, String? tripUUID, List<Landmark>? landmarks}) async {
try {
Map<String, dynamic> json;
if (tripUUID != null) {
json = await remote.fetchTrip(tripUUID);
} else {
if (preferences == null) {
throw ArgumentError('Either preferences or tripUUID must be provided');
}
final Map<String, dynamic> prefsPayload = _buildPreferencesPayload(preferences);
List<Map<String, dynamic>> landmarkBodies = landmarks != null
? landmarks.map((lm) => lm.toJson()).toList()
: await _fetchLandmarkPayloads(prefsPayload, preferences.startLocation);
// TODO: remove
// restrict the landmark list to 30 to iterate quickly
landmarkBodies = landmarkBodies.take(30).toList();
// change the json key because of backend inconsistency
for (var lm in landmarkBodies) {
if (lm.containsKey('type')) {
lm['type'] = "sightseeing";
}
lm['osm_type'] = 'node';
lm['osm_id'] = 1;
}
final Map<String, dynamic> body = {
'preferences': prefsPayload,
'landmarks': landmarkBodies,
'start': preferences.startLocation,
};
if (preferences.endLocation != null) {
body['end'] = preferences.endLocation;
}
if (preferences.detourToleranceMinutes != null) {
body['detour_tolerance_minute'] = preferences.detourToleranceMinutes;
}
json = await remote.createTrip(body);
}
return Trip.fromJson(json);
} catch (e) {
throw Exception('Failed to fetch trip: ${e.toString()}');
}
}
// TODO - maybe shorten this
@override
Future<List<Landmark>> searchLandmarks(Preferences preferences) async {
final Map<String, dynamic> prefsPayload = _buildPreferencesPayload(preferences);
final List<Map<String, dynamic>> rawLandmarks = await _fetchLandmarkPayloads(prefsPayload, preferences.startLocation);
return rawLandmarks.map((lmJson) => Landmark.fromJson(lmJson)).toList();
}
Future<List<Map<String, dynamic>>> _fetchLandmarkPayloads(
Map<String, dynamic> prefsPayload, List<double> startLocation) async {
final Map<String, dynamic> landmarkRequest = {
'preferences': prefsPayload,
'start': startLocation,
};
return await remote.fetchLandmarks(landmarkRequest);
}
Map<String, dynamic> _buildPreferencesPayload(Preferences preferences) {
final Map<String, dynamic> prefsPayload = {};
preferences.scores.forEach((type, score) {
prefsPayload[type] = {'type': type, 'score': score};
});
prefsPayload['max_time_minute'] = preferences.maxTimeMinutes;
return prefsPayload;
}
// TODO - should this be moved to a separate local repository?
@override
Future<List<Trip>> getSavedTrips() async {
final rawTrips = await local.loadTrips();
return rawTrips.map(Trip.fromJson).toList(growable: false);
}
@override
Future<Trip?> getSavedTrip(String uuid) async {
final json = await local.getTrip(uuid);
return json == null ? null : Trip.fromJson(json);
}
@override
Future<void> saveTrip(Trip trip) async {
await local.upsertTrip(trip.toJson());
}
@override
Future<void> deleteSavedTrip(String uuid) async {
await local.deleteTrip(uuid);
}
}

View File

@@ -0,0 +1,18 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:anyway/domain/repositories/onboarding_repository.dart';
class LocalOnboardingRepository implements OnboardingRepository {
static const _key = 'onboardingCompleted';
@override
Future<bool> isOnboarded() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(_key) ?? false;
}
@override
Future<void> setOnboarded(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_key, value);
}
}

View File

@@ -0,0 +1,50 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:anyway/domain/entities/preferences.dart';
import 'package:anyway/domain/repositories/preferences_repository.dart';
class PreferencesRepositoryImpl implements PreferencesRepository {
static const _key = 'userPreferences';
@override
Future<Preferences> getPreferences() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_key);
if (raw == null) {
// TODO - rethink this
// return a sensible default
return Preferences(
scores: {
'sightseeing': 0,
'shopping': 0,
'nature': 0,
},
maxTimeMinutes: 120,
startLocation: const [48.8575, 2.3514],
);
}
try {
final map = json.decode(raw) as Map<String, dynamic>;
return Preferences.fromJson(map);
} catch (_) {
return Preferences(
scores: {
'sightseeing': 0,
'shopping': 0,
'nature': 0,
},
maxTimeMinutes: 120,
startLocation: const [48.8575, 2.3514],
);
}
}
@override
Future<void> savePreferences(Preferences preferences) async {
final prefs = await SharedPreferences.getInstance();
final raw = json.encode(preferences.toJson());
await prefs.setString(_key, raw);
}
}

View File

@@ -0,0 +1,35 @@
# Domain layer
## `entities` - Model definition
Since we follow the repository structure convention, in this folder we purely define data models, without providing any logic. To reduce boilerplate, we use the `freezed` package. This requires some code generation, which means that all model definitions have the following structure:
```dart
import 'package:freezed_annotation/freezed_annotation.dart';
// required: associates our `main.dart` with the code generated by Freezed
part 'main.freezed.dart';
// optional: Since our Person class is serializable, we must add this line.
// But if Person was not serializable, we could skip it.
part 'main.g.dart';
```
This is required boilerplate for all models. Then, we define the model itself using the `@freezed` annotation:
```dart
@freezed
...
```
The `*.frozen.dart` and `*.g.dart` are pure boilerplate and should not be touched.
Note that the description of the data will losely follow the capabilities of the backend but does not need to reflect it exactly. That is where the `data` part is for: translating api calls into flutter objects.
To ensure the creation of the boilerplate code, run the following command in your terminal:
```bash
flutter pub run build_runner build --delete-conflicting-outputs
```
or interactively:
```
dart run build_runner watch -d
```

View File

@@ -0,0 +1,56 @@
import 'package:anyway/domain/entities/landmark_type.dart';
import 'package:anyway/domain/entities/landmark_description.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'landmark.freezed.dart';
part 'landmark.g.dart';
@unfreezed
abstract class Landmark with _$Landmark {
factory Landmark({
required String uuid,
required String name,
required List<double> location,
required LandmarkType type,
@JsonKey(name: 'is_secondary')
bool? isSecondary,
/// Optional rich description object (may be null if API returns plain string)
LandmarkDescription? description,
@JsonKey(name: 'name_en')
String? nameEn,
@JsonKey(name: 'website_url')
String? websiteUrl,
@JsonKey(name: 'image_url')
String? imageUrl,
@JsonKey(name: 'attractiveness')
int? attractiveness,
@JsonKey(name: 'n_tags')
int? tagCount,
/// Duration at landmark in minutes
@JsonKey(name: 'duration')
int? durationMinutes,
bool? visited,
@JsonKey(name: 'time_to_reach_next')
int? timeToReachNextMinutes,
}) = _Landmark;
factory Landmark.fromJson(Map<String, Object?> json) => _$LandmarkFromJson(json);
}
extension LandmarkVisitX on Landmark {
bool get isVisited => visited ?? false;
set isVisited(bool value) => visited = value;
}

View File

@@ -0,0 +1,350 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'landmark.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Landmark {
String get uuid; set uuid(String value); String get name; set name(String value); List<double> get location; set location(List<double> value); LandmarkType get type; set type(LandmarkType value);@JsonKey(name: 'is_secondary') bool? get isSecondary;@JsonKey(name: 'is_secondary') set isSecondary(bool? value);/// Optional rich description object (may be null if API returns plain string)
LandmarkDescription? get description;/// Optional rich description object (may be null if API returns plain string)
set description(LandmarkDescription? value);@JsonKey(name: 'name_en') String? get nameEn;@JsonKey(name: 'name_en') set nameEn(String? value);@JsonKey(name: 'website_url') String? get websiteUrl;@JsonKey(name: 'website_url') set websiteUrl(String? value);@JsonKey(name: 'image_url') String? get imageUrl;@JsonKey(name: 'image_url') set imageUrl(String? value);@JsonKey(name: 'attractiveness') int? get attractiveness;@JsonKey(name: 'attractiveness') set attractiveness(int? value);@JsonKey(name: 'n_tags') int? get tagCount;@JsonKey(name: 'n_tags') set tagCount(int? value);/// Duration at landmark in minutes
@JsonKey(name: 'duration') int? get durationMinutes;/// Duration at landmark in minutes
@JsonKey(name: 'duration') set durationMinutes(int? value); bool? get visited; set visited(bool? value);@JsonKey(name: 'time_to_reach_next') int? get timeToReachNextMinutes;@JsonKey(name: 'time_to_reach_next') set timeToReachNextMinutes(int? value);
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LandmarkCopyWith<Landmark> get copyWith => _$LandmarkCopyWithImpl<Landmark>(this as Landmark, _$identity);
/// Serializes this Landmark to a JSON map.
Map<String, dynamic> toJson();
@override
String toString() {
return 'Landmark(uuid: $uuid, name: $name, location: $location, type: $type, isSecondary: $isSecondary, description: $description, nameEn: $nameEn, websiteUrl: $websiteUrl, imageUrl: $imageUrl, attractiveness: $attractiveness, tagCount: $tagCount, durationMinutes: $durationMinutes, visited: $visited, timeToReachNextMinutes: $timeToReachNextMinutes)';
}
}
/// @nodoc
abstract mixin class $LandmarkCopyWith<$Res> {
factory $LandmarkCopyWith(Landmark value, $Res Function(Landmark) _then) = _$LandmarkCopyWithImpl;
@useResult
$Res call({
String uuid, String name, List<double> location, LandmarkType type,@JsonKey(name: 'is_secondary') bool? isSecondary, LandmarkDescription? description,@JsonKey(name: 'name_en') String? nameEn,@JsonKey(name: 'website_url') String? websiteUrl,@JsonKey(name: 'image_url') String? imageUrl,@JsonKey(name: 'attractiveness') int? attractiveness,@JsonKey(name: 'n_tags') int? tagCount,@JsonKey(name: 'duration') int? durationMinutes, bool? visited,@JsonKey(name: 'time_to_reach_next') int? timeToReachNextMinutes
});
$LandmarkTypeCopyWith<$Res> get type;$LandmarkDescriptionCopyWith<$Res>? get description;
}
/// @nodoc
class _$LandmarkCopyWithImpl<$Res>
implements $LandmarkCopyWith<$Res> {
_$LandmarkCopyWithImpl(this._self, this._then);
final Landmark _self;
final $Res Function(Landmark) _then;
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? uuid = null,Object? name = null,Object? location = null,Object? type = null,Object? isSecondary = freezed,Object? description = freezed,Object? nameEn = freezed,Object? websiteUrl = freezed,Object? imageUrl = freezed,Object? attractiveness = freezed,Object? tagCount = freezed,Object? durationMinutes = freezed,Object? visited = freezed,Object? timeToReachNextMinutes = freezed,}) {
return _then(_self.copyWith(
uuid: null == uuid ? _self.uuid : uuid // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as List<double>,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as LandmarkType,isSecondary: freezed == isSecondary ? _self.isSecondary : isSecondary // ignore: cast_nullable_to_non_nullable
as bool?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as LandmarkDescription?,nameEn: freezed == nameEn ? _self.nameEn : nameEn // ignore: cast_nullable_to_non_nullable
as String?,websiteUrl: freezed == websiteUrl ? _self.websiteUrl : websiteUrl // ignore: cast_nullable_to_non_nullable
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String?,attractiveness: freezed == attractiveness ? _self.attractiveness : attractiveness // ignore: cast_nullable_to_non_nullable
as int?,tagCount: freezed == tagCount ? _self.tagCount : tagCount // ignore: cast_nullable_to_non_nullable
as int?,durationMinutes: freezed == durationMinutes ? _self.durationMinutes : durationMinutes // ignore: cast_nullable_to_non_nullable
as int?,visited: freezed == visited ? _self.visited : visited // ignore: cast_nullable_to_non_nullable
as bool?,timeToReachNextMinutes: freezed == timeToReachNextMinutes ? _self.timeToReachNextMinutes : timeToReachNextMinutes // ignore: cast_nullable_to_non_nullable
as int?,
));
}
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LandmarkTypeCopyWith<$Res> get type {
return $LandmarkTypeCopyWith<$Res>(_self.type, (value) {
return _then(_self.copyWith(type: value));
});
}/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LandmarkDescriptionCopyWith<$Res>? get description {
if (_self.description == null) {
return null;
}
return $LandmarkDescriptionCopyWith<$Res>(_self.description!, (value) {
return _then(_self.copyWith(description: value));
});
}
}
/// Adds pattern-matching-related methods to [Landmark].
extension LandmarkPatterns on Landmark {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Landmark value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Landmark() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Landmark value) $default,){
final _that = this;
switch (_that) {
case _Landmark():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Landmark value)? $default,){
final _that = this;
switch (_that) {
case _Landmark() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String uuid, String name, List<double> location, LandmarkType type, @JsonKey(name: 'is_secondary') bool? isSecondary, LandmarkDescription? description, @JsonKey(name: 'name_en') String? nameEn, @JsonKey(name: 'website_url') String? websiteUrl, @JsonKey(name: 'image_url') String? imageUrl, @JsonKey(name: 'attractiveness') int? attractiveness, @JsonKey(name: 'n_tags') int? tagCount, @JsonKey(name: 'duration') int? durationMinutes, bool? visited, @JsonKey(name: 'time_to_reach_next') int? timeToReachNextMinutes)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Landmark() when $default != null:
return $default(_that.uuid,_that.name,_that.location,_that.type,_that.isSecondary,_that.description,_that.nameEn,_that.websiteUrl,_that.imageUrl,_that.attractiveness,_that.tagCount,_that.durationMinutes,_that.visited,_that.timeToReachNextMinutes);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String uuid, String name, List<double> location, LandmarkType type, @JsonKey(name: 'is_secondary') bool? isSecondary, LandmarkDescription? description, @JsonKey(name: 'name_en') String? nameEn, @JsonKey(name: 'website_url') String? websiteUrl, @JsonKey(name: 'image_url') String? imageUrl, @JsonKey(name: 'attractiveness') int? attractiveness, @JsonKey(name: 'n_tags') int? tagCount, @JsonKey(name: 'duration') int? durationMinutes, bool? visited, @JsonKey(name: 'time_to_reach_next') int? timeToReachNextMinutes) $default,) {final _that = this;
switch (_that) {
case _Landmark():
return $default(_that.uuid,_that.name,_that.location,_that.type,_that.isSecondary,_that.description,_that.nameEn,_that.websiteUrl,_that.imageUrl,_that.attractiveness,_that.tagCount,_that.durationMinutes,_that.visited,_that.timeToReachNextMinutes);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String uuid, String name, List<double> location, LandmarkType type, @JsonKey(name: 'is_secondary') bool? isSecondary, LandmarkDescription? description, @JsonKey(name: 'name_en') String? nameEn, @JsonKey(name: 'website_url') String? websiteUrl, @JsonKey(name: 'image_url') String? imageUrl, @JsonKey(name: 'attractiveness') int? attractiveness, @JsonKey(name: 'n_tags') int? tagCount, @JsonKey(name: 'duration') int? durationMinutes, bool? visited, @JsonKey(name: 'time_to_reach_next') int? timeToReachNextMinutes)? $default,) {final _that = this;
switch (_that) {
case _Landmark() when $default != null:
return $default(_that.uuid,_that.name,_that.location,_that.type,_that.isSecondary,_that.description,_that.nameEn,_that.websiteUrl,_that.imageUrl,_that.attractiveness,_that.tagCount,_that.durationMinutes,_that.visited,_that.timeToReachNextMinutes);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Landmark implements Landmark {
_Landmark({required this.uuid, required this.name, required this.location, required this.type, @JsonKey(name: 'is_secondary') this.isSecondary, this.description, @JsonKey(name: 'name_en') this.nameEn, @JsonKey(name: 'website_url') this.websiteUrl, @JsonKey(name: 'image_url') this.imageUrl, @JsonKey(name: 'attractiveness') this.attractiveness, @JsonKey(name: 'n_tags') this.tagCount, @JsonKey(name: 'duration') this.durationMinutes, this.visited, @JsonKey(name: 'time_to_reach_next') this.timeToReachNextMinutes});
factory _Landmark.fromJson(Map<String, dynamic> json) => _$LandmarkFromJson(json);
@override String uuid;
@override String name;
@override List<double> location;
@override LandmarkType type;
@override@JsonKey(name: 'is_secondary') bool? isSecondary;
/// Optional rich description object (may be null if API returns plain string)
@override LandmarkDescription? description;
@override@JsonKey(name: 'name_en') String? nameEn;
@override@JsonKey(name: 'website_url') String? websiteUrl;
@override@JsonKey(name: 'image_url') String? imageUrl;
@override@JsonKey(name: 'attractiveness') int? attractiveness;
@override@JsonKey(name: 'n_tags') int? tagCount;
/// Duration at landmark in minutes
@override@JsonKey(name: 'duration') int? durationMinutes;
@override bool? visited;
@override@JsonKey(name: 'time_to_reach_next') int? timeToReachNextMinutes;
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LandmarkCopyWith<_Landmark> get copyWith => __$LandmarkCopyWithImpl<_Landmark>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LandmarkToJson(this, );
}
@override
String toString() {
return 'Landmark(uuid: $uuid, name: $name, location: $location, type: $type, isSecondary: $isSecondary, description: $description, nameEn: $nameEn, websiteUrl: $websiteUrl, imageUrl: $imageUrl, attractiveness: $attractiveness, tagCount: $tagCount, durationMinutes: $durationMinutes, visited: $visited, timeToReachNextMinutes: $timeToReachNextMinutes)';
}
}
/// @nodoc
abstract mixin class _$LandmarkCopyWith<$Res> implements $LandmarkCopyWith<$Res> {
factory _$LandmarkCopyWith(_Landmark value, $Res Function(_Landmark) _then) = __$LandmarkCopyWithImpl;
@override @useResult
$Res call({
String uuid, String name, List<double> location, LandmarkType type,@JsonKey(name: 'is_secondary') bool? isSecondary, LandmarkDescription? description,@JsonKey(name: 'name_en') String? nameEn,@JsonKey(name: 'website_url') String? websiteUrl,@JsonKey(name: 'image_url') String? imageUrl,@JsonKey(name: 'attractiveness') int? attractiveness,@JsonKey(name: 'n_tags') int? tagCount,@JsonKey(name: 'duration') int? durationMinutes, bool? visited,@JsonKey(name: 'time_to_reach_next') int? timeToReachNextMinutes
});
@override $LandmarkTypeCopyWith<$Res> get type;@override $LandmarkDescriptionCopyWith<$Res>? get description;
}
/// @nodoc
class __$LandmarkCopyWithImpl<$Res>
implements _$LandmarkCopyWith<$Res> {
__$LandmarkCopyWithImpl(this._self, this._then);
final _Landmark _self;
final $Res Function(_Landmark) _then;
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? uuid = null,Object? name = null,Object? location = null,Object? type = null,Object? isSecondary = freezed,Object? description = freezed,Object? nameEn = freezed,Object? websiteUrl = freezed,Object? imageUrl = freezed,Object? attractiveness = freezed,Object? tagCount = freezed,Object? durationMinutes = freezed,Object? visited = freezed,Object? timeToReachNextMinutes = freezed,}) {
return _then(_Landmark(
uuid: null == uuid ? _self.uuid : uuid // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as List<double>,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as LandmarkType,isSecondary: freezed == isSecondary ? _self.isSecondary : isSecondary // ignore: cast_nullable_to_non_nullable
as bool?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as LandmarkDescription?,nameEn: freezed == nameEn ? _self.nameEn : nameEn // ignore: cast_nullable_to_non_nullable
as String?,websiteUrl: freezed == websiteUrl ? _self.websiteUrl : websiteUrl // ignore: cast_nullable_to_non_nullable
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String?,attractiveness: freezed == attractiveness ? _self.attractiveness : attractiveness // ignore: cast_nullable_to_non_nullable
as int?,tagCount: freezed == tagCount ? _self.tagCount : tagCount // ignore: cast_nullable_to_non_nullable
as int?,durationMinutes: freezed == durationMinutes ? _self.durationMinutes : durationMinutes // ignore: cast_nullable_to_non_nullable
as int?,visited: freezed == visited ? _self.visited : visited // ignore: cast_nullable_to_non_nullable
as bool?,timeToReachNextMinutes: freezed == timeToReachNextMinutes ? _self.timeToReachNextMinutes : timeToReachNextMinutes // ignore: cast_nullable_to_non_nullable
as int?,
));
}
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LandmarkTypeCopyWith<$Res> get type {
return $LandmarkTypeCopyWith<$Res>(_self.type, (value) {
return _then(_self.copyWith(type: value));
});
}/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LandmarkDescriptionCopyWith<$Res>? get description {
if (_self.description == null) {
return null;
}
return $LandmarkDescriptionCopyWith<$Res>(_self.description!, (value) {
return _then(_self.copyWith(description: value));
});
}
}
// dart format on

View File

@@ -0,0 +1,47 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'landmark.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Landmark _$LandmarkFromJson(Map<String, dynamic> json) => _Landmark(
uuid: json['uuid'] as String,
name: json['name'] as String,
location: (json['location'] as List<dynamic>)
.map((e) => (e as num).toDouble())
.toList(),
type: LandmarkType.fromJson(json['type'] as Map<String, dynamic>),
isSecondary: json['is_secondary'] as bool?,
description: json['description'] == null
? null
: LandmarkDescription.fromJson(
json['description'] as Map<String, dynamic>,
),
nameEn: json['name_en'] as String?,
websiteUrl: json['website_url'] as String?,
imageUrl: json['image_url'] as String?,
attractiveness: (json['attractiveness'] as num?)?.toInt(),
tagCount: (json['n_tags'] as num?)?.toInt(),
durationMinutes: (json['duration'] as num?)?.toInt(),
visited: json['visited'] as bool?,
timeToReachNextMinutes: (json['time_to_reach_next'] as num?)?.toInt(),
);
Map<String, dynamic> _$LandmarkToJson(_Landmark instance) => <String, dynamic>{
'uuid': instance.uuid,
'name': instance.name,
'location': instance.location,
'type': instance.type,
'is_secondary': instance.isSecondary,
'description': instance.description,
'name_en': instance.nameEn,
'website_url': instance.websiteUrl,
'image_url': instance.imageUrl,
'attractiveness': instance.attractiveness,
'n_tags': instance.tagCount,
'duration': instance.durationMinutes,
'visited': instance.visited,
'time_to_reach_next': instance.timeToReachNextMinutes,
};

View File

@@ -0,0 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'landmark_description.freezed.dart';
part 'landmark_description.g.dart';
@freezed
abstract class LandmarkDescription with _$LandmarkDescription {
const factory LandmarkDescription({
required String description,
required List<String> tags,
}) = _LandmarkDescription;
factory LandmarkDescription.fromJson(Map<String, Object?> json) => _$LandmarkDescriptionFromJson(json);
}

View File

@@ -0,0 +1,286 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'landmark_description.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LandmarkDescription {
String get description; List<String> get tags;
/// Create a copy of LandmarkDescription
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LandmarkDescriptionCopyWith<LandmarkDescription> get copyWith => _$LandmarkDescriptionCopyWithImpl<LandmarkDescription>(this as LandmarkDescription, _$identity);
/// Serializes this LandmarkDescription to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LandmarkDescription&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.tags, tags));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,description,const DeepCollectionEquality().hash(tags));
@override
String toString() {
return 'LandmarkDescription(description: $description, tags: $tags)';
}
}
/// @nodoc
abstract mixin class $LandmarkDescriptionCopyWith<$Res> {
factory $LandmarkDescriptionCopyWith(LandmarkDescription value, $Res Function(LandmarkDescription) _then) = _$LandmarkDescriptionCopyWithImpl;
@useResult
$Res call({
String description, List<String> tags
});
}
/// @nodoc
class _$LandmarkDescriptionCopyWithImpl<$Res>
implements $LandmarkDescriptionCopyWith<$Res> {
_$LandmarkDescriptionCopyWithImpl(this._self, this._then);
final LandmarkDescription _self;
final $Res Function(LandmarkDescription) _then;
/// Create a copy of LandmarkDescription
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? description = null,Object? tags = null,}) {
return _then(_self.copyWith(
description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// Adds pattern-matching-related methods to [LandmarkDescription].
extension LandmarkDescriptionPatterns on LandmarkDescription {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LandmarkDescription value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LandmarkDescription() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LandmarkDescription value) $default,){
final _that = this;
switch (_that) {
case _LandmarkDescription():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LandmarkDescription value)? $default,){
final _that = this;
switch (_that) {
case _LandmarkDescription() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String description, List<String> tags)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LandmarkDescription() when $default != null:
return $default(_that.description,_that.tags);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String description, List<String> tags) $default,) {final _that = this;
switch (_that) {
case _LandmarkDescription():
return $default(_that.description,_that.tags);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String description, List<String> tags)? $default,) {final _that = this;
switch (_that) {
case _LandmarkDescription() when $default != null:
return $default(_that.description,_that.tags);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LandmarkDescription implements LandmarkDescription {
const _LandmarkDescription({required this.description, required final List<String> tags}): _tags = tags;
factory _LandmarkDescription.fromJson(Map<String, dynamic> json) => _$LandmarkDescriptionFromJson(json);
@override final String description;
final List<String> _tags;
@override List<String> get tags {
if (_tags is EqualUnmodifiableListView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_tags);
}
/// Create a copy of LandmarkDescription
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LandmarkDescriptionCopyWith<_LandmarkDescription> get copyWith => __$LandmarkDescriptionCopyWithImpl<_LandmarkDescription>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LandmarkDescriptionToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LandmarkDescription&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._tags, _tags));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,description,const DeepCollectionEquality().hash(_tags));
@override
String toString() {
return 'LandmarkDescription(description: $description, tags: $tags)';
}
}
/// @nodoc
abstract mixin class _$LandmarkDescriptionCopyWith<$Res> implements $LandmarkDescriptionCopyWith<$Res> {
factory _$LandmarkDescriptionCopyWith(_LandmarkDescription value, $Res Function(_LandmarkDescription) _then) = __$LandmarkDescriptionCopyWithImpl;
@override @useResult
$Res call({
String description, List<String> tags
});
}
/// @nodoc
class __$LandmarkDescriptionCopyWithImpl<$Res>
implements _$LandmarkDescriptionCopyWith<$Res> {
__$LandmarkDescriptionCopyWithImpl(this._self, this._then);
final _LandmarkDescription _self;
final $Res Function(_LandmarkDescription) _then;
/// Create a copy of LandmarkDescription
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? description = null,Object? tags = null,}) {
return _then(_LandmarkDescription(
description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
// dart format on

View File

@@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'landmark_description.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_LandmarkDescription _$LandmarkDescriptionFromJson(Map<String, dynamic> json) =>
_LandmarkDescription(
description: json['description'] as String,
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> _$LandmarkDescriptionToJson(
_LandmarkDescription instance,
) => <String, dynamic>{
'description': instance.description,
'tags': instance.tags,
};

View File

@@ -0,0 +1,31 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'landmark_type.freezed.dart';
part 'landmark_type.g.dart';
@freezed
abstract class LandmarkType with _$LandmarkType {
const factory LandmarkType({
required LandmarkTypeEnum type,
}) = _LandmarkType;
factory LandmarkType.fromJson(Map<String, Object?> json) => _$LandmarkTypeFromJson(json);
}
@JsonEnum(alwaysCreate: true)
enum LandmarkTypeEnum {
@JsonValue('sightseeing')
sightseeing,
@JsonValue('nature')
nature,
@JsonValue('shopping')
shopping,
@JsonValue('start')
start,
@JsonValue('finish')
finish,
}
extension LandmarkTypeEnumExtension on LandmarkTypeEnum {
String get value => _$LandmarkTypeEnumEnumMap[this]!;
}

View File

@@ -0,0 +1,277 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'landmark_type.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LandmarkType {
LandmarkTypeEnum get type;
/// Create a copy of LandmarkType
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LandmarkTypeCopyWith<LandmarkType> get copyWith => _$LandmarkTypeCopyWithImpl<LandmarkType>(this as LandmarkType, _$identity);
/// Serializes this LandmarkType to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LandmarkType&&(identical(other.type, type) || other.type == type));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type);
@override
String toString() {
return 'LandmarkType(type: $type)';
}
}
/// @nodoc
abstract mixin class $LandmarkTypeCopyWith<$Res> {
factory $LandmarkTypeCopyWith(LandmarkType value, $Res Function(LandmarkType) _then) = _$LandmarkTypeCopyWithImpl;
@useResult
$Res call({
LandmarkTypeEnum type
});
}
/// @nodoc
class _$LandmarkTypeCopyWithImpl<$Res>
implements $LandmarkTypeCopyWith<$Res> {
_$LandmarkTypeCopyWithImpl(this._self, this._then);
final LandmarkType _self;
final $Res Function(LandmarkType) _then;
/// Create a copy of LandmarkType
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,}) {
return _then(_self.copyWith(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as LandmarkTypeEnum,
));
}
}
/// Adds pattern-matching-related methods to [LandmarkType].
extension LandmarkTypePatterns on LandmarkType {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LandmarkType value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LandmarkType() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LandmarkType value) $default,){
final _that = this;
switch (_that) {
case _LandmarkType():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LandmarkType value)? $default,){
final _that = this;
switch (_that) {
case _LandmarkType() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( LandmarkTypeEnum type)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LandmarkType() when $default != null:
return $default(_that.type);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( LandmarkTypeEnum type) $default,) {final _that = this;
switch (_that) {
case _LandmarkType():
return $default(_that.type);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( LandmarkTypeEnum type)? $default,) {final _that = this;
switch (_that) {
case _LandmarkType() when $default != null:
return $default(_that.type);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LandmarkType implements LandmarkType {
const _LandmarkType({required this.type});
factory _LandmarkType.fromJson(Map<String, dynamic> json) => _$LandmarkTypeFromJson(json);
@override final LandmarkTypeEnum type;
/// Create a copy of LandmarkType
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LandmarkTypeCopyWith<_LandmarkType> get copyWith => __$LandmarkTypeCopyWithImpl<_LandmarkType>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LandmarkTypeToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LandmarkType&&(identical(other.type, type) || other.type == type));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type);
@override
String toString() {
return 'LandmarkType(type: $type)';
}
}
/// @nodoc
abstract mixin class _$LandmarkTypeCopyWith<$Res> implements $LandmarkTypeCopyWith<$Res> {
factory _$LandmarkTypeCopyWith(_LandmarkType value, $Res Function(_LandmarkType) _then) = __$LandmarkTypeCopyWithImpl;
@override @useResult
$Res call({
LandmarkTypeEnum type
});
}
/// @nodoc
class __$LandmarkTypeCopyWithImpl<$Res>
implements _$LandmarkTypeCopyWith<$Res> {
__$LandmarkTypeCopyWithImpl(this._self, this._then);
final _LandmarkType _self;
final $Res Function(_LandmarkType) _then;
/// Create a copy of LandmarkType
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,}) {
return _then(_LandmarkType(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as LandmarkTypeEnum,
));
}
}
// dart format on

View File

@@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'landmark_type.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_LandmarkType _$LandmarkTypeFromJson(Map<String, dynamic> json) =>
_LandmarkType(type: $enumDecode(_$LandmarkTypeEnumEnumMap, json['type']));
Map<String, dynamic> _$LandmarkTypeToJson(_LandmarkType instance) =>
<String, dynamic>{'type': _$LandmarkTypeEnumEnumMap[instance.type]!};
const _$LandmarkTypeEnumEnumMap = {
LandmarkTypeEnum.sightseeing: 'sightseeing',
LandmarkTypeEnum.nature: 'nature',
LandmarkTypeEnum.shopping: 'shopping',
LandmarkTypeEnum.start: 'start',
LandmarkTypeEnum.finish: 'finish',
};

View File

@@ -0,0 +1,25 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'preferences.freezed.dart';
part 'preferences.g.dart';
@freezed
abstract class Preferences with _$Preferences {
const factory Preferences({
/// Scores keyed by preference type (e.g. 'sightseeing', 'shopping', 'nature')
required Map<String, int> scores,
/// Maximum trip duration in minutes
required int maxTimeMinutes,
/// Required start location [lat, lon]
required List<double> startLocation,
/// Optional end location
List<double>? endLocation,
/// Optional detour tolerance in minutes
int? detourToleranceMinutes,
}) = _Preferences;
factory Preferences.fromJson(Map<String, Object?> json) => _$PreferencesFromJson(json);
}

View File

@@ -0,0 +1,322 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'preferences.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Preferences {
/// Scores keyed by preference type (e.g. 'sightseeing', 'shopping', 'nature')
Map<String, int> get scores;/// Maximum trip duration in minutes
int get maxTimeMinutes;/// Required start location [lat, lon]
List<double> get startLocation;/// Optional end location
List<double>? get endLocation;/// Optional detour tolerance in minutes
int? get detourToleranceMinutes;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PreferencesCopyWith<Preferences> get copyWith => _$PreferencesCopyWithImpl<Preferences>(this as Preferences, _$identity);
/// Serializes this Preferences to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Preferences&&const DeepCollectionEquality().equals(other.scores, scores)&&(identical(other.maxTimeMinutes, maxTimeMinutes) || other.maxTimeMinutes == maxTimeMinutes)&&const DeepCollectionEquality().equals(other.startLocation, startLocation)&&const DeepCollectionEquality().equals(other.endLocation, endLocation)&&(identical(other.detourToleranceMinutes, detourToleranceMinutes) || other.detourToleranceMinutes == detourToleranceMinutes));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(scores),maxTimeMinutes,const DeepCollectionEquality().hash(startLocation),const DeepCollectionEquality().hash(endLocation),detourToleranceMinutes);
@override
String toString() {
return 'Preferences(scores: $scores, maxTimeMinutes: $maxTimeMinutes, startLocation: $startLocation, endLocation: $endLocation, detourToleranceMinutes: $detourToleranceMinutes)';
}
}
/// @nodoc
abstract mixin class $PreferencesCopyWith<$Res> {
factory $PreferencesCopyWith(Preferences value, $Res Function(Preferences) _then) = _$PreferencesCopyWithImpl;
@useResult
$Res call({
Map<String, int> scores, int maxTimeMinutes, List<double> startLocation, List<double>? endLocation, int? detourToleranceMinutes
});
}
/// @nodoc
class _$PreferencesCopyWithImpl<$Res>
implements $PreferencesCopyWith<$Res> {
_$PreferencesCopyWithImpl(this._self, this._then);
final Preferences _self;
final $Res Function(Preferences) _then;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? scores = null,Object? maxTimeMinutes = null,Object? startLocation = null,Object? endLocation = freezed,Object? detourToleranceMinutes = freezed,}) {
return _then(_self.copyWith(
scores: null == scores ? _self.scores : scores // ignore: cast_nullable_to_non_nullable
as Map<String, int>,maxTimeMinutes: null == maxTimeMinutes ? _self.maxTimeMinutes : maxTimeMinutes // ignore: cast_nullable_to_non_nullable
as int,startLocation: null == startLocation ? _self.startLocation : startLocation // ignore: cast_nullable_to_non_nullable
as List<double>,endLocation: freezed == endLocation ? _self.endLocation : endLocation // ignore: cast_nullable_to_non_nullable
as List<double>?,detourToleranceMinutes: freezed == detourToleranceMinutes ? _self.detourToleranceMinutes : detourToleranceMinutes // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// Adds pattern-matching-related methods to [Preferences].
extension PreferencesPatterns on Preferences {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Preferences value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Preferences value) $default,){
final _that = this;
switch (_that) {
case _Preferences():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Preferences value)? $default,){
final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Map<String, int> scores, int maxTimeMinutes, List<double> startLocation, List<double>? endLocation, int? detourToleranceMinutes)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that.scores,_that.maxTimeMinutes,_that.startLocation,_that.endLocation,_that.detourToleranceMinutes);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Map<String, int> scores, int maxTimeMinutes, List<double> startLocation, List<double>? endLocation, int? detourToleranceMinutes) $default,) {final _that = this;
switch (_that) {
case _Preferences():
return $default(_that.scores,_that.maxTimeMinutes,_that.startLocation,_that.endLocation,_that.detourToleranceMinutes);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Map<String, int> scores, int maxTimeMinutes, List<double> startLocation, List<double>? endLocation, int? detourToleranceMinutes)? $default,) {final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that.scores,_that.maxTimeMinutes,_that.startLocation,_that.endLocation,_that.detourToleranceMinutes);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Preferences implements Preferences {
const _Preferences({required final Map<String, int> scores, required this.maxTimeMinutes, required final List<double> startLocation, final List<double>? endLocation, this.detourToleranceMinutes}): _scores = scores,_startLocation = startLocation,_endLocation = endLocation;
factory _Preferences.fromJson(Map<String, dynamic> json) => _$PreferencesFromJson(json);
/// Scores keyed by preference type (e.g. 'sightseeing', 'shopping', 'nature')
final Map<String, int> _scores;
/// Scores keyed by preference type (e.g. 'sightseeing', 'shopping', 'nature')
@override Map<String, int> get scores {
if (_scores is EqualUnmodifiableMapView) return _scores;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_scores);
}
/// Maximum trip duration in minutes
@override final int maxTimeMinutes;
/// Required start location [lat, lon]
final List<double> _startLocation;
/// Required start location [lat, lon]
@override List<double> get startLocation {
if (_startLocation is EqualUnmodifiableListView) return _startLocation;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_startLocation);
}
/// Optional end location
final List<double>? _endLocation;
/// Optional end location
@override List<double>? get endLocation {
final value = _endLocation;
if (value == null) return null;
if (_endLocation is EqualUnmodifiableListView) return _endLocation;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
/// Optional detour tolerance in minutes
@override final int? detourToleranceMinutes;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PreferencesCopyWith<_Preferences> get copyWith => __$PreferencesCopyWithImpl<_Preferences>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$PreferencesToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Preferences&&const DeepCollectionEquality().equals(other._scores, _scores)&&(identical(other.maxTimeMinutes, maxTimeMinutes) || other.maxTimeMinutes == maxTimeMinutes)&&const DeepCollectionEquality().equals(other._startLocation, _startLocation)&&const DeepCollectionEquality().equals(other._endLocation, _endLocation)&&(identical(other.detourToleranceMinutes, detourToleranceMinutes) || other.detourToleranceMinutes == detourToleranceMinutes));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_scores),maxTimeMinutes,const DeepCollectionEquality().hash(_startLocation),const DeepCollectionEquality().hash(_endLocation),detourToleranceMinutes);
@override
String toString() {
return 'Preferences(scores: $scores, maxTimeMinutes: $maxTimeMinutes, startLocation: $startLocation, endLocation: $endLocation, detourToleranceMinutes: $detourToleranceMinutes)';
}
}
/// @nodoc
abstract mixin class _$PreferencesCopyWith<$Res> implements $PreferencesCopyWith<$Res> {
factory _$PreferencesCopyWith(_Preferences value, $Res Function(_Preferences) _then) = __$PreferencesCopyWithImpl;
@override @useResult
$Res call({
Map<String, int> scores, int maxTimeMinutes, List<double> startLocation, List<double>? endLocation, int? detourToleranceMinutes
});
}
/// @nodoc
class __$PreferencesCopyWithImpl<$Res>
implements _$PreferencesCopyWith<$Res> {
__$PreferencesCopyWithImpl(this._self, this._then);
final _Preferences _self;
final $Res Function(_Preferences) _then;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? scores = null,Object? maxTimeMinutes = null,Object? startLocation = null,Object? endLocation = freezed,Object? detourToleranceMinutes = freezed,}) {
return _then(_Preferences(
scores: null == scores ? _self._scores : scores // ignore: cast_nullable_to_non_nullable
as Map<String, int>,maxTimeMinutes: null == maxTimeMinutes ? _self.maxTimeMinutes : maxTimeMinutes // ignore: cast_nullable_to_non_nullable
as int,startLocation: null == startLocation ? _self._startLocation : startLocation // ignore: cast_nullable_to_non_nullable
as List<double>,endLocation: freezed == endLocation ? _self._endLocation : endLocation // ignore: cast_nullable_to_non_nullable
as List<double>?,detourToleranceMinutes: freezed == detourToleranceMinutes ? _self.detourToleranceMinutes : detourToleranceMinutes // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
// dart format on

View File

@@ -0,0 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'preferences.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Preferences _$PreferencesFromJson(Map<String, dynamic> json) => _Preferences(
scores: Map<String, int>.from(json['scores'] as Map),
maxTimeMinutes: (json['maxTimeMinutes'] as num).toInt(),
startLocation: (json['startLocation'] as List<dynamic>)
.map((e) => (e as num).toDouble())
.toList(),
endLocation: (json['endLocation'] as List<dynamic>?)
?.map((e) => (e as num).toDouble())
.toList(),
detourToleranceMinutes: (json['detourToleranceMinutes'] as num?)?.toInt(),
);
Map<String, dynamic> _$PreferencesToJson(_Preferences instance) =>
<String, dynamic>{
'scores': instance.scores,
'maxTimeMinutes': instance.maxTimeMinutes,
'startLocation': instance.startLocation,
'endLocation': instance.endLocation,
'detourToleranceMinutes': instance.detourToleranceMinutes,
};

View File

@@ -0,0 +1,24 @@
import 'package:anyway/domain/entities/landmark.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'trip.freezed.dart';
part 'trip.g.dart';
@unfreezed
abstract class Trip with _$Trip {
factory Trip({
required String uuid,
// Duration totalTime,
/// total time in minutes
int? totalTimeMinutes,
/// ordered list of landmarks in this trip
required List<Landmark> landmarks,
}) = _Trip;
factory Trip.fromJson(Map<String, Object?> json) => _$TripFromJson(json);
}

View File

@@ -0,0 +1,278 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'trip.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Trip {
String get uuid; set uuid(String value);// Duration totalTime,
/// total time in minutes
int? get totalTimeMinutes;// Duration totalTime,
/// total time in minutes
set totalTimeMinutes(int? value);/// ordered list of landmarks in this trip
List<Landmark> get landmarks;/// ordered list of landmarks in this trip
set landmarks(List<Landmark> value);
/// Create a copy of Trip
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TripCopyWith<Trip> get copyWith => _$TripCopyWithImpl<Trip>(this as Trip, _$identity);
/// Serializes this Trip to a JSON map.
Map<String, dynamic> toJson();
@override
String toString() {
return 'Trip(uuid: $uuid, totalTimeMinutes: $totalTimeMinutes, landmarks: $landmarks)';
}
}
/// @nodoc
abstract mixin class $TripCopyWith<$Res> {
factory $TripCopyWith(Trip value, $Res Function(Trip) _then) = _$TripCopyWithImpl;
@useResult
$Res call({
String uuid, int? totalTimeMinutes, List<Landmark> landmarks
});
}
/// @nodoc
class _$TripCopyWithImpl<$Res>
implements $TripCopyWith<$Res> {
_$TripCopyWithImpl(this._self, this._then);
final Trip _self;
final $Res Function(Trip) _then;
/// Create a copy of Trip
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? uuid = null,Object? totalTimeMinutes = freezed,Object? landmarks = null,}) {
return _then(_self.copyWith(
uuid: null == uuid ? _self.uuid : uuid // ignore: cast_nullable_to_non_nullable
as String,totalTimeMinutes: freezed == totalTimeMinutes ? _self.totalTimeMinutes : totalTimeMinutes // ignore: cast_nullable_to_non_nullable
as int?,landmarks: null == landmarks ? _self.landmarks : landmarks // ignore: cast_nullable_to_non_nullable
as List<Landmark>,
));
}
}
/// Adds pattern-matching-related methods to [Trip].
extension TripPatterns on Trip {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Trip value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Trip() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Trip value) $default,){
final _that = this;
switch (_that) {
case _Trip():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Trip value)? $default,){
final _that = this;
switch (_that) {
case _Trip() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String uuid, int? totalTimeMinutes, List<Landmark> landmarks)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Trip() when $default != null:
return $default(_that.uuid,_that.totalTimeMinutes,_that.landmarks);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String uuid, int? totalTimeMinutes, List<Landmark> landmarks) $default,) {final _that = this;
switch (_that) {
case _Trip():
return $default(_that.uuid,_that.totalTimeMinutes,_that.landmarks);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String uuid, int? totalTimeMinutes, List<Landmark> landmarks)? $default,) {final _that = this;
switch (_that) {
case _Trip() when $default != null:
return $default(_that.uuid,_that.totalTimeMinutes,_that.landmarks);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Trip implements Trip {
_Trip({required this.uuid, this.totalTimeMinutes, required this.landmarks});
factory _Trip.fromJson(Map<String, dynamic> json) => _$TripFromJson(json);
@override String uuid;
// Duration totalTime,
/// total time in minutes
@override int? totalTimeMinutes;
/// ordered list of landmarks in this trip
@override List<Landmark> landmarks;
/// Create a copy of Trip
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$TripCopyWith<_Trip> get copyWith => __$TripCopyWithImpl<_Trip>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$TripToJson(this, );
}
@override
String toString() {
return 'Trip(uuid: $uuid, totalTimeMinutes: $totalTimeMinutes, landmarks: $landmarks)';
}
}
/// @nodoc
abstract mixin class _$TripCopyWith<$Res> implements $TripCopyWith<$Res> {
factory _$TripCopyWith(_Trip value, $Res Function(_Trip) _then) = __$TripCopyWithImpl;
@override @useResult
$Res call({
String uuid, int? totalTimeMinutes, List<Landmark> landmarks
});
}
/// @nodoc
class __$TripCopyWithImpl<$Res>
implements _$TripCopyWith<$Res> {
__$TripCopyWithImpl(this._self, this._then);
final _Trip _self;
final $Res Function(_Trip) _then;
/// Create a copy of Trip
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? uuid = null,Object? totalTimeMinutes = freezed,Object? landmarks = null,}) {
return _then(_Trip(
uuid: null == uuid ? _self.uuid : uuid // ignore: cast_nullable_to_non_nullable
as String,totalTimeMinutes: freezed == totalTimeMinutes ? _self.totalTimeMinutes : totalTimeMinutes // ignore: cast_nullable_to_non_nullable
as int?,landmarks: null == landmarks ? _self.landmarks : landmarks // ignore: cast_nullable_to_non_nullable
as List<Landmark>,
));
}
}
// dart format on

View File

@@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'trip.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Trip _$TripFromJson(Map<String, dynamic> json) => _Trip(
uuid: json['uuid'] as String,
totalTimeMinutes: (json['totalTimeMinutes'] as num?)?.toInt(),
landmarks: (json['landmarks'] as List<dynamic>)
.map((e) => Landmark.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$TripToJson(_Trip instance) => <String, dynamic>{
'uuid': instance.uuid,
'totalTimeMinutes': instance.totalTimeMinutes,
'landmarks': instance.landmarks,
};

View File

@@ -0,0 +1,7 @@
abstract class OnboardingRepository {
/// Returns true when the user accepted the onboarding agreement
Future<bool> isOnboarded();
/// Sets the onboarding completion flag
Future<void> setOnboarded(bool value);
}

View File

@@ -0,0 +1,6 @@
import 'package:anyway/domain/entities/preferences.dart';
abstract class PreferencesRepository {
Future<Preferences> getPreferences();
Future<void> savePreferences(Preferences preferences);
}

View File

@@ -0,0 +1,19 @@
import 'package:anyway/domain/entities/landmark.dart';
import 'package:anyway/domain/entities/preferences.dart';
import 'package:anyway/domain/entities/trip.dart';
abstract class TripRepository {
Future<Trip> getTrip({Preferences? preferences, String? tripUUID, List<Landmark>? landmarks});
Future<List<Landmark>> searchLandmarks(Preferences preferences);
// TODO - should these be moved to a separate local repository?
// not every TripRepository should have a concept of "all saved trips"
Future<List<Trip>> getSavedTrips();
Future<Trip?> getSavedTrip(String uuid);
Future<void> saveTrip(Trip trip);
Future<void> deleteSavedTrip(String uuid);
}

View File

@@ -1,26 +1,39 @@
import 'package:anyway/presentation/pages/start.dart';
import 'package:flutter/material.dart';
import 'package:anyway/constants.dart';
import 'package:anyway/utils/get_first_page.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:anyway/core/constants.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(const App());
/// The app entry point.
/// Initializes persistence, sets up dependency injection via ProviderScope,
/// and determines which screen (login or main app) to show based on auth state.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Some global variables
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
final SavedTrips savedTrips = SavedTrips();
// the list of saved trips is then populated implicitly by getFirstPage()
// initialize local persistence (shared preferences)
SharedPreferences prefs = await SharedPreferences.getInstance();
// the app wrapped in ProviderScope
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
const MyApp({super.key});
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: APP_NAME,
home: getFirstPage(),
theme: APP_THEME,
scaffoldMessengerKey: rootScaffoldMessengerKey
);
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: APP_NAME,
theme: APP_THEME,
scaffoldMessengerKey: rootScaffoldMessengerKey,
home: const StartPage()
// TODO - set up routing
// onGenerateRoute: AppRouter.onGenerateRoute,
);
}
}

View File

@@ -1,64 +0,0 @@
import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart';
import 'package:anyway/structs/trip.dart';
class TripsOverview extends StatefulWidget {
final SavedTrips trips;
const TripsOverview({
super.key,
required this.trips,
});
@override
State<TripsOverview> createState() => _TripsOverviewState();
}
class _TripsOverviewState extends State<TripsOverview> {
Widget listBuild (BuildContext context, SavedTrips trips) {
List<Widget> children;
List<Trip> items = trips.trips;
children = List<Widget>.generate(items.length, (index) {
Trip trip = items[index];
return ListTile(
title: FutureBuilder(
future: trip.cityName,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text("Trip to ${snapshot.data}");
} else if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
} else {
return const Text("Trip to ...");
}
},
),
leading: Icon(Icons.pin_drop),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => TripPage(trip: trip)
)
);
},
);
});
return ListView(
children: children,
padding: const EdgeInsets.only(top: 0),
);
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: widget.trips,
builder: (BuildContext context, Widget? child) {
return listBuild(context, widget.trips);
}
);
}
}

View File

@@ -26,13 +26,13 @@ ThemeData APP_THEME = ThemeData(
cardColor: Colors.white,
useMaterial3: true,
colorScheme: ColorScheme.light(
colorScheme: const 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),
onSecondary: Color.fromARGB(255, 30, 22, 22),
onSurface: Colors.black,
onError: Colors.white,
brightness: Brightness.light,
@@ -64,12 +64,6 @@ ThemeData APP_THEME = ThemeData(
),
cardTheme: const CardTheme(
shadowColor: Colors.grey,
elevation: 2,
margin: EdgeInsets.all(10),
),
sliderTheme: const SliderThemeData(
trackHeight: 15,
inactiveTrackColor: Colors.grey,
@@ -83,4 +77,4 @@ const Gradient APP_GRADIENT = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [GRADIENT_START, GRADIENT_END],
);
);

View File

@@ -20,7 +20,7 @@ class _CurrentTripErrorMessageState extends State<CurrentTripErrorMessage> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
const Text(
"😢",
style: TextStyle(
fontSize: 40,

View File

@@ -8,7 +8,7 @@ import 'package:anyway/structs/trip.dart';
class CurrentTripGreeter extends StatefulWidget {
final Trip trip;
CurrentTripGreeter({
const CurrentTripGreeter({
super.key,
required this.trip,
});
@@ -47,4 +47,4 @@ class _CurrentTripGreeterState extends State<CurrentTripGreeter> {
)
);
}
}

View File

@@ -10,7 +10,7 @@ import 'package:anyway/modules/landmark_card.dart';
// Returns a list of widgets that represent the landmarks matching the given selector
List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector}) {
List<Widget> children = [];
if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == typeStart ) {
@@ -30,10 +30,10 @@ List<Widget> landmarksList(Trip trip, {required bool Function(Landmark) selector
Landmark? nextLandmark = landmark.next;
while (nextLandmark != null && nextLandmark.visited) {
nextLandmark = nextLandmark.next;
}
}
if (nextLandmark != null) {
children.add(
StepBetweenLandmarks(current: landmark, next: nextLandmark!)
StepBetweenLandmarks(current: landmark, next: nextLandmark)
);
}
}

View File

@@ -49,7 +49,7 @@ class _CurrentTripLoadingIndicatorState extends State<CurrentTripLoadingIndicato
// automatically cycle through the greeter texts
class StatusText extends StatefulWidget {
const StatusText({Key? key}) : super(key: key);
const StatusText({super.key});
@override
_StatusTextState createState() => _StatusTextState();
@@ -110,10 +110,10 @@ class AnimatedDotsText extends StatefulWidget {
final TextStyle style;
const AnimatedDotsText({
Key? key,
super.key,
required this.baseText,
required this.style,
}) : super(key: key);
});
@override
_AnimatedDotsTextState createState() => _AnimatedDotsTextState();

View File

@@ -0,0 +1,53 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:anyway/structs/trip.dart';
List<Map<String, dynamic>> locationActions = [
{'name': 'Toilet', 'action': () {}},
{'name': 'Food', 'action': () {}},
{'name': 'Surrounding landmarks', 'action': () {}},
];
class CurrentTripLocations extends StatefulWidget {
final Trip? trip;
const CurrentTripLocations({super.key, this.trip});
@override
State<CurrentTripLocations> createState() => _CurrentTripLocationsState();
}
class _CurrentTripLocationsState extends State<CurrentTripLocations> {
@override
Widget build(BuildContext context) {
// A horizontally scrolling list of buttons with predefined actions
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: Row(
children: [
if (widget.trip != null)
for (Map action in locationActions)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: ElevatedButton(
onPressed: action['action'],
child: AutoSizeText(
action['name'],
maxLines: 1,
minFontSize: 8,
maxFontSize: 16,
overflow: TextOverflow.ellipsis,
),
),
),
],
),
);
}
}

View File

@@ -13,7 +13,7 @@ import 'package:anyway/modules/landmark_map_marker.dart';
class CurrentTripMap extends StatefulWidget {
final Trip? trip;
CurrentTripMap({this.trip});
const CurrentTripMap({super.key, this.trip});
@override
State<CurrentTripMap> createState() => _CurrentTripMapState();
@@ -22,7 +22,7 @@ class CurrentTripMap extends StatefulWidget {
class _CurrentTripMapState extends State<CurrentTripMap> {
late GoogleMapController mapController;
CameraPosition _cameraPosition = CameraPosition(
final CameraPosition _cameraPosition = const CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 11.0,
);
@@ -41,7 +41,7 @@ class _CurrentTripMapState extends State<CurrentTripMap> {
void dispose() {
widget.trip?.removeListener(setMapMarkers);
widget.trip?.removeListener(setMapRoute);
super.dispose();
}

View File

@@ -0,0 +1,31 @@
import 'package:anyway/structs/trip.dart';
import 'package:flutter/material.dart';
import 'package:anyway/modules/current_trip_map.dart';
import 'package:anyway/modules/current_trip_locations.dart';
class CurrentTripOverview extends StatefulWidget {
final Trip? trip;
const CurrentTripOverview({super.key, this.trip});
@override
State<CurrentTripOverview> createState() => _CurrentTripOverviewState();
}
class _CurrentTripOverviewState extends State<CurrentTripOverview> {
@override
Widget build(BuildContext context) {
// The background map has a horizontally scrolling list of rounded buttons overlaid
return Stack(
alignment: Alignment.topLeft,
children: [
CurrentTripMap(trip: widget.trip),
CurrentTripLocations(trip: widget.trip),
],
);
}
}

View File

@@ -45,7 +45,7 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
// this way the greeter will be centered when the panel is collapsed
// note that we need to account for the padding above
height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 10,
child: Center(child:
child: Center(child:
AutoSizeText(
maxLines: 1,
'Error',
@@ -81,7 +81,7 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
),
Padding(
padding: EdgeInsets.all(10),
padding: const EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: Colors.grey[100],
@@ -94,9 +94,6 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
ExpansionTile(
leading: const Icon(Icons.location_on),
title: const Text('Visited Landmarks (tap to expand)'),
children: [
...landmarksList(widget.trip, selector: (Landmark landmark) => landmark.visited),
],
visualDensity: VisualDensity.compact,
collapsedShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
@@ -104,12 +101,15 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
children: [
...landmarksList(widget.trip, selector: (Landmark landmark) => landmark.visited),
],
),
],
),
),
),
const Padding(padding: EdgeInsets.only(top: 10)),

View File

@@ -20,14 +20,14 @@ class _saveButtonState extends State<saveButton> {
onPressed: () async {
savedTrips.addTrip(widget.trip);
rootScaffoldMessengerKey.currentState!.showSnackBar(
SnackBar(
const SnackBar(
content: Text('Trip saved'),
duration: Duration(seconds: 2),
dismissDirection: DismissDirection.horizontal
)
);
},
child: SizedBox(
child: const SizedBox(
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,

View File

@@ -13,12 +13,12 @@ import 'package:anyway/structs/landmark.dart';
class LandmarkCard extends StatefulWidget {
final Landmark landmark;
final Trip parentTrip;
LandmarkCard(
const LandmarkCard(
this.landmark,
this.parentTrip,
);
@override
_LandmarkCardState createState() => _LandmarkCardState();
}
@@ -26,7 +26,7 @@ class LandmarkCard extends StatefulWidget {
class _LandmarkCardState extends State<LandmarkCard> {
@override
Widget build(BuildContext context) {
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints(
// express the max height in terms text lines
@@ -38,7 +38,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
),
elevation: 5,
clipBehavior: Clip.antiAliasWithSaveLayer,
// if the image is available, display it on the left side of the card, otherwise only display the text
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -66,11 +66,11 @@ class _LandmarkCardState extends State<LandmarkCard> {
color: PRIMARY_COLOR,
child: Center(
child: Padding(
padding: EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.timer_outlined, size: 16),
const Icon(Icons.timer_outlined, size: 16),
Text("${widget.landmark.duration?.inMinutes} minutes"),
],
)
@@ -97,7 +97,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
if (widget.landmark.nameEN != null)
Text(
widget.landmark.nameEN!,
@@ -114,7 +114,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(left: 5, right: 5, bottom: 10),
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 10),
// the scroll view should be flush once the buttons are scrolled to the left
// but initially there should be some padding
child: Wrap(
@@ -124,7 +124,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
doneToggleButton(),
if (widget.landmark.websiteURL != null)
websiteButton(),
optionsButton()
],
),
@@ -181,7 +181,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
title: const Text('Favorite'),
onTap: () async {
rootScaffoldMessengerKey.currentState!.showSnackBar(
SnackBar(content: Text("Not implemented yet"))
const SnackBar(content: Text("Not implemented yet"))
);
},
),
@@ -193,7 +193,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
Widget imagePlaceholder (Landmark landmark) => Expanded(
child:
child:
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(

View File

@@ -7,7 +7,7 @@ class ThemedMarker extends StatelessWidget {
final Landmark landmark;
final int position;
ThemedMarker({
const ThemedMarker({
super.key,
required this.landmark,
required this.position
@@ -24,12 +24,12 @@ class ThemedMarker extends StatelessWidget {
top: 0,
right: 0,
child: Container(
padding: EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
),
child: Text('$position', style: TextStyle(color: Colors.black, fontSize: 25)),
child: Text('$position', style: const TextStyle(color: Colors.black, fontSize: 25)),
),
);
}
@@ -40,7 +40,7 @@ class ThemedMarker extends StatelessWidget {
children: [
Container(
decoration: BoxDecoration(
gradient: landmark.visited ? LinearGradient(colors: [Colors.grey, Colors.white]) : APP_GRADIENT,
gradient: landmark.visited ? const LinearGradient(colors: [Colors.grey, Colors.white]) : APP_GRADIENT,
shape: BoxShape.circle,
border: Border.all(color: Colors.black, width: 5),
),
@@ -54,4 +54,4 @@ class ThemedMarker extends StatelessWidget {
),
);
}
}
}

View File

@@ -11,7 +11,7 @@ class NewTripButton extends StatefulWidget {
final Trip trip;
final UserPreferences preferences;
const NewTripButton({
const NewTripButton({super.key,
required this.trip,
required this.preferences,
});
@@ -35,8 +35,8 @@ class _NewTripButtonState extends State<NewTripButton> {
return FloatingActionButton.extended(
onPressed: onPressed,
icon: const Icon(Icons.directions),
label: AutoSizeText('Start planning!'),
);
label: const AutoSizeText('Start planning!'),
);
}
);
}

Some files were not shown because too many files have changed in this diff Show More