Compass app (#2446)

main
Eric Windmill 1 day ago committed by GitHub
parent fcf2552cda
commit 46b5a26b26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,59 @@
# compass_app
The Compass sample application is an app that helps users build and book
itineraries for trips. Its a robust sample application with many features, routes, and screens. The app communicates
with an HTTP server, has development and production environments, brand-specific styling, and high test coverage. In
these ways and more, it simulates a real-world, feature rich Flutter application.
The code in this application is used for explaining architecture in a Flutter application.
![compass app splash screen](./docs/Global%20—%20Splash.png)
![compass app activities screen](./docs/Legacy%20—%20Activities.png)
![compass app home screen](./docs/Legacy%20—%20Home.png)
![compass app booking screen](./docs/Legacy%20—%20Book.png)
The Compass app was originally [designed and built by the Firebase GenKit team](https://developers.googleblog.com/en/how-firebase-genkit-helped-add-ai-to-our-compass-app/).
## Running the app
This app contains multiple environments.
* Development environment - This environment uses data from a JSON file, which is stored in the `assets` directory, and simulates developing locally.
```bash
$ cd app
$ flutter run --target lib/main_development.dart
```
* Staging environment - This environment uses an HTTP server to get data, simulating a real app experience. This is a "dummy" server, that has endpoints that simply return fake data. The server can be found in the `compass_app/server` directory. You need to run the server locally before running the Flutter application.
```bash
$ cd server
$ dart run
# => Server listening on port 8080
$ cd ../compass_app/app
$ flutter run --target lib/main_staging.dart
```
## Integration Tests
Integration tests must be run from the `app` directory.
**Integration tests with local data**
```bash
cd app
$ flutter test integration_test/app_local_data_test.dart
```
**Integration tests with background server and remote data**
```bash
cd app
$ flutter test integration_test/app_server_data_test.dart
```
Running the tests together with `flutter test integration_test` will fail.
See: https://github.com/flutter/flutter/issues/101031

@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# Coverage test report
coverage/

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "ee624bc4fd41413cbb89099b0701a42287643d9a"
channel: "beta"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
- platform: android
create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
- platform: ios
create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
- platform: linux
create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
- platform: macos
create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
- platform: web
create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
- platform: windows
create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

@ -0,0 +1,5 @@
include: package:flutter_lints/flutter.yaml
linter:
rules:
- prefer_relative_imports

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

@ -0,0 +1,44 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
android {
namespace = "com.example.compass_app"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.compass_app"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
}
}
}
flutter {
source = "../.."
}

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="compass_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

@ -0,0 +1,5 @@
package com.example.compass_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -0,0 +1,18 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip

