Compare commits
16 Commits
v0.1.3
...
feature/fr
| Author | SHA1 | Date | |
|---|---|---|---|
| 014b48591e | |||
| 81ed2fd8c3 | |||
| 239b63ca81 | |||
| 0070e57aec | |||
| 71c7325370 | |||
| aeed9c7dc9 | |||
| 89c5fc9370 | |||
| d8c6bfcda0 | |||
| ec3ed054fd | |||
| 219cfcf1a6 | |||
| 708c07cf49 | |||
| e14900e9f0 | |||
| d1cbf972fe | |||
| fe2a0cf1d5 | |||
| c11faee824 | |||
| 97dacb1189 |
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
[](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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -1 +1,2 @@
|
||||
cache/
|
||||
.direnv/
|
||||
|
||||
3
.gitmodules
vendored
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"nixEnvSelector.nixFile": "${workspaceFolder}/default.nix"
|
||||
}
|
||||
5
backend/.gitignore
vendored
@@ -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
@@ -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."
|
||||
'';
|
||||
}
|
||||
@@ -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 }}
|
||||
64
frontend/.github/workflows/build_app_ios.yaml
vendored
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
Before Width: | Height: | Size: 3.0 MiB After Width: | Height: | Size: 3.0 MiB |
|
Before Width: | Height: | Size: 4.1 MiB After Width: | Height: | Size: 4.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
2025 anydev
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
@@ -1 +1 @@
|
||||
AnyWay
|
||||
Any.Way
|
||||
@@ -1 +1 @@
|
||||
|
||||
TRAVEL
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
anydev.anyway@gmail.com
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
Remy
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
Moll
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
+4915128785827
|
||||
|
||||
BIN
frontend/ios/fastlane/screenshots/en-US/iOS Phones 6.9-01.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 626 KiB After Width: | Height: | Size: 626 KiB |
|
Before Width: | Height: | Size: 758 KiB After Width: | Height: | Size: 758 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 574 KiB After Width: | Height: | Size: 574 KiB |
|
Before Width: | Height: | Size: 800 KiB After Width: | Height: | Size: 800 KiB |
83
frontend/lib/core/constants.dart
Normal 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>();
|
||||
16
frontend/lib/core/dio_client.dart
Normal 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,
|
||||
));
|
||||
}
|
||||
0
frontend/lib/data/README.md
Normal file
83
frontend/lib/data/datasources/trip_local_datasource.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
142
frontend/lib/data/datasources/trip_remote_datasource.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
32
frontend/lib/data/models/landmark_model.dart
Normal 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: []),
|
||||
);
|
||||
}
|
||||
298
frontend/lib/data/models/landmark_model.freezed.dart
Normal 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
|
||||
29
frontend/lib/data/models/landmark_model.g.dart
Normal 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,
|
||||
};
|
||||
114
frontend/lib/data/repositories/backend_trip_repository.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
35
frontend/lib/domain/README.md
Normal 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
|
||||
```
|
||||
|
||||
56
frontend/lib/domain/entities/landmark.dart
Normal 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;
|
||||
}
|
||||
350
frontend/lib/domain/entities/landmark.freezed.dart
Normal 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
|
||||
47
frontend/lib/domain/entities/landmark.g.dart
Normal 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,
|
||||
};
|
||||
15
frontend/lib/domain/entities/landmark_description.dart
Normal 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);
|
||||
}
|
||||
|
||||
286
frontend/lib/domain/entities/landmark_description.freezed.dart
Normal 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
|
||||
20
frontend/lib/domain/entities/landmark_description.g.dart
Normal 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,
|
||||
};
|
||||
31
frontend/lib/domain/entities/landmark_type.dart
Normal 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]!;
|
||||
}
|
||||
277
frontend/lib/domain/entities/landmark_type.freezed.dart
Normal 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
|
||||
21
frontend/lib/domain/entities/landmark_type.g.dart
Normal 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',
|
||||
};
|
||||
25
frontend/lib/domain/entities/preferences.dart
Normal 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);
|
||||
}
|
||||
322
frontend/lib/domain/entities/preferences.freezed.dart
Normal 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
|
||||
28
frontend/lib/domain/entities/preferences.g.dart
Normal 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,
|
||||
};
|
||||
24
frontend/lib/domain/entities/trip.dart
Normal 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);
|
||||
|
||||
}
|
||||
278
frontend/lib/domain/entities/trip.freezed.dart
Normal 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
|
||||
21
frontend/lib/domain/entities/trip.g.dart
Normal 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,
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import 'package:anyway/domain/entities/preferences.dart';
|
||||
|
||||
abstract class PreferencesRepository {
|
||||
Future<Preferences> getPreferences();
|
||||
Future<void> savePreferences(Preferences preferences);
|
||||
}
|
||||
19
frontend/lib/domain/repositories/trip_repository.dart
Normal 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);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
);
|
||||
);
|
||||
@@ -20,7 +20,7 @@ class _CurrentTripErrorMessageState extends State<CurrentTripErrorMessage> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
const Text(
|
||||
"😢",
|
||||
style: TextStyle(
|
||||
fontSize: 40,
|
||||
@@ -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> {
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
53
frontend/lib/old/modules/current_trip_locations.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
31
frontend/lib/old/modules/current_trip_overview.dart
Normal 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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
|
||||
@@ -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,
|
||||
@@ -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(
|
||||
@@ -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 {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!'),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||