@ -0,0 +1,25 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
}
include ":app"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,10 @@
<svg width="179" height="41" viewBox="0 0 179 41" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.4858 33.9056C10.6086 28.3987 4.24247 19.7722 4.24247 15.4858C4.24247 9.2856 9.2856 4.24247 15.4858 4.24247C20.9097 4.24247 25.45 8.10415 26.4996 13.2205H30.8006C29.7022 5.75102 23.253 0 15.4858 0C6.94711 0 0 6.94711 0 15.4858C0 23.4825 12.5126 36.9911 13.943 38.5045L15.4858 40.1399L17.0285 38.4996C18.3271 37.118 28.8185 25.7917 30.6835 17.7413H26.3043C24.8006 22.3499 19.6111 29.2384 15.4858 33.9007V33.9056ZM15.4858 11.2091C17.0138 11.2091 18.3515 12.0146 19.1082 13.2205H23.6973C22.7014 9.62246 19.4011 6.96664 15.4906 6.96664C10.7941 6.96664 6.9764 10.7893 6.9764 15.4809C6.9764 20.1725 10.799 23.9951 15.4906 23.9951C19.4011 23.9951 22.7063 21.3442 23.6973 17.7413H19.1082C18.3515 18.9471 17.0138 19.7526 15.4858 19.7526C13.1277 19.7526 11.2091 17.834 11.2091 15.476C11.2091 13.118 13.1277 11.1993 15.4858 11.1993V11.2091Z" fill="white"/>
<path d="M46.2525 28.389C44.5536 27.4028 43.2452 26.0358 42.3176 24.2978C41.3901 22.5598 40.9263 20.5826 40.9263 18.3711C40.9263 16.1595 41.3852 14.1823 42.3079 12.4443C43.2306 10.7063 44.5292 9.34422 46.2135 8.35317C47.8978 7.36212 49.8555 6.87392 52.0866 6.87392C53.8978 6.87392 55.5284 7.21566 56.9783 7.89914C58.4283 8.58262 59.6146 9.55414 60.5373 10.8137C61.46 12.0733 62.0459 13.533 62.3046 15.188H57.8766C57.6814 14.2604 57.3201 13.4646 56.7977 12.7958C56.2753 12.127 55.6163 11.6095 54.8205 11.2433C54.0296 10.8772 53.1362 10.6965 52.15 10.6965C50.8221 10.6965 49.6553 11.0334 48.6447 11.7071C47.6342 12.3808 46.8579 13.3035 46.3063 14.4703C45.7546 15.6371 45.4812 16.9406 45.4812 18.3808C45.4812 19.9138 45.7546 21.2563 46.3063 22.4085C46.8579 23.5607 47.6342 24.4492 48.635 25.079C49.6358 25.7087 50.8026 26.0212 52.1305 26.0212C53.1508 26.0212 54.0687 25.8259 54.884 25.4353C55.7041 25.0448 56.373 24.498 56.8905 23.7999C57.408 23.1017 57.7448 22.306 57.901 21.4223H62.3437C62.1582 23.0578 61.6211 24.5126 60.7326 25.7917C59.8441 27.0659 58.6626 28.0668 57.1883 28.7844C55.7188 29.5021 54.0394 29.8633 52.1549 29.8633C49.9141 29.8633 47.9417 29.3703 46.2428 28.3841L46.2525 28.389Z" fill="white"/>
<path d="M67.4649 28.76C66.1272 28.0179 65.0727 26.9732 64.3014 25.6209C63.53 24.2685 63.1443 22.7258 63.1443 20.9976C63.1443 19.2694 63.5202 17.7315 64.277 16.3841C65.0337 15.0367 66.0833 13.9821 67.4307 13.2205C68.7782 12.4589 70.2916 12.0781 71.9661 12.0781C73.6407 12.0781 75.1639 12.4541 76.5015 13.2108C77.8392 13.9675 78.8839 15.0171 79.6358 16.3646C80.3876 17.712 80.7635 19.2547 80.7635 20.9927C80.7635 22.7307 80.3827 24.2588 79.6211 25.6062C78.8595 26.9537 77.8099 28.0033 76.4674 28.7454C75.1248 29.4874 73.6211 29.8633 71.9515 29.8633C70.2818 29.8633 68.7977 29.4923 67.46 28.7502L67.4649 28.76ZM74.2607 25.6502C74.9442 25.2254 75.4861 24.6103 75.8815 23.8048C76.277 23.0041 76.4771 22.0668 76.4771 20.9976C76.4771 19.9284 76.2818 18.9911 75.8913 18.1904C75.5007 17.3898 74.9637 16.7698 74.2802 16.3304C73.5967 15.891 72.8205 15.6762 71.9564 15.6762C71.0923 15.6762 70.316 15.8959 69.6325 16.3304C68.9491 16.7698 68.412 17.3898 68.0215 18.2002C67.6309 19.0057 67.4356 19.948 67.4356 21.0171C67.4356 22.0863 67.6358 23.0041 68.0312 23.8048C68.4267 24.6005 68.9637 25.2157 69.6423 25.6502C70.3209 26.0847 71.0923 26.2995 71.9564 26.2995C72.8205 26.2995 73.5772 26.0847 74.2656 25.6599L74.2607 25.6502Z" fill="white"/>
<path d="M86.7391 12.4492V14.7633H86.8026C87.0711 14.2067 87.447 13.7283 87.9352 13.3182C88.4234 12.913 88.9946 12.6005 89.6488 12.3857C90.303 12.1709 91.0255 12.0635 91.8164 12.0635C93.0223 12.0635 94.0329 12.3076 94.8481 12.7958C95.6634 13.284 96.2737 13.9919 96.6789 14.9195H96.7228C97.2159 13.9919 97.9092 13.284 98.8075 12.7958C99.7058 12.3076 100.765 12.0635 102 12.0635C103.235 12.0635 104.241 12.3027 105.12 12.7812C105.999 13.2596 106.663 13.9724 107.126 14.9195C107.59 15.8666 107.82 17.0187 107.82 18.3759V29.5021H103.577V19.0838C103.577 18.0537 103.309 17.2482 102.767 16.6624C102.225 16.0765 101.473 15.7836 100.506 15.7836C99.8473 15.7836 99.2761 15.9349 98.7831 16.2327C98.2949 16.5305 97.9189 16.9553 97.6553 17.507C97.3917 18.0586 97.2599 18.6982 97.2599 19.4305V29.507H93.0613V19.0594C93.0613 18.0391 92.7977 17.2433 92.2656 16.6575C91.7334 16.0716 90.9962 15.7836 90.0491 15.7836C89.3901 15.7836 88.8091 15.9349 88.3063 16.2376C87.8034 16.5403 87.4128 16.9699 87.1394 17.5167C86.8661 18.0684 86.7294 18.703 86.7294 19.4207V29.4972H82.4869V12.4492H86.7391Z" fill="white"/>
<path d="M118.013 29.5509C117.305 29.3361 116.656 29.009 116.075 28.5794C115.494 28.1449 114.996 27.6127 114.586 26.9732H114.508V35.507H110.266V12.4492H114.508V14.9781H114.586C114.967 14.3727 115.44 13.8503 116.007 13.4207C116.573 12.9911 117.208 12.6591 117.911 12.4248C118.614 12.1904 119.38 12.0781 120.2 12.0781C121.68 12.0781 122.988 12.4541 124.111 13.2059C125.239 13.9577 126.108 15.0122 126.718 16.3694C127.328 17.7266 127.636 19.2742 127.636 21.0171C127.636 22.76 127.318 24.3857 126.689 25.7234C126.054 27.0611 125.175 28.0863 124.047 28.8039C122.92 29.5167 121.646 29.878 120.23 29.878C119.458 29.878 118.716 29.7706 118.008 29.5558L118.013 29.5509ZM121.192 25.5574C121.87 25.1327 122.402 24.5273 122.783 23.7462C123.164 22.965 123.354 22.0423 123.354 20.9829C123.354 19.9235 123.164 19.0155 122.783 18.2295C122.402 17.4435 121.875 16.8381 121.201 16.4183C120.528 15.9984 119.756 15.7836 118.892 15.7836C118.028 15.7836 117.227 16.0033 116.554 16.4378C115.88 16.8723 115.362 17.4923 114.996 18.2832C114.63 19.0741 114.449 19.9821 114.449 20.9976C114.449 22.0131 114.635 22.965 115.006 23.7511C115.377 24.5371 115.895 25.1424 116.563 25.5623C117.232 25.9821 118.004 26.1969 118.877 26.1969C119.751 26.1969 120.513 25.9821 121.192 25.5574Z" fill="white"/>
<path d="M132.381 28.7991C131.258 28.0863 130.379 27.0562 129.745 25.7185C129.105 24.3808 128.788 22.8137 128.788 21.0122C128.788 19.2108 129.095 17.7266 129.706 16.3646C130.316 15.0074 131.185 13.9528 132.313 13.201C133.44 12.4492 134.739 12.0733 136.209 12.0733C137.034 12.0733 137.795 12.1904 138.498 12.4199C139.201 12.6493 139.836 12.9813 140.397 13.4061C140.959 13.8308 141.427 14.3434 141.808 14.9439H141.916V12.4443H146.158V29.4972H141.916V27.0122H141.808C141.408 27.6274 140.915 28.1497 140.334 28.5794C139.753 29.009 139.109 29.3263 138.406 29.546C137.702 29.7608 136.96 29.8682 136.189 29.8682C134.768 29.8682 133.499 29.5118 132.376 28.7942L132.381 28.7991ZM139.87 25.5672C140.539 25.1473 141.056 24.5419 141.427 23.7559C141.799 22.9699 141.984 22.0521 141.984 21.0025C141.984 19.9528 141.803 19.079 141.437 18.2881C141.071 17.4972 140.554 16.8821 139.885 16.4427C139.216 16.0033 138.435 15.7885 137.537 15.7885C136.638 15.7885 135.901 16.0033 135.227 16.428C134.554 16.8528 134.026 17.4581 133.655 18.2393C133.279 19.0204 133.094 19.9382 133.094 20.9878C133.094 22.0375 133.284 22.9699 133.665 23.7559C134.046 24.5419 134.573 25.1473 135.247 25.5672C135.921 25.987 136.692 26.2018 137.556 26.2018C138.42 26.2018 139.201 25.9919 139.87 25.5672Z" fill="white"/>
<path d="M149.839 28.345C148.545 27.3149 147.852 25.8601 147.75 23.9756H151.855C151.904 24.8397 152.222 25.5232 152.803 26.0212C153.384 26.5192 154.209 26.7681 155.278 26.7681C156.235 26.7681 156.967 26.5875 157.47 26.2262C157.973 25.865 158.227 25.4158 158.227 24.869C158.227 24.415 158.109 24.0537 157.88 23.7803C157.65 23.5069 157.27 23.2775 156.747 23.092C156.225 22.9065 155.473 22.7161 154.492 22.5208C152.988 22.2327 151.807 21.8959 150.943 21.5102C150.078 21.1245 149.439 20.6168 149.019 19.9821C148.604 19.3475 148.394 18.5371 148.394 17.5509C148.394 16.472 148.658 15.52 149.18 14.6949C149.702 13.8698 150.493 13.2303 151.543 12.7665C152.593 12.3027 153.847 12.0733 155.307 12.0733C156.767 12.0733 157.968 12.2832 159.003 12.7079C160.038 13.1278 160.838 13.7527 161.405 14.5777C161.971 15.4028 162.288 16.389 162.362 17.5411H158.334C158.261 16.8088 157.978 16.2376 157.48 15.8227C156.982 15.4077 156.259 15.1978 155.312 15.1978C154.746 15.1978 154.253 15.271 153.838 15.4126C153.423 15.559 153.1 15.769 152.881 16.0472C152.661 16.3255 152.549 16.6575 152.549 17.048C152.549 17.4581 152.656 17.7852 152.866 18.0293C153.076 18.2734 153.437 18.4736 153.945 18.6396C154.453 18.8056 155.185 18.9813 156.142 19.162C157.704 19.4598 158.934 19.8015 159.823 20.1872C160.711 20.5729 161.38 21.0855 161.82 21.725C162.264 22.3646 162.484 23.1896 162.484 24.21C162.484 25.3719 162.21 26.3727 161.663 27.2124C161.117 28.0521 160.301 28.7063 159.218 29.1799C158.134 29.6534 156.806 29.8877 155.244 29.8877C152.93 29.8877 151.123 29.3751 149.834 28.345H149.839Z" fill="white"/>
<path d="M165.476 28.345C164.183 27.3149 163.489 25.8601 163.387 23.9756H167.493C167.541 24.8397 167.859 25.5232 168.44 26.0212C169.021 26.5192 169.846 26.7681 170.915 26.7681C171.872 26.7681 172.604 26.5875 173.107 26.2262C173.61 25.865 173.864 25.4158 173.864 24.869C173.864 24.415 173.746 24.0537 173.517 23.7803C173.288 23.5069 172.907 23.2775 172.384 23.092C171.862 22.9065 171.11 22.7161 170.129 22.5208C168.625 22.2327 167.444 21.8959 166.58 21.5102C165.716 21.1245 165.076 20.6168 164.656 19.9821C164.241 19.3475 164.031 18.5371 164.031 17.5509C164.031 16.472 164.295 15.52 164.817 14.6949C165.34 13.8698 166.13 13.2303 167.18 12.7665C168.23 12.3027 169.484 12.0733 170.944 12.0733C172.404 12.0733 173.605 12.2832 174.64 12.7079C175.675 13.1278 176.475 13.7527 177.042 14.5777C177.608 15.4028 177.925 16.389 177.999 17.5411H173.971C173.898 16.8088 173.615 16.2376 173.117 15.8227C172.619 15.4077 171.896 15.1978 170.949 15.1978C170.383 15.1978 169.89 15.271 169.475 15.4126C169.06 15.559 168.737 15.769 168.518 16.0472C168.298 16.3255 168.186 16.6575 168.186 17.048C168.186 17.4581 168.293 17.7852 168.503 18.0293C168.713 18.2734 169.074 18.4736 169.582 18.6396C170.09 18.8056 170.822 18.9813 171.779 19.162C173.341 19.4598 174.571 19.8015 175.46 20.1872C176.349 20.5729 177.017 21.0855 177.457 21.725C177.901 22.3646 178.121 23.1896 178.121 24.21C178.121 25.3719 177.847 26.3727 177.301 27.2124C176.754 28.0521 175.938 28.7063 174.855 29.1799C173.771 29.6534 172.443 29.8877 170.881 29.8877C168.567 29.8877 166.76 29.3751 165.471 28.345H165.476Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

@ -0,0 +1,145 @@
import 'package:compass_app/config/dependencies.dart';
import 'package:compass_app/main.dart';
import 'package:compass_app/ui/activities/widgets/activities_screen.dart';
import 'package:compass_app/ui/booking/widgets/booking_screen.dart';
import 'package:compass_app/ui/core/ui/custom_checkbox.dart';
import 'package:compass_app/ui/core/ui/home_button.dart';
import 'package:compass_app/ui/home/widgets/home_screen.dart';
import 'package:compass_app/ui/results/widgets/result_card.dart';
import 'package:compass_app/ui/results/widgets/results_screen.dart';
import 'package:compass_app/ui/search_form/widgets/search_form_guests.dart';
import 'package:compass_app/ui/search_form/widgets/search_form_screen.dart';
import 'package:compass_app/ui/search_form/widgets/search_form_submit.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:provider/provider.dart';
/// This Integration Test launches the Compass-App with the local configuration.
/// The app uses data from the assets folder to create a booking.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test with local data', () {
testWidgets('should load app', (tester) async {
// Load app widget.
await tester.pumpWidget(
MultiProvider(
providers: providersLocal,
child: const MainApp(),
),
);
});
testWidgets('Open a booking', (tester) async {
// Load app widget with local configuration
await tester.pumpWidget(
MultiProvider(
providers: providersLocal,
child: const MainApp(),
),
);
await tester.pumpAndSettle();
// Home screen
expect(find.byType(HomeScreen), findsOneWidget);
await tester.pumpAndSettle();
// Should show user name
expect(find.text('Sofie\'s Trips'), findsOneWidget);
// Tap on booking (Alaska is created by default)
await tester.tap(find.text('Alaska, North America'));
await tester.pumpAndSettle();
// Should be at booking screen
expect(find.byType(BookingScreen), findsOneWidget);
expect(find.text('Alaska'), findsOneWidget);
});
testWidgets('Create booking', (tester) async {
// Load app widget with local configuration
await tester.pumpWidget(
MultiProvider(
providers: providersLocal,
child: const MainApp(),
),
);
await tester.pumpAndSettle();
// Home screen
expect(find.byType(HomeScreen), findsOneWidget);
await tester.pumpAndSettle();
// Select create new booking
await tester.tap(find.byKey(const ValueKey(bookingButtonKey)));
await tester.pumpAndSettle();
// Search destinations screen
expect(find.byType(SearchFormScreen), findsOneWidget);
// Select Europe because it is always the first result
await tester.tap(find.text('Europe'), warnIfMissed: false);
// Select dates
await tester.tap(find.text('Add Dates'));
await tester.pumpAndSettle();
final tomorrow = DateTime.now().add(const Duration(days: 1)).day;
final nextDay = DateTime.now().add(const Duration(days: 2)).day;
// Select first and last widget that matches today number
//and tomorrow number, sort of ensures a valid range
await tester.tap(find.text(tomorrow.toString()).first);
await tester.pumpAndSettle();
await tester.tap(find.text(nextDay.toString()).first);
await tester.pumpAndSettle();
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
// Select guests
await tester.tap(find.byKey(const ValueKey(addGuestsKey)),
warnIfMissed: false);
// Refresh screen state
await tester.pumpAndSettle();
// Perform search and navigate to next screen
await tester.tap(find.byKey(const ValueKey(searchFormSubmitButtonKey)));
await tester.pumpAndSettle();
// Results Screen
expect(find.byType(ResultsScreen), findsOneWidget);
// Amalfi Coast should be the first result for Europe
// Tap and navigate to activities screen
await tester.tap(find.byType(ResultCard).first);
await tester.pumpAndSettle();
// Activities Screen
expect(find.byType(ActivitiesScreen), findsOneWidget);
// Select one activity
await tester.tap(find.byType(CustomCheckbox).first);
await tester.pumpAndSettle();
expect(find.text('1 selected'), findsOneWidget);
// Submit selection
await tester.tap(find.byKey(const ValueKey(confirmButtonKey)));
await tester.pumpAndSettle();
// Should be at booking screen
expect(find.byType(BookingScreen), findsOneWidget);
expect(find.text('Amalfi Coast'), findsOneWidget);
// Navigate back home
await tester.tap(find.byType(HomeButton));
await tester.pumpAndSettle();
// Home screen
expect(find.byType(HomeScreen), findsOneWidget);
// New Booking should appear
expect(find.text('Amalfi Coast, Europe'), findsOneWidget);
});
});
}

@ -0,0 +1,208 @@
import 'dart:io';
import 'package:compass_app/config/dependencies.dart';
import 'package:compass_app/main.dart';
import 'package:compass_app/ui/activities/widgets/activities_screen.dart';
import 'package:compass_app/ui/auth/login/widgets/login_screen.dart';
import 'package:compass_app/ui/auth/logout/widgets/logout_button.dart';
import 'package:compass_app/ui/booking/widgets/booking_screen.dart';
import 'package:compass_app/ui/core/ui/custom_checkbox.dart';
import 'package:compass_app/ui/core/ui/home_button.dart';
import 'package:compass_app/ui/home/widgets/home_screen.dart';
import 'package:compass_app/ui/results/widgets/result_card.dart';
import 'package:compass_app/ui/results/widgets/results_screen.dart';
import 'package:compass_app/ui/search_form/widgets/search_form_screen.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// This Integration Test starts the Dart server
/// before launching the Compass-App with the remote configuration.
/// The app connects to its endpoints to perform login and create a booking.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test with remote data', () {
const port = '8080';
late Process p;
setUpAll(() async {
// Clear any stored shared preferences
final sharedPreferences = await SharedPreferences.getInstance();
await sharedPreferences.clear();
// Start the dart server
p = await Process.start(
'dart',
['run', 'bin/compass_server.dart'],
environment: {'PORT': port},
// Relative to the app/ folder
workingDirectory: '../server',
);
// Wait for server to start and print to stdout.
await p.stdout.first;
});
tearDownAll(() => p.kill());
testWidgets('should load app', (tester) async {
// Load app widget.
await tester.pumpWidget(
MultiProvider(
providers: providersRemote,
child: const MainApp(),
),
);
await tester.pumpAndSettle();
// Login screen because logget out
expect(find.byType(LoginScreen), findsOneWidget);
});
testWidgets('Open a booking', (tester) async {
// Load app widget with local configuration
await tester.pumpWidget(
MultiProvider(
providers: providersRemote,
child: const MainApp(),
),
);
await tester.pumpAndSettle();
// Login screen because logget out
expect(find.byType(LoginScreen), findsOneWidget);
// Perform login (credentials are prefilled)
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();
// Home screen
expect(find.byType(HomeScreen), findsOneWidget);
await tester.pumpAndSettle();
// Should show user name
expect(find.text('Sofie\'s Trips'), findsOneWidget);
// Tap on booking (Alaska is created by default)
await tester.tap(find.text('Alaska, North America'));
await tester.pumpAndSettle();
// Should be at booking screen
expect(find.byType(BookingScreen), findsOneWidget);
expect(find.text('Alaska'), findsOneWidget);
// Navigate back to home
await tester.tap(find.byType(HomeButton).first);
await tester.pumpAndSettle();
// Home screen
expect(find.byType(HomeScreen), findsOneWidget);
// Perform logout
await tester.tap(find.byType(LogoutButton).first);
await tester.pumpAndSettle();
expect(find.byType(LoginScreen), findsOneWidget);
});
testWidgets('Create booking', (tester) async {
// Load app widget with local configuration
await tester.pumpWidget(
MultiProvider(
providers: providersRemote,
child: const MainApp(),
),
);
await tester.pumpAndSettle();
// Login screen because logget out
expect(find.byType(LoginScreen), findsOneWidget);
// Perform login (credentials are prefilled)
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();
// Home screen
expect(find.byType(HomeScreen), findsOneWidget);
await tester.pumpAndSettle();
// Select create new booking
await tester.tap(find.byKey(const ValueKey('booking-button')));
await tester.pumpAndSettle();
// Search destinations screen
expect(find.byType(SearchFormScreen), findsOneWidget);
// Select Europe because it is always the first result
await tester.tap(find.text('Europe'), warnIfMissed: false);
// Select dates
await tester.tap(find.text('Add Dates'));
await tester.pumpAndSettle();
final tomorrow = DateTime.now().add(const Duration(days: 1)).day;
final nextDay = DateTime.now().add(const Duration(days: 2)).day;
// Select first and last widget that matches today number
//and tomorrow number, sort of ensures a valid range
await tester.tap(find.text(tomorrow.toString()).first);
await tester.pumpAndSettle();
await tester.tap(find.text(nextDay.toString()).first);
await tester.pumpAndSettle();
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
// Select guests
await tester.tap(find.byKey(const ValueKey('add_guests')),
warnIfMissed: false);
// Refresh screen state
await tester.pumpAndSettle();
// Perform search and navigate to next screen
await tester.tap(find.byKey(const ValueKey('submit_button')));
await tester.pumpAndSettle();
// Results Screen
expect(find.byType(ResultsScreen), findsOneWidget);
// Amalfi Coast should be the first result for Europe
// Tap and navigate to activities screen
await tester.tap(find.byType(ResultCard).first);
await tester.pumpAndSettle();
// Activities Screen
expect(find.byType(ActivitiesScreen), findsOneWidget);
// Select one activity
await tester.tap(find.byType(CustomCheckbox).first);
await tester.pumpAndSettle();
expect(find.text('1 selected'), findsOneWidget);
// Submit selection
await tester.tap(find.byKey(const ValueKey('confirm-button')));
await tester.pumpAndSettle();
// Should be at booking screen
expect(find.byType(BookingScreen), findsOneWidget);
expect(find.text('Amalfi Coast'), findsOneWidget);
// Navigate back to home
await tester.tap(find.byType(HomeButton).first);
await tester.pumpAndSettle();
// Home screen
expect(find.byType(HomeScreen), findsOneWidget);
// New Booking should appear
expect(find.text('Amalfi Coast, Europe'), findsOneWidget);
// Perform logout
await tester.tap(find.byType(LogoutButton).first);
await tester.pumpAndSettle();
expect(find.byType(LoginScreen), findsOneWidget);
});
});
}

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

@ -0,0 +1,44 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

@ -0,0 +1,13 @@
import Flutter
import UIKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Compass App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>compass_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

@ -0,0 +1,8 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
class Assets {
static const activities = 'assets/activities.json';
static const destinations = 'assets/destinations.json';
}

@ -0,0 +1,144 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:provider/single_child_widget.dart';
import 'package:provider/provider.dart';
import '../data/repositories/auth/auth_repository.dart';
import '../data/repositories/auth/auth_repository_dev.dart';
import '../data/repositories/auth/auth_repository_remote.dart';
import '../data/repositories/booking/booking_repository.dart';
import '../data/repositories/booking/booking_repository_local.dart';
import '../data/repositories/booking/booking_repository_remote.dart';
import '../data/repositories/user/user_repository.dart';
import '../data/repositories/user/user_repository_local.dart';
import '../data/repositories/user/user_repository_remote.dart';
import '../data/services/api/auth_api_client.dart';
import '../data/services/local/local_data_service.dart';
import '../data/services/shared_preferences_service.dart';
import '../data/repositories/activity/activity_repository.dart';
import '../data/repositories/activity/activity_repository_local.dart';
import '../data/repositories/activity/activity_repository_remote.dart';
import '../data/repositories/continent/continent_repository.dart';
import '../data/repositories/continent/continent_repository_local.dart';
import '../data/repositories/continent/continent_repository_remote.dart';
import '../data/repositories/destination/destination_repository.dart';
import '../data/repositories/destination/destination_repository_local.dart';
import '../data/repositories/destination/destination_repository_remote.dart';
import '../data/repositories/itinerary_config/itinerary_config_repository.dart';
import '../data/repositories/itinerary_config/itinerary_config_repository_memory.dart';
import '../data/services/api/api_client.dart';
import '../domain/use_cases/booking/booking_create_use_case.dart';
import '../domain/use_cases/booking/booking_share_use_case.dart';
/// Shared providers for all configurations.
List<SingleChildWidget> _sharedProviders = [
Provider(
lazy: true,
create: (context) => BookingCreateUseCase(
destinationRepository: context.read(),
activityRepository: context.read(),
bookingRepository: context.read(),
),
),
Provider(
lazy: true,
create: (context) => BookingShareUseCase.withSharePlus(),
),
];
/// Configure dependencies for remote data.
/// This dependency list uses repositories that connect to a remote server.
List<SingleChildWidget> get providersRemote {
return [
Provider(
create: (context) => AuthApiClient(),
),
Provider(
create: (context) => ApiClient(),
),
Provider(
create: (context) => SharedPreferencesService(),
),
ChangeNotifierProvider(
create: (context) => AuthRepositoryRemote(
authApiClient: context.read(),
apiClient: context.read(),
sharedPreferencesService: context.read(),
) as AuthRepository,
),
Provider(
create: (context) => DestinationRepositoryRemote(
apiClient: context.read(),
) as DestinationRepository,
),
Provider(
create: (context) => ContinentRepositoryRemote(
apiClient: context.read(),
) as ContinentRepository,
),
Provider(
create: (context) => ActivityRepositoryRemote(
apiClient: context.read(),
) as ActivityRepository,
),
Provider.value(
value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository,
),
Provider(
create: (context) => BookingRepositoryRemote(
apiClient: context.read(),
) as BookingRepository,
),
Provider(
create: (context) => UserRepositoryRemote(
apiClient: context.read(),
) as UserRepository,
),
..._sharedProviders,
];
}
/// Configure dependencies for local data.
/// This dependency list uses repositories that provide local data.
/// The user is always logged in.
List<SingleChildWidget> get providersLocal {
return [
ChangeNotifierProvider.value(
value: AuthRepositoryDev() as AuthRepository,
),
Provider.value(
value: LocalDataService(),
),
Provider(
create: (context) => DestinationRepositoryLocal(
localDataService: context.read(),
) as DestinationRepository,
),
Provider(
create: (context) => ContinentRepositoryLocal(
localDataService: context.read(),
) as ContinentRepository,
),
Provider(
create: (context) => ActivityRepositoryLocal(
localDataService: context.read(),
) as ActivityRepository,
),
Provider(
create: (context) => BookingRepositoryLocal(
localDataService: context.read(),
) as BookingRepository,
),
Provider.value(
value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository,
),
Provider(
create: (context) => UserRepositoryLocal(
localDataService: context.read(),
) as UserRepository,
),
..._sharedProviders,
];
}

@ -0,0 +1,12 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/activity/activity.dart';
import '../../../utils/result.dart';
/// Data source for activities.
abstract class ActivityRepository {
/// Get activities by [Destination] ref.
Future<Result<List<Activity>>> getByDestination(String ref);
}

@ -0,0 +1,31 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/activity/activity.dart';
import '../../../utils/result.dart';
import '../../services/local/local_data_service.dart';
import 'activity_repository.dart';
/// Local implementation of ActivityRepository
/// Uses data from assets folder
class ActivityRepositoryLocal implements ActivityRepository {
ActivityRepositoryLocal({
required LocalDataService localDataService,
}) : _localDataService = localDataService;
final LocalDataService _localDataService;
@override
Future<Result<List<Activity>>> getByDestination(String ref) async {
try {
final activities = (await _localDataService.getActivities())
.where((activity) => activity.destinationRef == ref)
.toList();
return Result.ok(activities);
} on Exception catch (error) {
return Result.error(error);
}
}
}

@ -0,0 +1,36 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/activity/activity.dart';
import '../../../utils/result.dart';
import '../../services/api/api_client.dart';
import 'activity_repository.dart';
/// Remote data source for [Activity].
/// Implements basic local caching.
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
class ActivityRepositoryRemote implements ActivityRepository {
ActivityRepositoryRemote({
required ApiClient apiClient,
}) : _apiClient = apiClient;
final ApiClient _apiClient;
final Map<String, List<Activity>> _cachedData = {};
@override
Future<Result<List<Activity>>> getByDestination(String ref) async {
if (!_cachedData.containsKey(ref)) {
// No cached data, request activities
final result = await _apiClient.getActivityByDestination(ref);
if (result is Ok) {
_cachedData[ref] = result.asOk.value;
}
return result;
} else {
// Return cached data if available
return Result.ok(_cachedData[ref]!);
}
}
}

@ -0,0 +1,22 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import '../../../utils/result.dart';
abstract class AuthRepository extends ChangeNotifier {
/// Returns true when the user is logged in
/// Returns [Future] because it will load a stored auth state the first time.
Future<bool> get isAuthenticated;
/// Perform login
Future<Result<void>> login({
required String email,
required String password,
});
/// Perform logout
Future<Result<void>> logout();
}

@ -0,0 +1,27 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../utils/result.dart';
import 'auth_repository.dart';
class AuthRepositoryDev extends AuthRepository {
/// User is always authenticated in dev scenarios
@override
Future<bool> get isAuthenticated => Future.value(true);
/// Login is always successful in dev scenarios
@override
Future<Result<void>> login({
required String email,
required String password,
}) async {
return Result.ok(null);
}
/// Logout is always successful in dev scenarios
@override
Future<Result<void>> logout() async {
return Result.ok(null);
}
}

@ -0,0 +1,112 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:logging/logging.dart';
import '../../../utils/result.dart';
import '../../services/api/api_client.dart';
import '../../services/api/auth_api_client.dart';
import '../../services/api/model/login_request/login_request.dart';
import '../../services/api/model/login_response/login_response.dart';
import '../../services/shared_preferences_service.dart';
import 'auth_repository.dart';
class AuthRepositoryRemote extends AuthRepository {
AuthRepositoryRemote({
required ApiClient apiClient,
required AuthApiClient authApiClient,
required SharedPreferencesService sharedPreferencesService,
}) : _apiClient = apiClient,
_authApiClient = authApiClient,
_sharedPreferencesService = sharedPreferencesService {
_apiClient.authHeaderProvider = _authHeaderProvider;
}
final AuthApiClient _authApiClient;
final ApiClient _apiClient;
final SharedPreferencesService _sharedPreferencesService;
bool? _isAuthenticated;
String? _authToken;
final _log = Logger('AuthRepositoryRemote');
/// Fetch token from shared preferences
Future<void> _fetch() async {
final result = await _sharedPreferencesService.fetchToken();
switch (result) {
case Ok<String?>():
_authToken = result.value;
_isAuthenticated = result.value != null;
case Error<String?>():
_log.severe(
'Failed to fech Token from SharedPreferences',
result.error,
);
}
}
@override
Future<bool> get isAuthenticated async {
// Status is cached
if (_isAuthenticated != null) {
return _isAuthenticated!;
}
// No status cached, fetch from storage
await _fetch();
return _isAuthenticated ?? false;
}
@override
Future<Result<void>> login({
required String email,
required String password,
}) async {
try {
final result = await _authApiClient.login(
LoginRequest(
email: email,
password: password,
),
);
switch (result) {
case Ok<LoginResponse>():
_log.info('User logged int');
// Set auth status
_isAuthenticated = true;
_authToken = result.value.token;
// Store in Shared preferences
return await _sharedPreferencesService.saveToken(result.value.token);
case Error<LoginResponse>():
_log.warning('Error logging in: ${result.error}');
return Result.error(result.error);
}
} finally {
notifyListeners();
}
}
@override
Future<Result<void>> logout() async {
_log.info('User logged out');
try {
// Clear stored auth token
final result = await _sharedPreferencesService.saveToken(null);
if (result is Error<void>) {
_log.severe('Failed to clear stored auth token');
}
// Clear token in ApiClient
_authToken = null;
// Clear authenticated status
_isAuthenticated = false;
return result;
} finally {
notifyListeners();
}
}
String? _authHeaderProvider() =>
_authToken != null ? 'Bearer $_authToken' : null;
}

@ -0,0 +1,21 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/booking/booking.dart';
import '../../../domain/models/booking/booking_summary.dart';
import '../../../utils/result.dart';
abstract class BookingRepository {
/// Returns the list of [BookingSummary] for the current user.
Future<Result<List<BookingSummary>>> getBookingsList();
/// Returns a full [Booking] given the id.
Future<Result<Booking>> getBooking(int id);
/// Creates a new [Booking].
Future<Result<void>> createBooking(Booking booking);
/// Delete booking
Future<Result<void>> delete(int id);
}

@ -0,0 +1,97 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:collection/collection.dart';
import '../../../domain/models/booking/booking.dart';
import '../../../domain/models/booking/booking_summary.dart';
import '../../../utils/result.dart';
import '../../services/local/local_data_service.dart';
import 'booking_repository.dart';
class BookingRepositoryLocal implements BookingRepository {
BookingRepositoryLocal({
required LocalDataService localDataService,
}) : _localDataService = localDataService;
// Only create default booking once
bool _isInitialized = false;
// Used to generate IDs for bookings
int _sequentialId = 0;
final _bookings = List<Booking>.empty(growable: true);
final LocalDataService _localDataService;
@override
Future<Result<void>> createBooking(Booking booking) async {
// Bookings created come without id, we need to assign one
final bookingWithId = booking.copyWith(id: _sequentialId++);
_bookings.add(bookingWithId);
return Result.ok(null);
}
@override
Future<Result<Booking>> getBooking(int id) async {
final booking = _bookings.firstWhereOrNull((booking) => booking.id == id);
if (booking == null) {
return Result.error(Exception('Booking not found'));
}
return Result.ok(booking);
}
@override
Future<Result<List<BookingSummary>>> getBookingsList() async {
// Initialize the repository with a default booking
if (!_isInitialized) {
await _createDefaultBooking();
_isInitialized = true;
}
return Result.ok(_createSummaries());
}
List<BookingSummary> _createSummaries() {
return _bookings
.map(
(booking) => BookingSummary(
id: booking.id!,
name:
'${booking.destination.name}, ${booking.destination.continent}',
startDate: booking.startDate,
endDate: booking.endDate,
),
)
.toList();
}
Future<void> _createDefaultBooking() async {
// create a default booking the first time
if (_bookings.isEmpty) {
final destination = (await _localDataService.getDestinations()).first;
final activities = (await _localDataService.getActivities())
.where((activity) => activity.destinationRef == destination.ref)
.take(4)
.toList();
_bookings.add(
Booking(
id: _sequentialId++,
startDate: DateTime(2024, 1, 1),
endDate: DateTime(2024, 2, 1),
destination: destination,
activity: activities,
),
);
}
}
@override
Future<Result<void>> delete(int id) async {
_bookings.removeWhere((booking) => booking.id == id);
return Result.ok(null);
}
}

@ -0,0 +1,118 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/activity/activity.dart';
import '../../../domain/models/booking/booking.dart';
import '../../../domain/models/booking/booking_summary.dart';
import '../../../domain/models/destination/destination.dart';
import '../../../utils/result.dart';
import '../../services/api/api_client.dart';
import '../../services/api/model/booking/booking_api_model.dart';
import 'booking_repository.dart';
class BookingRepositoryRemote implements BookingRepository {
BookingRepositoryRemote({
required ApiClient apiClient,
}) : _apiClient = apiClient;
final ApiClient _apiClient;
List<Destination>? _cachedDestinations;
@override
Future<Result<void>> createBooking(Booking booking) async {
try {
final BookingApiModel bookingApiModel = BookingApiModel(
startDate: booking.startDate,
endDate: booking.endDate,
name: '${booking.destination.name}, ${booking.destination.continent}',
destinationRef: booking.destination.ref,
activitiesRef:
booking.activity.map((activity) => activity.ref).toList(),
);
return _apiClient.postBooking(bookingApiModel);
} on Exception catch (e) {
return Result.error(e);
}
}
@override
Future<Result<Booking>> getBooking(int id) async {
try {
// Get booking by ID from server
final resultBooking = await _apiClient.getBooking(id);
if (resultBooking is Error<BookingApiModel>) {
return Result.error(resultBooking.error);
}
final booking = resultBooking.asOk.value;
// Load destinations if not loaded yet
if (_cachedDestinations == null) {
final resultDestination = await _apiClient.getDestinations();
if (resultDestination is Error<List<Destination>>) {
return Result.error(resultDestination.error);
}
_cachedDestinations = resultDestination.asOk.value;
}
// Get destination for booking
final destination = _cachedDestinations!.firstWhere(
(destination) => destination.ref == booking.destinationRef);
final resultActivities =
await _apiClient.getActivityByDestination(destination.ref);
if (resultActivities is Error<List<Activity>>) {
return Result.error(resultActivities.error);
}
final activities = resultActivities.asOk.value
.where((activity) => booking.activitiesRef.contains(activity.ref))
.toList();
return Result.ok(
Booking(
id: booking.id,
startDate: booking.startDate,
endDate: booking.endDate,
destination: destination,
activity: activities,
),
);
} on Exception catch (e) {
return Result.error(e);
}
}
@override
Future<Result<List<BookingSummary>>> getBookingsList() async {
try {
final result = await _apiClient.getBookings();
if (result is Error<List<BookingApiModel>>) {
return Result.error(result.error);
}
final bookingsApi = result.asOk.value;
return Result.ok(bookingsApi
.map(
(bookingApi) => BookingSummary(
id: bookingApi.id!,
name: bookingApi.name,
startDate: bookingApi.startDate,
endDate: bookingApi.endDate,
),
)
.toList());
} on Exception catch (e) {
return Result.error(e);
}
}
@override
Future<Result<void>> delete(int id) async {
try {
return _apiClient.deleteBooking(id);
} on Exception catch (e) {
return Result.error(e);
}
}
}

@ -0,0 +1,12 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/continent/continent.dart';
import '../../../utils/result.dart';
/// Data source with all possible continents.
abstract class ContinentRepository {
/// Get complete list of continents.
Future<Result<List<Continent>>> getContinents();
}

@ -0,0 +1,22 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/continent/continent.dart';
import '../../../utils/result.dart';
import '../../services/local/local_data_service.dart';
import 'continent_repository.dart';
/// Local data source with all possible continents.
class ContinentRepositoryLocal implements ContinentRepository {
ContinentRepositoryLocal({
required LocalDataService localDataService,
}) : _localDataService = localDataService;
final LocalDataService _localDataService;
@override
Future<Result<List<Continent>>> getContinents() async {
return Future.value(Result.ok(_localDataService.getContinents()));
}
}

@ -0,0 +1,37 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/continent/continent.dart';
import '../../../utils/result.dart';
import '../../services/api/api_client.dart';
import 'continent_repository.dart';
/// Remote data source for [Continent].
/// Implements basic local caching.
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
class ContinentRepositoryRemote implements ContinentRepository {
ContinentRepositoryRemote({
required ApiClient apiClient,
}) : _apiClient = apiClient;
final ApiClient _apiClient;
List<Continent>? _cachedData;
@override
Future<Result<List<Continent>>> getContinents() async {
if (_cachedData == null) {
// No cached data, request continents
final result = await _apiClient.getContinents();
if (result is Ok) {
// Store value if result Ok
_cachedData = result.asOk.value;
}
return result;
} else {
// Return cached data if available
return Result.ok(_cachedData!);
}
}
}

@ -0,0 +1,12 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/destination/destination.dart';
import '../../../utils/result.dart';
/// Data source with all possible destinations
abstract class DestinationRepository {
/// Get complete list of destinations
Future<Result<List<Destination>>> getDestinations();
}

@ -0,0 +1,28 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/destination/destination.dart';
import '../../../utils/result.dart';
import '../../services/local/local_data_service.dart';
import 'destination_repository.dart';
/// Local implementation of DestinationRepository
/// Uses data from assets folder
class DestinationRepositoryLocal implements DestinationRepository {
DestinationRepositoryLocal({
required LocalDataService localDataService,
}) : _localDataService = localDataService;
final LocalDataService _localDataService;
/// Obtain list of destinations from local assets
@override
Future<Result<List<Destination>>> getDestinations() async {
try {
return Result.ok(await _localDataService.getDestinations());
} on Exception catch (error) {
return Result.error(error);
}
}
}

@ -0,0 +1,37 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/destination/destination.dart';
import '../../../utils/result.dart';
import '../../services/api/api_client.dart';
import 'destination_repository.dart';
/// Remote data source for [Destination].
/// Implements basic local caching.
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
class DestinationRepositoryRemote implements DestinationRepository {
DestinationRepositoryRemote({
required ApiClient apiClient,
}) : _apiClient = apiClient;
final ApiClient _apiClient;
List<Destination>? _cachedData;
@override
Future<Result<List<Destination>>> getDestinations() async {
if (_cachedData == null) {
// No cached data, request destinations
final result = await _apiClient.getDestinations();
if (result is Ok) {
// Store value if result Ok
_cachedData = result.asOk.value;
}
return result;
} else {
// Return cached data if available
return Result.ok(_cachedData!);
}
}
}

@ -0,0 +1,17 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/itinerary_config/itinerary_config.dart';
import '../../../utils/result.dart';
/// Data source for the current [ItineraryConfig]
abstract class ItineraryConfigRepository {
/// Get current [ItineraryConfig], may be empty if no configuration started.
/// Method is async to support writing to database, file, etc.
Future<Result<ItineraryConfig>> getItineraryConfig();
/// Sets [ItineraryConfig], overrides the previous one stored.
/// Returns Result.Ok if set is successful.
Future<Result<void>> setItineraryConfig(ItineraryConfig itineraryConfig);
}

@ -0,0 +1,27 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import '../../../domain/models/itinerary_config/itinerary_config.dart';
import '../../../utils/result.dart';
import 'itinerary_config_repository.dart';
/// In-memory implementation of [ItineraryConfigRepository].
class ItineraryConfigRepositoryMemory implements ItineraryConfigRepository {
ItineraryConfig? _itineraryConfig;
@override
Future<Result<ItineraryConfig>> getItineraryConfig() async {
return Result.ok(_itineraryConfig ?? const ItineraryConfig());
}
@override
Future<Result<bool>> setItineraryConfig(
ItineraryConfig itineraryConfig,
) async {
_itineraryConfig = itineraryConfig;
return Result.ok(true);
}
}

@ -0,0 +1,12 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/user/user.dart';
import '../../../utils/result.dart';
/// Data source for user related data
abstract class UserRepository {
/// Get current user
Future<Result<User>> getUser();
}

@ -0,0 +1,21 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/user/user.dart';
import '../../../utils/result.dart';
import '../../services/local/local_data_service.dart';
import 'user_repository.dart';
class UserRepositoryLocal implements UserRepository {
UserRepositoryLocal({
required LocalDataService localDataService,
}) : _localDataService = localDataService;
final LocalDataService _localDataService;
@override
Future<Result<User>> getUser() async {
return Result.ok(_localDataService.getUser());
}
}

@ -0,0 +1,39 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../domain/models/user/user.dart';
import '../../../utils/result.dart';
import '../../services/api/api_client.dart';
import '../../services/api/model/user/user_api_model.dart';
import 'user_repository.dart';
class UserRepositoryRemote implements UserRepository {
UserRepositoryRemote({
required ApiClient apiClient,
}) : _apiClient = apiClient;
final ApiClient _apiClient;
User? _cachedData;
@override
Future<Result<User>> getUser() async {
if (_cachedData != null) {
return Future.value(Result.ok(_cachedData!));
}
final result = await _apiClient.getUser();
switch (result) {
case Ok<UserApiModel>():
final user = User(
name: result.value.name,
picture: result.value.picture,
);
_cachedData = user;
return Result.ok(user);
case Error<UserApiModel>():
return Result.error(result.error);
}
}
}

@ -0,0 +1,210 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import '../../../domain/models/activity/activity.dart';
import '../../../domain/models/continent/continent.dart';
import '../../../domain/models/destination/destination.dart';
import '../../../utils/result.dart';
import 'model/booking/booking_api_model.dart';
import 'model/user/user_api_model.dart';
/// Adds the `Authentication` header to a header configuration.
typedef AuthHeaderProvider = String? Function();
class ApiClient {
ApiClient({
String? host,
int? port,
HttpClient Function()? clientFactory,
}) : _host = host ?? 'localhost',
_port = port ?? 8080,
_clientFactory = clientFactory ?? (() => HttpClient());
final String _host;
final int _port;
final HttpClient Function() _clientFactory;
AuthHeaderProvider? _authHeaderProvider;
set authHeaderProvider(AuthHeaderProvider authHeaderProvider) {
_authHeaderProvider = authHeaderProvider;
}
Future<void> _authHeader(HttpHeaders headers) async {
final header = _authHeaderProvider?.call();
if (header != null) {
headers.add(HttpHeaders.authorizationHeader, header);
}
}
Future<Result<List<Continent>>> getContinents() async {
final client = _clientFactory();
try {
final request = await client.get(_host, _port, '/continent');
await _authHeader(request.headers);
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
final json = jsonDecode(stringData) as List<dynamic>;
return Result.ok(
json.map((element) => Continent.fromJson(element)).toList());
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
Future<Result<List<Destination>>> getDestinations() async {
final client = _clientFactory();
try {
final request = await client.get(_host, _port, '/destination');
await _authHeader(request.headers);
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
final json = jsonDecode(stringData) as List<dynamic>;
return Result.ok(
json.map((element) => Destination.fromJson(element)).toList());
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
Future<Result<List<Activity>>> getActivityByDestination(String ref) async {
final client = _clientFactory();
try {
final request =
await client.get(_host, _port, '/destination/$ref/activity');
await _authHeader(request.headers);
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
final json = jsonDecode(stringData) as List<dynamic>;
final activities =
json.map((element) => Activity.fromJson(element)).toList();
return Result.ok(activities);
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
Future<Result<List<BookingApiModel>>> getBookings() async {
final client = _clientFactory();
try {
final request = await client.get(_host, _port, '/booking');
await _authHeader(request.headers);
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
final json = jsonDecode(stringData) as List<dynamic>;
final bookings =
json.map((element) => BookingApiModel.fromJson(element)).toList();
return Result.ok(bookings);
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
Future<Result<BookingApiModel>> getBooking(int id) async {
final client = _clientFactory();
try {
final request = await client.get(_host, _port, '/booking/$id');
await _authHeader(request.headers);
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
final booking = BookingApiModel.fromJson(jsonDecode(stringData));
return Result.ok(booking);
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
Future<Result<BookingApiModel>> postBooking(BookingApiModel booking) async {
final client = _clientFactory();
try {
final request = await client.post(_host, _port, '/booking');
await _authHeader(request.headers);
request.write(jsonEncode(booking));
final response = await request.close();
if (response.statusCode == 201) {
final stringData = await response.transform(utf8.decoder).join();
final booking = BookingApiModel.fromJson(jsonDecode(stringData));
return Result.ok(booking);
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
Future<Result<UserApiModel>> getUser() async {
final client = _clientFactory();
try {
final request = await client.get(_host, _port, '/user');
await _authHeader(request.headers);
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
final user = UserApiModel.fromJson(jsonDecode(stringData));
return Result.ok(user);
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
Future<Result<void>> deleteBooking(int id) async {
final client = _clientFactory();
try {
final request = await client.delete(_host, _port, '/booking/$id');
await _authHeader(request.headers);
final response = await request.close();
// Response 204 "No Content", delete was successful
if (response.statusCode == 204) {
return Result.ok(null);
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
}

@ -0,0 +1,43 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import '../../../utils/result.dart';
import 'model/login_request/login_request.dart';
import 'model/login_response/login_response.dart';
class AuthApiClient {
AuthApiClient({
String? host,
int? port,
HttpClient Function()? clientFactory,
}) : _host = host ?? 'localhost',
_port = port ?? 8080,
_clientFactory = clientFactory ?? (() => HttpClient());
final String _host;
final int _port;
final HttpClient Function() _clientFactory;
Future<Result<LoginResponse>> login(LoginRequest loginRequest) async {
final client = _clientFactory();
try {
final request = await client.post(_host, _port, '/login');
request.write(jsonEncode(loginRequest));
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
return Result.ok(LoginResponse.fromJson(jsonDecode(stringData)));
} else {
return Result.error(const HttpException("Login error"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
}

@ -0,0 +1,35 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:freezed_annotation/freezed_annotation.dart';
part 'booking_api_model.freezed.dart';
part 'booking_api_model.g.dart';
@freezed
class BookingApiModel with _$BookingApiModel {
const factory BookingApiModel({
/// Booking ID. Generated when stored in server.
int? id,
/// Start date of the trip
required DateTime startDate,
/// End date of the trip
required DateTime endDate,
/// Booking name
/// Should be "Destination, Continent"
required String name,
/// Destination of the trip
required String destinationRef,
/// List of chosen activities
required List<String> activitiesRef,
}) = _BookingApiModel;
factory BookingApiModel.fromJson(Map<String, Object?> json) =>
_$BookingApiModelFromJson(json);
}

@ -0,0 +1,317 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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 'booking_api_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
BookingApiModel _$BookingApiModelFromJson(Map<String, dynamic> json) {
return _BookingApiModel.fromJson(json);
}
/// @nodoc
mixin _$BookingApiModel {
/// Booking ID. Generated when stored in server.
int? get id => throw _privateConstructorUsedError;
/// Start date of the trip
DateTime get startDate => throw _privateConstructorUsedError;
/// End date of the trip
DateTime get endDate => throw _privateConstructorUsedError;
/// Booking name
/// Should be "Destination, Continent"
String get name => throw _privateConstructorUsedError;
/// Destination of the trip
String get destinationRef => throw _privateConstructorUsedError;
/// List of chosen activities
List<String> get activitiesRef => throw _privateConstructorUsedError;
/// Serializes this BookingApiModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of BookingApiModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$BookingApiModelCopyWith<BookingApiModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $BookingApiModelCopyWith<$Res> {
factory $BookingApiModelCopyWith(
BookingApiModel value, $Res Function(BookingApiModel) then) =
_$BookingApiModelCopyWithImpl<$Res, BookingApiModel>;
@useResult
$Res call(
{int? id,
DateTime startDate,
DateTime endDate,
String name,
String destinationRef,
List<String> activitiesRef});
}
/// @nodoc
class _$BookingApiModelCopyWithImpl<$Res, $Val extends BookingApiModel>
implements $BookingApiModelCopyWith<$Res> {
_$BookingApiModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of BookingApiModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? startDate = null,
Object? endDate = null,
Object? name = null,
Object? destinationRef = null,
Object? activitiesRef = null,
}) {
return _then(_value.copyWith(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
destinationRef: null == destinationRef
? _value.destinationRef
: destinationRef // ignore: cast_nullable_to_non_nullable
as String,
activitiesRef: null == activitiesRef
? _value.activitiesRef
: activitiesRef // ignore: cast_nullable_to_non_nullable
as List<String>,
) as $Val);
}
}
/// @nodoc
abstract class _$$BookingApiModelImplCopyWith<$Res>
implements $BookingApiModelCopyWith<$Res> {
factory _$$BookingApiModelImplCopyWith(_$BookingApiModelImpl value,
$Res Function(_$BookingApiModelImpl) then) =
__$$BookingApiModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int? id,
DateTime startDate,
DateTime endDate,
String name,
String destinationRef,
List<String> activitiesRef});
}
/// @nodoc
class __$$BookingApiModelImplCopyWithImpl<$Res>
extends _$BookingApiModelCopyWithImpl<$Res, _$BookingApiModelImpl>
implements _$$BookingApiModelImplCopyWith<$Res> {
__$$BookingApiModelImplCopyWithImpl(
_$BookingApiModelImpl _value, $Res Function(_$BookingApiModelImpl) _then)
: super(_value, _then);
/// Create a copy of BookingApiModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? startDate = null,
Object? endDate = null,
Object? name = null,
Object? destinationRef = null,
Object? activitiesRef = null,
}) {
return _then(_$BookingApiModelImpl(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
destinationRef: null == destinationRef
? _value.destinationRef
: destinationRef // ignore: cast_nullable_to_non_nullable
as String,
activitiesRef: null == activitiesRef
? _value._activitiesRef
: activitiesRef // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$BookingApiModelImpl implements _BookingApiModel {
const _$BookingApiModelImpl(
{this.id,
required this.startDate,
required this.endDate,
required this.name,
required this.destinationRef,
required final List<String> activitiesRef})
: _activitiesRef = activitiesRef;
factory _$BookingApiModelImpl.fromJson(Map<String, dynamic> json) =>
_$$BookingApiModelImplFromJson(json);
/// Booking ID. Generated when stored in server.
@override
final int? id;
/// Start date of the trip
@override
final DateTime startDate;
/// End date of the trip
@override
final DateTime endDate;
/// Booking name
/// Should be "Destination, Continent"
@override
final String name;
/// Destination of the trip
@override
final String destinationRef;
/// List of chosen activities
final List<String> _activitiesRef;
/// List of chosen activities
@override
List<String> get activitiesRef {
if (_activitiesRef is EqualUnmodifiableListView) return _activitiesRef;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_activitiesRef);
}
@override
String toString() {
return 'BookingApiModel(id: $id, startDate: $startDate, endDate: $endDate, name: $name, destinationRef: $destinationRef, activitiesRef: $activitiesRef)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$BookingApiModelImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.startDate, startDate) ||
other.startDate == startDate) &&
(identical(other.endDate, endDate) || other.endDate == endDate) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.destinationRef, destinationRef) ||
other.destinationRef == destinationRef) &&
const DeepCollectionEquality()
.equals(other._activitiesRef, _activitiesRef));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, startDate, endDate, name,
destinationRef, const DeepCollectionEquality().hash(_activitiesRef));
/// Create a copy of BookingApiModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$BookingApiModelImplCopyWith<_$BookingApiModelImpl> get copyWith =>
__$$BookingApiModelImplCopyWithImpl<_$BookingApiModelImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$BookingApiModelImplToJson(
this,
);
}
}
abstract class _BookingApiModel implements BookingApiModel {
const factory _BookingApiModel(
{final int? id,
required final DateTime startDate,
required final DateTime endDate,
required final String name,
required final String destinationRef,
required final List<String> activitiesRef}) = _$BookingApiModelImpl;
factory _BookingApiModel.fromJson(Map<String, dynamic> json) =
_$BookingApiModelImpl.fromJson;
/// Booking ID. Generated when stored in server.
@override
int? get id;
/// Start date of the trip
@override
DateTime get startDate;
/// End date of the trip
@override
DateTime get endDate;
/// Booking name
/// Should be "Destination, Continent"
@override
String get name;
/// Destination of the trip
@override
String get destinationRef;
/// List of chosen activities
@override
List<String> get activitiesRef;
/// Create a copy of BookingApiModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$BookingApiModelImplCopyWith<_$BookingApiModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'booking_api_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$BookingApiModelImpl _$$BookingApiModelImplFromJson(
Map<String, dynamic> json) =>
_$BookingApiModelImpl(
id: (json['id'] as num?)?.toInt(),
startDate: DateTime.parse(json['startDate'] as String),
endDate: DateTime.parse(json['endDate'] as String),
name: json['name'] as String,
destinationRef: json['destinationRef'] as String,
activitiesRef: (json['activitiesRef'] as List<dynamic>)
.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$$BookingApiModelImplToJson(
_$BookingApiModelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'startDate': instance.startDate.toIso8601String(),
'endDate': instance.endDate.toIso8601String(),
'name': instance.name,
'destinationRef': instance.destinationRef,
'activitiesRef': instance.activitiesRef,
};

@ -0,0 +1,24 @@
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:freezed_annotation/freezed_annotation.dart';
part 'login_request.freezed.dart';
part 'login_request.g.dart';
/// Simple data class to hold login request data.
@freezed
class LoginRequest with _$LoginRequest {
const factory LoginRequest({
/// Email address.
required String email,
/// Plain text password.
required String password,
}) = _LoginRequest;
factory LoginRequest.fromJson(Map<String, Object?> json) =>
_$LoginRequestFromJson(json);
}

@ -0,0 +1,192 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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 'login_request.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
LoginRequest _$LoginRequestFromJson(Map<String, dynamic> json) {
return _LoginRequest.fromJson(json);
}
/// @nodoc
mixin _$LoginRequest {
/// Email address.
String get email => throw _privateConstructorUsedError;
/// Plain text password.
String get password => throw _privateConstructorUsedError;
/// Serializes this LoginRequest to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LoginRequestCopyWith<LoginRequest> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LoginRequestCopyWith<$Res> {
factory $LoginRequestCopyWith(
LoginRequest value, $Res Function(LoginRequest) then) =
_$LoginRequestCopyWithImpl<$Res, LoginRequest>;
@useResult
$Res call({String email, String password});
}
/// @nodoc
class _$LoginRequestCopyWithImpl<$Res, $Val extends LoginRequest>
implements $LoginRequestCopyWith<$Res> {
_$LoginRequestCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? email = null,
Object? password = null,
}) {
return _then(_value.copyWith(
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$LoginRequestImplCopyWith<$Res>
implements $LoginRequestCopyWith<$Res> {
factory _$$LoginRequestImplCopyWith(
_$LoginRequestImpl value, $Res Function(_$LoginRequestImpl) then) =
__$$LoginRequestImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String email, String password});
}
/// @nodoc
class __$$LoginRequestImplCopyWithImpl<$Res>
extends _$LoginRequestCopyWithImpl<$Res, _$LoginRequestImpl>
implements _$$LoginRequestImplCopyWith<$Res> {
__$$LoginRequestImplCopyWithImpl(
_$LoginRequestImpl _value, $Res Function(_$LoginRequestImpl) _then)
: super(_value, _then);
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? email = null,
Object? password = null,
}) {
return _then(_$LoginRequestImpl(
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$LoginRequestImpl implements _LoginRequest {
const _$LoginRequestImpl({required this.email, required this.password});
factory _$LoginRequestImpl.fromJson(Map<String, dynamic> json) =>
_$$LoginRequestImplFromJson(json);
/// Email address.
@override
final String email;
/// Plain text password.
@override
final String password;
@override
String toString() {
return 'LoginRequest(email: $email, password: $password)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoginRequestImpl &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, email, password);
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith =>
__$$LoginRequestImplCopyWithImpl<_$LoginRequestImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$LoginRequestImplToJson(
this,
);
}
}
abstract class _LoginRequest implements LoginRequest {
const factory _LoginRequest(
{required final String email,
required final String password}) = _$LoginRequestImpl;
factory _LoginRequest.fromJson(Map<String, dynamic> json) =
_$LoginRequestImpl.fromJson;
/// Email address.
@override
String get email;
/// Plain text password.
@override
String get password;
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith =>
throw _privateConstructorUsedError;
}

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'login_request.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$LoginRequestImpl _$$LoginRequestImplFromJson(Map<String, dynamic> json) =>
_$LoginRequestImpl(
email: json['email'] as String,
password: json['password'] as String,
);
Map<String, dynamic> _$$LoginRequestImplToJson(_$LoginRequestImpl instance) =>
<String, dynamic>{
'email': instance.email,
'password': instance.password,
};

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

Loading…
Cancel
Save