6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit 7b903aa6
Changed files
comwell_key_app/pubspec.yaml | 2 + concierge/.gitignore | 33 + concierge/.metadata | 33 + concierge/CHANGELOG.md | 3 + concierge/LICENSE | 1 + concierge/README.md | 15 + concierge/analysis_options.yaml | 4 + concierge/android/.gitignore | 9 + concierge/android/build.gradle | 66 ++ concierge/android/settings.gradle | 1 + concierge/android/src/main/AndroidManifest.xml | 3 + .../com/example/concierge/ConciergePlugin.kt | 33 + .../com/example/concierge/ConciergePluginTest.kt | 27 + concierge/assets/icons/arrow-right.svg | 3 + concierge/assets/icons/cocktail.svg | 3 + concierge/assets/icons/drop.svg | 4 + concierge/assets/icons/group-medium.svg | 6 + concierge/assets/icons/single-bed.svg | 3 + concierge/assets/icons/sun.svg | 3 + concierge/assets/icons/user-open.svg | 4 + concierge/build.yaml | 19 + concierge/example/.gitignore | 45 + concierge/example/README.md | 16 + concierge/example/analysis_options.yaml | 28 + concierge/example/android/.gitignore | 14 + concierge/example/android/app/build.gradle.kts | 44 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 + .../com/example/concierge_example/MainActivity.kt | 5 + .../main/res/drawable-v21/launch_background.xml | 12 + .../src/main/res/drawable/launch_background.xml | 12 + .../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../android/app/src/main/res/values/styles.xml | 18 + .../android/app/src/profile/AndroidManifest.xml | 7 + concierge/example/android/build.gradle.kts | 21 + concierge/example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + concierge/example/android/settings.gradle.kts | 25 + .../integration_test/plugin_integration_test.dart | 25 + concierge/example/ios/.gitignore | 34 + .../example/ios/Flutter/AppFrameworkInfo.plist | 26 + concierge/example/ios/Flutter/Debug.xcconfig | 2 + concierge/example/ios/Flutter/Release.xcconfig | 2 + concierge/example/ios/Podfile | 43 + .../example/ios/Runner.xcodeproj/project.pbxproj | 641 +++++++++++++++ .../project.xcworkspace/contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 119 +++ .../Runner.xcworkspace/contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + concierge/example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++ .../AppIcon.appiconset/Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../AppIcon.appiconset/Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../Assets.xcassets/LaunchImage.imageset/README.md | 5 + .../ios/Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../example/ios/Runner/Base.lproj/Main.storyboard | 26 + concierge/example/ios/Runner/Info.plist | 49 ++ .../example/ios/Runner/Runner-Bridging-Header.h | 1 + .../example/ios/RunnerTests/RunnerTests.swift | 29 + concierge/example/lib/main.dart | 59 ++ concierge/example/pubspec.lock | 901 +++++++++++++++++++++ concierge/example/pubspec.yaml | 87 ++ concierge/example/test/widget_test.dart | 27 + concierge/ios/.gitignore | 38 + concierge/ios/concierge.podspec | 29 + concierge/ios/concierge/Package.swift | 32 + .../Sources/concierge/ConciergePlugin.swift | 19 + .../Sources/concierge/PrivacyInfo.xcprivacy | 14 + .../data/remote/api/concierge_service.g.dart | 104 +++ .../data/remote/models/area_category_dto.g.dart | 24 + .../data/remote/models/area_details_dto.g.dart | 60 ++ .../remote/models/area_sub_category_dto.g.dart | 32 + .../remote/models/get_area_response_dto.g.dart | 14 + .../remote/models/get_property_response_dto.g.dart | 18 + .../data/remote/models/media_image_dto.g.dart | 24 + .../data/remote/models/promotion_dto.g.dart | 17 + .../data/remote/models/property_area_dto.g.dart | 48 ++ .../data/remote/models/property_dto.g.dart | 63 ++ .../data/remote/models/property_event_dto.g.dart | 26 + .../data/remote/models/property_intro_dto.g.dart | 26 + .../data/remote/models/reject_message_dto.g.dart | 15 + .../bloc/hotel_overview_page_state.freezed.dart | 280 +++++++ .../hotel_overview_page_route.g.dart | 35 + concierge/lib/concierge.dart | 64 ++ concierge/lib/concierge_config.dart | 15 + concierge/lib/concierge_method_channel.dart | 17 + concierge/lib/concierge_platform_interface.dart | 22 + .../secure_storage/concierge_secure_storage.dart | 23 + .../lib/data/remote/api/concierge_service.dart | 38 + .../lib/data/remote/models/area_category_dto.dart | 26 + .../lib/data/remote/models/area_details_dto.dart | 75 ++ .../data/remote/models/area_sub_category_dto.dart | 38 + .../data/remote/models/get_area_response_dto.dart | 19 + .../remote/models/get_property_response_dto.dart | 19 + .../lib/data/remote/models/media_image_dto.dart | 28 + .../lib/data/remote/models/promotion_dto.dart | 20 + .../lib/data/remote/models/property_area_dto.dart | 59 ++ concierge/lib/data/remote/models/property_dto.dart | 61 ++ .../lib/data/remote/models/property_event_dto.dart | 32 + .../lib/data/remote/models/property_intro_dto.dart | 30 + .../lib/data/remote/models/reject_message_dto.dart | 18 + .../repositories/property_repository_impl.dart | 18 + .../lib/domain/mappers/area_details_mapper.dart | 99 +++ concierge/lib/domain/mappers/property_mapper.dart | 106 +++ concierge/lib/domain/models/area_category.dart | 15 + concierge/lib/domain/models/area_details.dart | 50 ++ concierge/lib/domain/models/area_sub_category.dart | 23 + concierge/lib/domain/models/media_image.dart | 17 + concierge/lib/domain/models/promotion.dart | 8 + concierge/lib/domain/models/property.dart | 42 + concierge/lib/domain/models/property_area.dart | 39 + concierge/lib/domain/models/property_event.dart | 19 + concierge/lib/domain/models/property_intro.dart | 19 + concierge/lib/domain/models/reject_message.dart | 8 + .../domain/repositories/property_repository.dart | 7 + concierge/lib/flavors.dart | 28 + .../presentation/app/concierge_hotel_overview.dart | 42 + concierge/lib/presentation/base/base_cubit.dart | 15 + .../lib/presentation/navigation/app_routes.dart | 7 + concierge/lib/presentation/navigation/router.dart | 28 + .../transitions/slide_in_transition.dart | 58 ++ .../transitions/slide_up_transition.dart | 22 + .../bloc/hotel_overview_page_cubit.dart | 34 + .../bloc/hotel_overview_page_state.dart | 15 + .../hotel_overview_page_route.dart | 26 + .../hotel_overview_page_screen.dart | 45 + concierge/lib/presentation/theme/app_borders.dart | 8 + .../lib/presentation/theme/app_button_styles.dart | 311 +++++++ .../lib/presentation/theme/app_color_scheme.dart | 77 ++ concierge/lib/presentation/theme/app_colors.dart | 72 ++ .../lib/presentation/theme/app_textstyles.dart | 60 ++ concierge/lib/presentation/theme/dimens.dart | 25 + concierge/lib/presentation/theme/spaces.dart | 30 + .../widgets/hotel_overview_app_bar.dart | 297 +++++++ .../presentation/widgets/round_icon_button.dart | 34 + concierge/pubspec.yaml | 99 +++ concierge/scripts/build_ios.sh | 1 + concierge/scripts/dart/new_feature.dart | 149 ++++ concierge/scripts/dart/new_model.dart | 144 ++++ concierge/scripts/dart/utils.dart | 15 + concierge/scripts/dart/write_arb.dart | 52 ++ concierge/scripts/gen.sh | 2 + concierge/scripts/new_feature.sh | 17 + concierge/scripts/new_translations.sh | 3 + concierge/scripts/run_dev.sh | 1 + concierge/scripts/run_prod.sh | 1 + concierge/test/concierge_method_channel_test.dart | 27 + concierge/test/concierge_test.dart | 29 + .../data/remote/models/api_response.g.dart | 1 + payment_plugin/lib/payment_plugin_core.dart | 1 + 175 files changed, 6685 insertions(+)
Diff
diff --git a/comwell_key_app/pubspec.yaml b/comwell_key_app/pubspec.yaml
index 52b2df1c..5d81fa84 100644
--- a/comwell_key_app/pubspec.yaml
+++ b/comwell_key_app/pubspec.yaml
@@ -12,6 +12,8 @@ dependencies:
path: ../mobilekeys_sdk_plugin
payment_plugin:
path: ../payment_plugin
+ concierge_plugin:
+ path: ../concierge
cupertino_icons: ^1.0.2
focus_detector: ^2.0.0
permission_handler: ^11.3.0
diff --git a/concierge/.gitignore b/concierge/.gitignore
new file mode 100644
index 00000000..e7d347d9
--- /dev/null
+++ b/concierge/.gitignore
@@ -0,0 +1,33 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+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
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+build/
diff --git a/concierge/.metadata b/concierge/.metadata
new file mode 100644
index 00000000..c6db6cda
--- /dev/null
+++ b/concierge/.metadata
@@ -0,0 +1,33 @@
+# 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: "b25305a8832cfc6ba632a7f87ad455e319dccce8"
+ channel: "stable"
+
+project_type: plugin
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
+ base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
+ - platform: android
+ create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
+ base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
+ - platform: ios
+ create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
+ base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
+
+ # 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'
diff --git a/concierge/CHANGELOG.md b/concierge/CHANGELOG.md
new file mode 100644
index 00000000..41cc7d81
--- /dev/null
+++ b/concierge/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.
diff --git a/concierge/LICENSE b/concierge/LICENSE
new file mode 100644
index 00000000..ba75c69f
--- /dev/null
+++ b/concierge/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/concierge/README.md b/concierge/README.md
new file mode 100644
index 00000000..58278a05
--- /dev/null
+++ b/concierge/README.md
@@ -0,0 +1,15 @@
+# concierge
+
+A new Flutter plugin project.
+
+## Getting Started
+
+This project is a starting point for a Flutter
+[plug-in package](https://flutter.dev/to/develop-plugins),
+a specialized package that includes platform-specific implementation code for
+Android and/or iOS.
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
+
diff --git a/concierge/analysis_options.yaml b/concierge/analysis_options.yaml
new file mode 100644
index 00000000..a5744c1c
--- /dev/null
+++ b/concierge/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/concierge/android/.gitignore b/concierge/android/.gitignore
new file mode 100644
index 00000000..161bdcda
--- /dev/null
+++ b/concierge/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/concierge/android/build.gradle b/concierge/android/build.gradle
new file mode 100644
index 00000000..9d672e56
--- /dev/null
+++ b/concierge/android/build.gradle
@@ -0,0 +1,66 @@
+group = "com.example.concierge"
+version = "1.0-SNAPSHOT"
+
+buildscript {
+ ext.kotlin_version = "2.1.0"
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath("com.android.tools.build:gradle:8.7.3")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: "com.android.library"
+apply plugin: "kotlin-android"
+
+android {
+ namespace = "com.example.concierge"
+
+ compileSdk = 35
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11
+ }
+
+ sourceSets {
+ main.java.srcDirs += "src/main/kotlin"
+ test.java.srcDirs += "src/test/kotlin"
+ }
+
+ defaultConfig {
+ minSdk = 21
+ }
+
+ dependencies {
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+ testImplementation("org.mockito:mockito-core:5.0.0")
+ }
+
+ testOptions {
+ unitTests.all {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed", "standardOut", "standardError"
+ outputs.upToDateWhen {false}
+ showStandardStreams = true
+ }
+ }
+ }
+}
diff --git a/concierge/android/settings.gradle b/concierge/android/settings.gradle
new file mode 100644
index 00000000..30bd61c1
--- /dev/null
+++ b/concierge/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'concierge'
diff --git a/concierge/android/src/main/AndroidManifest.xml b/concierge/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..c5375b3c
--- /dev/null
+++ b/concierge/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.concierge">
+</manifest>
diff --git a/concierge/android/src/main/kotlin/com/example/concierge/ConciergePlugin.kt b/concierge/android/src/main/kotlin/com/example/concierge/ConciergePlugin.kt
new file mode 100644
index 00000000..eed31af5
--- /dev/null
+++ b/concierge/android/src/main/kotlin/com/example/concierge/ConciergePlugin.kt
@@ -0,0 +1,33 @@
+package com.example.concierge
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+
+/** ConciergePlugin */
+class ConciergePlugin: FlutterPlugin, MethodCallHandler {
+ /// The MethodChannel that will the communication between Flutter and native Android
+ ///
+ /// This local reference serves to register the plugin with the Flutter Engine and unregister it
+ /// when the Flutter Engine is detached from the Activity
+ private lateinit var channel : MethodChannel
+
+ override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, "concierge")
+ channel.setMethodCallHandler(this)
+ }
+
+ override fun onMethodCall(call: MethodCall, result: Result) {
+ if (call.method == "getPlatformVersion") {
+ result.success("Android ${android.os.Build.VERSION.RELEASE}")
+ } else {
+ result.notImplemented()
+ }
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ }
+}
diff --git a/concierge/android/src/test/kotlin/com/example/concierge/ConciergePluginTest.kt b/concierge/android/src/test/kotlin/com/example/concierge/ConciergePluginTest.kt
new file mode 100644
index 00000000..fb715c2b
--- /dev/null
+++ b/concierge/android/src/test/kotlin/com/example/concierge/ConciergePluginTest.kt
@@ -0,0 +1,27 @@
+package com.example.concierge
+
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import kotlin.test.Test
+import org.mockito.Mockito
+
+/*
+ * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
+ *
+ * Once you have built the plugin's example app, you can run these tests from the command
+ * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
+ * you can run them directly from IDEs that support JUnit such as Android Studio.
+ */
+
+internal class ConciergePluginTest {
+ @Test
+ fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
+ val plugin = ConciergePlugin()
+
+ val call = MethodCall("getPlatformVersion", null)
+ val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
+ plugin.onMethodCall(call, mockResult)
+
+ Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
+ }
+}
diff --git a/concierge/assets/icons/arrow-right.svg b/concierge/assets/icons/arrow-right.svg
new file mode 100644
index 00000000..0eb37809
--- /dev/null
+++ b/concierge/assets/icons/arrow-right.svg
@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7.52519 9.6103L13.36 3.77548L12.4761 2.8916L5.81095 9.55678L12.4494 17.0804L13.3867 16.2534L7.52519 9.6103Z" fill="black"/>
+</svg>
diff --git a/concierge/assets/icons/cocktail.svg b/concierge/assets/icons/cocktail.svg
new file mode 100644
index 00000000..a92b5d20
--- /dev/null
+++ b/concierge/assets/icons/cocktail.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.8312 9H16.2463L17.9771 6.75L13.1937 6.75L11.8312 9ZM12.3484 5.25L5.26184 5.25C4.32845 5.25 3.80104 6.3211 4.37013 7.06093L11.2503 16.0051L11.2503 21H7.50026V22.5H16.5003V21H12.7503L12.7503 16.0051L19.6304 7.06093C20.1995 6.3211 19.6721 5.25 18.7387 5.25L14.102 5.25L15.4645 3L20.2503 3V1.5H15.0418H14.6192L14.4003 1.86152L12.3484 5.25ZM11.4401 6.75L10.0776 9H7.75418L6.02341 6.75L11.4401 6.75ZM12.0003 14.5199L8.90803 10.5H15.0925L12.0003 14.5199Z" fill="black"/>
+</svg>
diff --git a/concierge/assets/icons/drop.svg b/concierge/assets/icons/drop.svg
new file mode 100644
index 00000000..7be8f091
--- /dev/null
+++ b/concierge/assets/icons/drop.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12 18.0001V16.5001C12.5799 16.4499 13.1235 16.1967 13.5351 15.7851C13.9466 15.3736 14.1998 14.83 14.25 14.2501H15.75C15.7015 15.229 15.2909 16.1549 14.5979 16.8479C13.9048 17.541 12.9789 17.9516 12 18.0001Z" fill="black"/>
+<path d="M12 21.0001C10.2105 20.9978 8.49491 20.2859 7.22953 19.0205C5.96415 17.7552 5.25226 16.0396 5.25 14.2501C5.29035 12.9343 5.67686 11.6523 6.37057 10.5335L11.3646 2.5776C11.4367 2.4763 11.532 2.39371 11.6426 2.33672C11.7531 2.27973 11.8756 2.25 12 2.25C12.1244 2.25 12.2469 2.27973 12.3574 2.33672C12.468 2.39371 12.5633 2.4763 12.6354 2.5776L17.6071 10.4949C18.3126 11.6239 18.7069 12.9195 18.75 14.2501C18.7477 16.0396 18.0359 17.7552 16.7705 19.0205C15.5051 20.2859 13.7895 20.9978 12 21.0001ZM12 4.3863L7.66372 11.2922C7.10492 12.1809 6.7898 13.201 6.75 14.2501C6.75 15.6425 7.30312 16.9778 8.28769 17.9624C9.27226 18.947 10.6076 19.5001 12 19.5001C13.3924 19.5001 14.7277 18.947 15.7123 17.9624C16.6969 16.9778 17.25 15.6425 17.25 14.2501C17.2073 13.1863 16.8844 12.1526 16.314 11.2536L12 4.3863Z" fill="black"/>
+</svg>
diff --git a/concierge/assets/icons/group-medium.svg b/concierge/assets/icons/group-medium.svg
new file mode 100644
index 00000000..019087ea
--- /dev/null
+++ b/concierge/assets/icons/group-medium.svg
@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M23.25 22.5H21.75V20.25C21.75 19.6533 21.5129 19.081 21.091 18.659C20.669 18.2371 20.0967 18 19.5 18H16.5C15.9033 18 15.331 18.2371 14.909 18.659C14.4871 19.081 14.25 19.6533 14.25 20.25V22.5H12.75V20.25C12.75 19.2554 13.1451 18.3016 13.8483 17.5984C14.5516 16.8951 15.5054 16.5 16.5 16.5H19.5C20.4946 16.5 21.4484 16.8951 22.1516 17.5984C22.8549 18.3016 23.25 19.2554 23.25 20.25V22.5Z" fill="black"/>
+<path d="M18 9C18.445 9 18.88 9.13196 19.25 9.37919C19.62 9.62643 19.9084 9.97783 20.0787 10.389C20.249 10.8001 20.2936 11.2525 20.2068 11.689C20.12 12.1254 19.9057 12.5263 19.591 12.841C19.2763 13.1557 18.8754 13.37 18.439 13.4568C18.0025 13.5436 17.5501 13.499 17.139 13.3287C16.7278 13.1584 16.3764 12.87 16.1292 12.5C15.882 12.13 15.75 11.695 15.75 11.25C15.75 10.6533 15.9871 10.081 16.409 9.65901C16.831 9.23705 17.4033 9 18 9ZM18 7.5C17.2583 7.5 16.5333 7.71993 15.9166 8.13199C15.2999 8.54404 14.8193 9.12971 14.5355 9.81494C14.2516 10.5002 14.1774 11.2542 14.3221 11.9816C14.4667 12.709 14.8239 13.3772 15.3483 13.9017C15.8728 14.4261 16.541 14.7833 17.2684 14.9279C17.9958 15.0726 18.7498 14.9984 19.4351 14.7145C20.1203 14.4307 20.706 13.9501 21.118 13.3334C21.5301 12.7167 21.75 11.9917 21.75 11.25C21.75 10.2554 21.3549 9.30161 20.6516 8.59835C19.9484 7.89509 18.9946 7.5 18 7.5Z" fill="black"/>
+<path d="M11.25 16.5H9.75V14.25C9.75 13.6533 9.51295 13.081 9.09099 12.659C8.66903 12.2371 8.09674 12 7.5 12H4.5C3.90326 12 3.33097 12.2371 2.90901 12.659C2.48705 13.081 2.25 13.6533 2.25 14.25V16.5H0.75V14.25C0.75 13.2554 1.14509 12.3016 1.84835 11.5983C2.55161 10.8951 3.50544 10.5 4.5 10.5H7.5C8.49456 10.5 9.44839 10.8951 10.1517 11.5983C10.8549 12.3016 11.25 13.2554 11.25 14.25V16.5Z" fill="black"/>
+<path d="M6 3C6.44501 3 6.88002 3.13196 7.25003 3.37919C7.62004 3.62643 7.90843 3.97783 8.07873 4.38896C8.24903 4.8001 8.29358 5.2525 8.20677 5.68895C8.11995 6.12541 7.90566 6.52632 7.59099 6.84099C7.27632 7.15566 6.87541 7.36995 6.43895 7.45677C6.0025 7.54358 5.5501 7.49903 5.13896 7.32873C4.72783 7.15843 4.37643 6.87004 4.12919 6.50003C3.88196 6.13002 3.75 5.69501 3.75 5.25C3.75 4.65326 3.98705 4.08097 4.40901 3.65901C4.83097 3.23705 5.40326 3 6 3ZM6 1.5C5.25832 1.5 4.5333 1.71993 3.91661 2.13199C3.29993 2.54404 2.81928 3.12971 2.53545 3.81494C2.25162 4.50016 2.17736 5.25416 2.32206 5.98159C2.46675 6.70902 2.8239 7.3772 3.34835 7.90165C3.8728 8.4261 4.54098 8.78325 5.26841 8.92795C5.99584 9.07264 6.74984 8.99838 7.43506 8.71455C8.12029 8.43072 8.70596 7.95007 9.11801 7.33339C9.53007 6.7167 9.75 5.99168 9.75 5.25C9.75 4.25544 9.35491 3.30161 8.65165 2.59835C7.94839 1.89509 6.99456 1.5 6 1.5Z" fill="black"/>
+</svg>
diff --git a/concierge/assets/icons/single-bed.svg b/concierge/assets/icons/single-bed.svg
new file mode 100644
index 00000000..ac496301
--- /dev/null
+++ b/concierge/assets/icons/single-bed.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6.75 3.75C6.33579 3.75 6 4.08579 6 4.5V9.75H8.25V6.75C8.25 5.92157 8.92157 5.25 9.75 5.25H14.25C15.0784 5.25 15.75 5.92157 15.75 6.75V9.75H18V4.5C18 4.08579 17.6642 3.75 17.25 3.75H6.75ZM15.75 11.25H8.25H5.54057L4.54057 14.25H19.4594L18.4594 11.25H15.75ZM14.25 9.75V6.75H9.75V9.75H14.25ZM4.5 15.75V18.75H19.5V15.75H4.5ZM4.5 21.75V20.25H19.5V21.75H21V14.1283L19.5 9.62829V4.5C19.5 3.25736 18.4926 2.25 17.25 2.25H6.75C5.50736 2.25 4.5 3.25736 4.5 4.5V9.62829L3 14.1283V21.75H4.5Z" fill="#AA8D65"/>
+</svg>
diff --git a/concierge/assets/icons/sun.svg b/concierge/assets/icons/sun.svg
new file mode 100644
index 00000000..e1980618
--- /dev/null
+++ b/concierge/assets/icons/sun.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12 9.00391C13.6569 9.00391 15 10.3471 15 12.0039C15 13.6608 13.6569 15.0039 12 15.0039C10.3431 15.0039 9 13.6608 9 12.0039C9.00207 10.3479 10.344 9.00597 12 9.00391ZM12 7.50391C9.51472 7.50391 7.5 9.51863 7.5 12.0039C7.5 14.4892 9.51472 16.5039 12 16.5039C14.4853 16.5039 16.5 14.4892 16.5 12.0039C16.5 9.51863 14.4853 7.50391 12 7.50391ZM4.0455 5.10991L5.1075 4.04941L7.73625 6.67891L6.675 7.73941L4.0455 5.10991ZM1.5 11.2539H5.25V12.7539H1.5V11.2539ZM4.0455 18.8987L6.675 16.2692L7.7355 17.3297L5.10675 19.9592L4.0455 18.8987ZM11.25 18.7539H12.75V22.5039H11.25V18.7539ZM16.2653 17.3289L17.3258 16.2684L19.9552 18.8979L18.8948 19.9584L16.2653 17.3289ZM18.75 11.2539H22.5V12.7539H18.75V11.2539ZM16.2653 6.67816L18.8948 4.04866L19.9552 5.10916L17.3258 7.73866L16.2653 6.67816ZM11.25 1.50391H12.75V5.25391H11.25V1.50391Z" fill="black"/>
+</svg>
diff --git a/concierge/assets/icons/user-open.svg b/concierge/assets/icons/user-open.svg
new file mode 100644
index 00000000..f0e5fa87
--- /dev/null
+++ b/concierge/assets/icons/user-open.svg
@@ -0,0 +1,4 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10 2.5C10.6181 2.5 11.2223 2.68328 11.7362 3.02666C12.2501 3.37004 12.6506 3.8581 12.8871 4.42911C13.1236 5.00013 13.1855 5.62847 13.065 6.23466C12.9444 6.84085 12.6467 7.39767 12.2097 7.83471C11.7727 8.27175 11.2158 8.56938 10.6097 8.68995C10.0035 8.81053 9.37513 8.74865 8.80411 8.51212C8.2331 8.2756 7.74504 7.87506 7.40166 7.36116C7.05828 6.84725 6.875 6.24307 6.875 5.625C6.875 4.7962 7.20424 4.00134 7.79029 3.41529C8.37634 2.82924 9.1712 2.5 10 2.5ZM10 1.25C9.13471 1.25 8.28885 1.50659 7.56938 1.98732C6.84992 2.46805 6.28916 3.15133 5.95803 3.95076C5.62689 4.75019 5.54025 5.62985 5.70906 6.47852C5.87787 7.32719 6.29455 8.10674 6.90641 8.71859C7.51826 9.33045 8.29781 9.74712 9.14648 9.91594C9.99515 10.0847 10.8748 9.99811 11.6742 9.66697C12.4737 9.33584 13.1569 8.77508 13.6377 8.05562C14.1184 7.33615 14.375 6.49029 14.375 5.625C14.375 4.46468 13.9141 3.35188 13.0936 2.53141C12.2731 1.71094 11.1603 1.25 10 1.25Z" fill="black"/>
+<path d="M16.25 18.75H15V15.625C15 15.2146 14.9192 14.8083 14.7621 14.4291C14.6051 14.05 14.3749 13.7055 14.0847 13.4153C13.7945 13.1251 13.45 12.8949 13.0709 12.7379C12.6917 12.5808 12.2854 12.5 11.875 12.5H8.125C7.2962 12.5 6.50134 12.8292 5.91529 13.4153C5.32924 14.0013 5 14.7962 5 15.625V18.75H3.75V15.625C3.75 14.4647 4.21094 13.3519 5.03141 12.5314C5.85188 11.7109 6.96468 11.25 8.125 11.25H11.875C13.0353 11.25 14.1481 11.7109 14.9686 12.5314C15.7891 13.3519 16.25 14.4647 16.25 15.625V18.75Z" fill="black"/>
+</svg>
diff --git a/concierge/build.yaml b/concierge/build.yaml
new file mode 100644
index 00000000..e7c27e27
--- /dev/null
+++ b/concierge/build.yaml
@@ -0,0 +1,19 @@
+targets:
+ $default:
+ builders:
+ source_gen:combining_builder:
+ options:
+ build_extensions:
+ '^lib/{{}}.dart': 'lib/_generated/{{}}.g.dart'
+ json_serializable:
+ options:
+ explicit_to_json: true
+ any_map: true
+ generate_for:
+ exclude:
+ - 'lib/database/**'
+ freezed|freezed:
+ enabled: true
+ options:
+ build_extensions:
+ '^lib/{{}}.dart': 'lib/_generated/{{}}.freezed.dart'
\ No newline at end of file
diff --git a/concierge/example/.gitignore b/concierge/example/.gitignore
new file mode 100644
index 00000000..79c113f9
--- /dev/null
+++ b/concierge/example/.gitignore
@@ -0,0 +1,45 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+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
diff --git a/concierge/example/README.md b/concierge/example/README.md
new file mode 100644
index 00000000..5f1c27f8
--- /dev/null
+++ b/concierge/example/README.md
@@ -0,0 +1,16 @@
+# concierge_example
+
+Demonstrates how to use the concierge plugin.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/concierge/example/analysis_options.yaml b/concierge/example/analysis_options.yaml
new file mode 100644
index 00000000..0d290213
--- /dev/null
+++ b/concierge/example/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/concierge/example/android/.gitignore b/concierge/example/android/.gitignore
new file mode 100644
index 00000000..be3943c9
--- /dev/null
+++ b/concierge/example/android/.gitignore
@@ -0,0 +1,14 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+.cxx/
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/concierge/example/android/app/build.gradle.kts b/concierge/example/android/app/build.gradle.kts
new file mode 100644
index 00000000..8a40242c
--- /dev/null
+++ b/concierge/example/android/app/build.gradle.kts
@@ -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.concierge_example"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11.toString()
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.example.concierge_example"
+ // 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.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/concierge/example/android/app/src/debug/AndroidManifest.xml b/concierge/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..399f6981
--- /dev/null
+++ b/concierge/example/android/app/src/debug/AndroidManifest.xml
@@ -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>
diff --git a/concierge/example/android/app/src/main/AndroidManifest.xml b/concierge/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..6cbb270b
--- /dev/null
+++ b/concierge/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <application
+ android:label="concierge_example"
+ 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>
diff --git a/concierge/example/android/app/src/main/kotlin/com/example/concierge_example/MainActivity.kt b/concierge/example/android/app/src/main/kotlin/com/example/concierge_example/MainActivity.kt
new file mode 100644
index 00000000..1b8ed9d5
--- /dev/null
+++ b/concierge/example/android/app/src/main/kotlin/com/example/concierge_example/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.concierge_example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/concierge/example/android/app/src/main/res/drawable-v21/launch_background.xml b/concierge/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 00000000..f74085f3
--- /dev/null
+++ b/concierge/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -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>
diff --git a/concierge/example/android/app/src/main/res/drawable/launch_background.xml b/concierge/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 00000000..304732f8
--- /dev/null
+++ b/concierge/example/android/app/src/main/res/drawable/launch_background.xml
@@ -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>
diff --git a/concierge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/concierge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..db77bb4b
Binary files /dev/null and b/concierge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/concierge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/concierge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..17987b79
Binary files /dev/null and b/concierge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/concierge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/concierge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..09d43914
Binary files /dev/null and b/concierge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/concierge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/concierge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..d5f1c8d3
Binary files /dev/null and b/concierge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/concierge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/concierge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..4d6372ee
Binary files /dev/null and b/concierge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/concierge/example/android/app/src/main/res/values-night/styles.xml b/concierge/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 00000000..06952be7
--- /dev/null
+++ b/concierge/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?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>
+ <!-- Theme applied to the Android Window as soon as the process has started.
+ This theme determines the color of the Android Window while your
+ Flutter UI initializes, as well as behind your Flutter UI while its
+ running.
+
+ This Theme is only used starting with V2 of Flutter's Android embedding. -->
+ <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+ <item name="android:windowBackground">?android:colorBackground</item>
+ </style>
+</resources>
diff --git a/concierge/example/android/app/src/main/res/values/styles.xml b/concierge/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..cb1ef880
--- /dev/null
+++ b/concierge/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?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>
+ <!-- Theme applied to the Android Window as soon as the process has started.
+ This theme determines the color of the Android Window while your
+ Flutter UI initializes, as well as behind your Flutter UI while its
+ running.
+
+ This Theme is only used starting with V2 of Flutter's Android embedding. -->
+ <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+ <item name="android:windowBackground">?android:colorBackground</item>
+ </style>
+</resources>
diff --git a/concierge/example/android/app/src/profile/AndroidManifest.xml b/concierge/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 00000000..399f6981
--- /dev/null
+++ b/concierge/example/android/app/src/profile/AndroidManifest.xml
@@ -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>
diff --git a/concierge/example/android/build.gradle.kts b/concierge/example/android/build.gradle.kts
new file mode 100644
index 00000000..89176ef4
--- /dev/null
+++ b/concierge/example/android/build.gradle.kts
@@ -0,0 +1,21 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register<Delete>("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/concierge/example/android/gradle.properties b/concierge/example/android/gradle.properties
new file mode 100644
index 00000000..f018a618
--- /dev/null
+++ b/concierge/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/concierge/example/android/gradle/wrapper/gradle-wrapper.properties b/concierge/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..ac3b4792
--- /dev/null
+++ b/concierge/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -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.12-all.zip
diff --git a/concierge/example/android/settings.gradle.kts b/concierge/example/android/settings.gradle.kts
new file mode 100644
index 00000000..ab39a10a
--- /dev/null
+++ b/concierge/example/android/settings.gradle.kts
@@ -0,0 +1,25 @@
+pluginManagement {
+ val flutterSdkPath = run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ 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.7.3" apply false
+ id("org.jetbrains.kotlin.android") version "2.1.0" apply false
+}
+
+include(":app")
diff --git a/concierge/example/integration_test/plugin_integration_test.dart b/concierge/example/integration_test/plugin_integration_test.dart
new file mode 100644
index 00000000..46e4eed5
--- /dev/null
+++ b/concierge/example/integration_test/plugin_integration_test.dart
@@ -0,0 +1,25 @@
+// This is a basic Flutter integration test.
+//
+// Since integration tests run in a full Flutter application, they can interact
+// with the host side of a plugin implementation, unlike Dart unit tests.
+//
+// For more information about Flutter integration tests, please see
+// https://flutter.dev/to/integration-testing
+
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'package:concierge/concierge.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ testWidgets('getPlatformVersion test', (WidgetTester tester) async {
+ final Concierge plugin = Concierge();
+ final String? version = await plugin.getPlatformVersion();
+ // The version string depends on the host platform running the test, so
+ // just assert that some non-empty string is returned.
+ expect(version?.isNotEmpty, true);
+ });
+}
diff --git a/concierge/example/ios/.gitignore b/concierge/example/ios/.gitignore
new file mode 100644
index 00000000..7a7f9873
--- /dev/null
+++ b/concierge/example/ios/.gitignore
@@ -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
diff --git a/concierge/example/ios/Flutter/AppFrameworkInfo.plist b/concierge/example/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 00000000..7c569640
--- /dev/null
+++ b/concierge/example/ios/Flutter/AppFrameworkInfo.plist
@@ -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>
diff --git a/concierge/example/ios/Flutter/Debug.xcconfig b/concierge/example/ios/Flutter/Debug.xcconfig
new file mode 100644
index 00000000..ec97fc6f
--- /dev/null
+++ b/concierge/example/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/concierge/example/ios/Flutter/Release.xcconfig b/concierge/example/ios/Flutter/Release.xcconfig
new file mode 100644
index 00000000..c4855bfe
--- /dev/null
+++ b/concierge/example/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/concierge/example/ios/Podfile b/concierge/example/ios/Podfile
new file mode 100644
index 00000000..e549ee22
--- /dev/null
+++ b/concierge/example/ios/Podfile
@@ -0,0 +1,43 @@
+# 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!
+
+ 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
diff --git a/concierge/example/ios/Runner.xcodeproj/project.pbxproj b/concierge/example/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..4a1e6b4c
--- /dev/null
+++ b/concierge/example/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,641 @@
+// !$*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 */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
+ 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 = (
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
+ );
+ 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;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
+ 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;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
+ );
+ 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)";
+ DEVELOPMENT_TEAM = 8RNV6AX4ZL;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.conciergeExample;
+ 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.conciergeExample.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.conciergeExample.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.conciergeExample.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)";
+ DEVELOPMENT_TEAM = 8RNV6AX4ZL;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.conciergeExample;
+ 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)";
+ DEVELOPMENT_TEAM = 8RNV6AX4ZL;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.conciergeExample;
+ 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 */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:">
+ </FileRef>
+</Workspace>
diff --git a/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.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>IDEDidComputeMac32BitWarning</key>
+ <true/>
+</dict>
+</plist>
diff --git a/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..f9b0d7c5
--- /dev/null
+++ b/concierge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -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>
diff --git a/concierge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/concierge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 00000000..c3fedb29
--- /dev/null
+++ b/concierge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "1510"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <PreActions>
+ <ExecutionAction
+ ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
+ <ActionContent
+ title = "Run Prepare Flutter Framework Script"
+ scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
+ <EnvironmentBuildable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+ BuildableName = "Runner.app"
+ BlueprintName = "Runner"
+ ReferencedContainer = "container:Runner.xcodeproj">
+ </BuildableReference>
+ </EnvironmentBuildable>
+ </ActionContent>
+ </ExecutionAction>
+ </PreActions>
+ <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"
+ customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
+ 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"
+ customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ enableGPUValidationMode = "1"
+ 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>
diff --git a/concierge/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/concierge/example/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..1d526a16
--- /dev/null
+++ b/concierge/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "group:Runner.xcodeproj">
+ </FileRef>
+</Workspace>
diff --git a/concierge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/concierge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/concierge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.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>IDEDidComputeMac32BitWarning</key>
+ <true/>
+</dict>
+</plist>
diff --git a/concierge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/concierge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..f9b0d7c5
--- /dev/null
+++ b/concierge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -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>
diff --git a/concierge/example/ios/Runner/AppDelegate.swift b/concierge/example/ios/Runner/AppDelegate.swift
new file mode 100644
index 00000000..62666446
--- /dev/null
+++ b/concierge/example/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import Flutter
+import UIKit
+
+@main
+@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)
+ }
+}
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..d36b1fab
--- /dev/null
+++ b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -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"
+ }
+}
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 00000000..dc9ada47
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 00000000..7353c41e
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 00000000..797d452e
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 00000000..6ed2d933
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 00000000..4cd7b009
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 00000000..fe730945
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 00000000..321773cd
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 00000000..797d452e
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 00000000..502f463a
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 00000000..0ec30343
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 00000000..0ec30343
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 00000000..e9f5fea2
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 00000000..84ac32ae
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 00000000..8953cba0
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 00000000..0467bf12
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 00000000..0bedcf2f
--- /dev/null
+++ b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -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"
+ }
+}
diff --git a/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 00000000..89c2725b
--- /dev/null
+++ b/concierge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -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.
\ No newline at end of file
diff --git a/concierge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/concierge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..f2e259c7
--- /dev/null
+++ b/concierge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -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>
diff --git a/concierge/example/ios/Runner/Base.lproj/Main.storyboard b/concierge/example/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 00000000..f3c28516
--- /dev/null
+++ b/concierge/example/ios/Runner/Base.lproj/Main.storyboard
@@ -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>
diff --git a/concierge/example/ios/Runner/Info.plist b/concierge/example/ios/Runner/Info.plist
new file mode 100644
index 00000000..2ec69669
--- /dev/null
+++ b/concierge/example/ios/Runner/Info.plist
@@ -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>Concierge</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>concierge_example</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>
diff --git a/concierge/example/ios/Runner/Runner-Bridging-Header.h b/concierge/example/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 00000000..308a2a56
--- /dev/null
+++ b/concierge/example/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/concierge/example/ios/RunnerTests/RunnerTests.swift b/concierge/example/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 00000000..4328f972
--- /dev/null
+++ b/concierge/example/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,29 @@
+import Flutter
+import UIKit
+import XCTest
+
+// If your plugin has been explicitly set to "type: .dynamic" in the Package.swift,
+// you will need to add your plugin as a dependency of RunnerTests within Xcode.
+
+@testable import concierge
+
+// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
+//
+// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+
+class RunnerTests: XCTestCase {
+
+ func testGetPlatformVersion() {
+ let plugin = ConciergePlugin()
+
+ let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
+
+ let resultExpectation = expectation(description: "result block must be called.")
+ plugin.handle(call) { result in
+ XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
+ resultExpectation.fulfill()
+ }
+ waitForExpectations(timeout: 1)
+ }
+
+}
diff --git a/concierge/example/lib/main.dart b/concierge/example/lib/main.dart
new file mode 100644
index 00000000..f6efaee7
--- /dev/null
+++ b/concierge/example/lib/main.dart
@@ -0,0 +1,59 @@
+import 'package:concierge/concierge.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+void main() => runApp(const MyApp());
+
+final _router = GoRouter(
+ routes: [
+ GoRoute(
+ path: '/',
+ builder: (context, state) => const HomePage(),
+ ),
+ GoRoute(
+ path: '/concierge/:hotelCode',
+ builder: (context, state) => Concierge.hotelOverview(
+ flavor: Flavor.production,
+ hotelCode: state.pathParameters['hotelCode']!,
+ userToken: 'example-token',
+ ),
+ ),
+ ],
+);
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp.router(
+ routerConfig: _router,
+ );
+ }
+}
+
+class HomePage extends StatelessWidget {
+ const HomePage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: const Text('Concierge example (GoRouter)')),
+ body: Center(
+ child: InkWell(
+ onTap: () => context.push('/concierge/cso'),
+ child: Container(
+ width: 220,
+ height: 120,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ color: Colors.black12,
+ borderRadius: BorderRadius.circular(16),
+ ),
+ child: const Text('Tap image button → open concierge'),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/concierge/example/pubspec.lock b/concierge/example/pubspec.lock
new file mode 100644
index 00000000..a7c6fada
--- /dev/null
+++ b/concierge/example/pubspec.lock
@@ -0,0 +1,901 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ _fe_analyzer_shared:
+ dependency: transitive
+ description:
+ name: _fe_analyzer_shared
+ sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
+ url: "https://pub.dev"
+ source: hosted
+ version: "88.0.0"
+ analyzer:
+ dependency: transitive
+ description:
+ name: analyzer
+ sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.1.1"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.7"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.7.0"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.13.0"
+ bloc:
+ dependency: transitive
+ description:
+ name: bloc
+ sha256: a2cebb899f91d36eeeaa55c7b20b5915db5a9df1b8fd4a3c9c825e22e474537d
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.1.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ build:
+ dependency: transitive
+ description:
+ name: build
+ sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.3"
+ build_config:
+ dependency: transitive
+ description:
+ name: build_config
+ sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.4"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.19.1"
+ concierge:
+ dependency: "direct main"
+ description:
+ path: ".."
+ relative: true
+ source: path
+ version: "0.0.1"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.7"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.8"
+ dart_style:
+ dependency: transitive
+ description:
+ name: dart_style
+ sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
+ dio:
+ dependency: transitive
+ description:
+ name: dio
+ sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.9.0"
+ dio_web_adapter:
+ dependency: transitive
+ description:
+ name: dio_web_adapter
+ sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ easy_localization:
+ dependency: transitive
+ description:
+ name: easy_localization
+ sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.8"
+ easy_logger:
+ dependency: transitive
+ description:
+ name: easy_logger
+ sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.0.2"
+ equatable:
+ dependency: transitive
+ description:
+ name: equatable
+ sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.7"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.3"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_bloc:
+ dependency: transitive
+ description:
+ name: flutter_bloc
+ sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.1.1"
+ flutter_dotenv:
+ dependency: transitive
+ description:
+ name: flutter_dotenv
+ sha256: d4130c4a43e0b13fefc593bc3961f2cb46e30cb79e253d4a526b1b5d24ae1ce4
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.0"
+ flutter_driver:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.0"
+ flutter_localizations:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_secure_storage:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage
+ sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.2.4"
+ flutter_secure_storage_linux:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_linux
+ sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.3"
+ flutter_secure_storage_macos:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_macos
+ sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
+ flutter_secure_storage_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_platform_interface
+ sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ flutter_secure_storage_web:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_web
+ sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ flutter_secure_storage_windows:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_windows
+ sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
+ flutter_svg:
+ dependency: transitive
+ description:
+ name: flutter_svg
+ sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.3"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ freezed_annotation:
+ dependency: transitive
+ description:
+ name: freezed_annotation
+ sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ fuchsia_remote_debug_protocol:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ gap:
+ dependency: transitive
+ description:
+ name: gap
+ sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.1"
+ get_it:
+ dependency: transitive
+ description:
+ name: get_it
+ sha256: ae78de7c3f2304b8d81f2bb6e320833e5e81de942188542328f074978cc0efa9
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.3.0"
+ glob:
+ dependency: transitive
+ description:
+ name: glob
+ sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.3"
+ go_router:
+ dependency: "direct main"
+ description:
+ name: go_router
+ sha256: d8f590a69729f719177ea68eb1e598295e8dbc41bbc247fed78b2c8a25660d7c
+ url: "https://pub.dev"
+ source: hosted
+ version: "16.3.0"
+ go_router_builder:
+ dependency: transitive
+ description:
+ name: go_router_builder
+ sha256: "07215ebe9d458cc2426f271a6443dbe5ee35cc313088064e013378614453f50a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.2"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.6.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.2"
+ integration_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ intl:
+ dependency: transitive
+ description:
+ name: intl
+ sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.20.2"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.7"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.9.0"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.0.9"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.9"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.1"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.1"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ lottie:
+ dependency: transitive
+ description:
+ name: lottie
+ sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.1"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.17"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.11.1"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.16.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
+ nested:
+ dependency: transitive
+ description:
+ name: nested
+ sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ package_config:
+ dependency: transitive
+ description:
+ name: package_config
+ sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.1"
+ path_parsing:
+ dependency: transitive
+ description:
+ name: path_parsing
+ sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ path_provider:
+ dependency: transitive
+ description:
+ name: path_provider
+ sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.5"
+ path_provider_android:
+ dependency: transitive
+ description:
+ name: path_provider_android
+ sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.19"
+ path_provider_foundation:
+ dependency: transitive
+ description:
+ name: path_provider_foundation
+ sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.0"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.6"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.8"
+ posix:
+ dependency: transitive
+ description:
+ name: posix
+ sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.3"
+ pretty_dio_logger:
+ dependency: transitive
+ description:
+ name: pretty_dio_logger
+ sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
+ process:
+ dependency: transitive
+ description:
+ name: process
+ sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.3"
+ provider:
+ dependency: transitive
+ description:
+ name: provider
+ sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.5+1"
+ pub_semver:
+ dependency: transitive
+ description:
+ name: pub_semver
+ sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ pubspec_parse:
+ dependency: transitive
+ description:
+ name: pubspec_parse
+ sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.5.0"
+ retrofit:
+ dependency: transitive
+ description:
+ name: retrofit
+ sha256: "84063c18a00d55af41d6b8401edf8473e8c215bd7068ef7ec5e34c60657ffdbe"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.9.1"
+ shared_preferences:
+ dependency: transitive
+ description:
+ name: shared_preferences
+ sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.3"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.13"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.4"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.3"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shimmer:
+ dependency: transitive
+ description:
+ name: shimmer
+ sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ source_gen:
+ dependency: transitive
+ description:
+ name: source_gen
+ sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.1"
+ source_helper:
+ dependency: transitive
+ description:
+ name: source_helper
+ sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.8"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.1"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.12.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.1"
+ sync_http:
+ dependency: transitive
+ description:
+ name: sync_http
+ sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.1"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.2"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.4"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
+ vector_graphics:
+ dependency: transitive
+ description:
+ name: vector_graphics
+ sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.19"
+ vector_graphics_codec:
+ dependency: transitive
+ description:
+ name: vector_graphics_codec
+ sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.13"
+ vector_graphics_compiler:
+ dependency: transitive
+ description:
+ name: vector_graphics_compiler
+ sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.19"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
+ url: "https://pub.dev"
+ source: hosted
+ version: "15.0.0"
+ watcher:
+ dependency: transitive
+ description:
+ name: watcher
+ sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ webdriver:
+ dependency: transitive
+ description:
+ name: webdriver
+ sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.15.0"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.6.1"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
+sdks:
+ dart: ">=3.8.1 <4.0.0"
+ flutter: ">=3.32.0"
diff --git a/concierge/example/pubspec.yaml b/concierge/example/pubspec.yaml
new file mode 100644
index 00000000..c906c058
--- /dev/null
+++ b/concierge/example/pubspec.yaml
@@ -0,0 +1,87 @@
+name: concierge_example
+description: "Demonstrates how to use the concierge plugin."
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+environment:
+ sdk: ^3.8.1
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+ concierge:
+ # When depending on this package from a real application you should use:
+ # concierge: ^x.y.z
+ # See https://dart.dev/tools/pub/dependencies#version-constraints
+ # The example app is bundled with the plugin so we use a path dependency on
+ # the parent directory to use the current plugin's version.
+ path: ../
+
+ go_router: ^16.2.0
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.8
+
+dev_dependencies:
+ integration_test:
+ sdk: flutter
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^5.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/to/resolution-aware-images
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/to/asset-from-package
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/to/font-from-package
diff --git a/concierge/example/test/widget_test.dart b/concierge/example/test/widget_test.dart
new file mode 100644
index 00000000..c73079fe
--- /dev/null
+++ b/concierge/example/test/widget_test.dart
@@ -0,0 +1,27 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:concierge_example/main.dart';
+
+void main() {
+ testWidgets('Verify Platform version', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that platform version is retrieved.
+ expect(
+ find.byWidgetPredicate(
+ (Widget widget) => widget is Text &&
+ widget.data!.startsWith('Running on:'),
+ ),
+ findsOneWidget,
+ );
+ });
+}
diff --git a/concierge/ios/.gitignore b/concierge/ios/.gitignore
new file mode 100644
index 00000000..034771fc
--- /dev/null
+++ b/concierge/ios/.gitignore
@@ -0,0 +1,38 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/ephemeral/
+/Flutter/flutter_export_environment.sh
diff --git a/concierge/ios/concierge.podspec b/concierge/ios/concierge.podspec
new file mode 100644
index 00000000..2268d027
--- /dev/null
+++ b/concierge/ios/concierge.podspec
@@ -0,0 +1,29 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint concierge.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+ s.name = 'concierge'
+ s.version = '0.0.1'
+ s.summary = 'A new Flutter plugin project.'
+ s.description = <<-DESC
+A new Flutter plugin project.
+ DESC
+ s.homepage = 'http://example.com'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'Your Company' => 'email@example.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'concierge/Sources/concierge/**/*'
+ s.dependency 'Flutter'
+ s.platform = :ios, '12.0'
+
+ # Flutter.framework does not contain a i386 slice.
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
+ s.swift_version = '5.0'
+
+ # If your plugin requires a privacy manifest, for example if it uses any
+ # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
+ # plugin's privacy impact, and then uncomment this line. For more information,
+ # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
+ # s.resource_bundles = {'concierge_privacy' => ['concierge/Sources/concierge/PrivacyInfo.xcprivacy']}
+end
diff --git a/concierge/ios/concierge/Package.swift b/concierge/ios/concierge/Package.swift
new file mode 100644
index 00000000..441c462b
--- /dev/null
+++ b/concierge/ios/concierge/Package.swift
@@ -0,0 +1,32 @@
+// swift-tools-version: 5.9
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "concierge",
+ platforms: [
+ .iOS("12.0")
+ ],
+ products: [
+ .library(name: "concierge", targets: ["concierge"])
+ ],
+ dependencies: [],
+ targets: [
+ .target(
+ name: "concierge",
+ dependencies: [],
+ resources: [
+ // If your plugin requires a privacy manifest, for example if it uses any required
+ // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's
+ // privacy impact, and then uncomment these lines. For more information, see
+ // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
+ // .process("PrivacyInfo.xcprivacy"),
+
+ // If you have other resources that need to be bundled with your plugin, refer to
+ // the following instructions to add them:
+ // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
+ ]
+ )
+ ]
+)
diff --git a/concierge/ios/concierge/Sources/concierge/ConciergePlugin.swift b/concierge/ios/concierge/Sources/concierge/ConciergePlugin.swift
new file mode 100644
index 00000000..0e35f410
--- /dev/null
+++ b/concierge/ios/concierge/Sources/concierge/ConciergePlugin.swift
@@ -0,0 +1,19 @@
+import Flutter
+import UIKit
+
+public class ConciergePlugin: NSObject, FlutterPlugin {
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let channel = FlutterMethodChannel(name: "concierge", binaryMessenger: registrar.messenger())
+ let instance = ConciergePlugin()
+ registrar.addMethodCallDelegate(instance, channel: channel)
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "getPlatformVersion":
+ result("iOS " + UIDevice.current.systemVersion)
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+ }
+}
diff --git a/concierge/ios/concierge/Sources/concierge/PrivacyInfo.xcprivacy b/concierge/ios/concierge/Sources/concierge/PrivacyInfo.xcprivacy
new file mode 100644
index 00000000..a34b7e2e
--- /dev/null
+++ b/concierge/ios/concierge/Sources/concierge/PrivacyInfo.xcprivacy
@@ -0,0 +1,14 @@
+<?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>NSPrivacyTrackingDomains</key>
+ <array/>
+ <key>NSPrivacyAccessedAPITypes</key>
+ <array/>
+ <key>NSPrivacyCollectedDataTypes</key>
+ <array/>
+ <key>NSPrivacyTracking</key>
+ <false/>
+</dict>
+</plist>
diff --git a/concierge/lib/_generated/data/remote/api/concierge_service.g.dart b/concierge/lib/_generated/data/remote/api/concierge_service.g.dart
new file mode 100644
index 00000000..1fdeb9e6
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/api/concierge_service.g.dart
@@ -0,0 +1,104 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/api/concierge_service.dart';
+
+// dart format off
+
+// **************************************************************************
+// RetrofitGenerator
+// **************************************************************************
+
+// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main
+
+class _ConciergeService implements ConciergeService {
+ _ConciergeService(this._dio, {this.baseUrl, this.errorLogger});
+
+ final Dio _dio;
+
+ String? baseUrl;
+
+ final ParseErrorLogger? errorLogger;
+
+ @override
+ Future<GetPropertyResponseDto> getHotelOverview(String hotelcode) async {
+ final _extra = <String, dynamic>{};
+ final queryParameters = <String, dynamic>{};
+ final _headers = <String, dynamic>{};
+ const Map<String, dynamic>? _data = null;
+ final _options = _setStreamType<GetPropertyResponseDto>(
+ Options(method: 'GET', headers: _headers, extra: _extra)
+ .compose(
+ _dio.options,
+ '/hotels/domains/${hotelcode}',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+ );
+ final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+ late GetPropertyResponseDto _value;
+ try {
+ _value = GetPropertyResponseDto.fromJson(_result.data!);
+ } on Object catch (e, s) {
+ errorLogger?.logError(e, s, _options, _result);
+ rethrow;
+ }
+ return _value;
+ }
+
+ @override
+ Future<GetAreaResponseDto> getArea(String areaid) async {
+ final _extra = <String, dynamic>{};
+ final queryParameters = <String, dynamic>{};
+ final _headers = <String, dynamic>{};
+ const Map<String, dynamic>? _data = null;
+ final _options = _setStreamType<GetAreaResponseDto>(
+ Options(method: 'GET', headers: _headers, extra: _extra)
+ .compose(
+ _dio.options,
+ '/areas/${areaid}',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+ );
+ final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+ late GetAreaResponseDto _value;
+ try {
+ _value = GetAreaResponseDto.fromJson(_result.data!);
+ } on Object catch (e, s) {
+ errorLogger?.logError(e, s, _options, _result);
+ rethrow;
+ }
+ return _value;
+ }
+
+ RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
+ if (T != dynamic &&
+ !(requestOptions.responseType == ResponseType.bytes ||
+ requestOptions.responseType == ResponseType.stream)) {
+ if (T == String) {
+ requestOptions.responseType = ResponseType.plain;
+ } else {
+ requestOptions.responseType = ResponseType.json;
+ }
+ }
+ return requestOptions;
+ }
+
+ String _combineBaseUrls(String dioBaseUrl, String? baseUrl) {
+ if (baseUrl == null || baseUrl.trim().isEmpty) {
+ return dioBaseUrl;
+ }
+
+ final url = Uri.parse(baseUrl);
+
+ if (url.isAbsolute) {
+ return url.toString();
+ }
+
+ return Uri.parse(dioBaseUrl).resolveUri(url).toString();
+ }
+}
+
+// dart format on
diff --git a/concierge/lib/_generated/data/remote/models/area_category_dto.g.dart b/concierge/lib/_generated/data/remote/models/area_category_dto.g.dart
new file mode 100644
index 00000000..d614c2f6
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/area_category_dto.g.dart
@@ -0,0 +1,24 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/area_category_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+AreaCategoryDto _$AreaCategoryDtoFromJson(Map json) => AreaCategoryDto(
+ id: (json['id'] as num).toInt(),
+ name: json['name'] as String,
+ subCategories: (json['sub_categories'] as List<dynamic>)
+ .map(
+ (e) => AreaSubCategoryDto.fromJson(Map<String, dynamic>.from(e as Map)),
+ )
+ .toList(),
+);
+
+Map<String, dynamic> _$AreaCategoryDtoToJson(AreaCategoryDto instance) =>
+ <String, dynamic>{
+ 'id': instance.id,
+ 'name': instance.name,
+ 'sub_categories': instance.subCategories.map((e) => e.toJson()).toList(),
+ };
diff --git a/concierge/lib/_generated/data/remote/models/area_details_dto.g.dart b/concierge/lib/_generated/data/remote/models/area_details_dto.g.dart
new file mode 100644
index 00000000..b639ac62
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/area_details_dto.g.dart
@@ -0,0 +1,60 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/area_details_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+AreaDetailsDto _$AreaDetailsDtoFromJson(Map json) => AreaDetailsDto(
+ id: (json['id'] as num).toInt(),
+ name: json['name'] as String,
+ pickupText: json['pickup_text'] as String,
+ deliveryText: json['delivery_text'] as String,
+ isOpen: (json['is_open'] as num).toInt(),
+ isMeetingArea: (json['is_meeting_area'] as num).toInt(),
+ isGuestArea: (json['is_guest_area'] as num).toInt(),
+ isPrearrivalArea: (json['is_prearrival_area'] as num).toInt(),
+ theme: json['theme'] as String,
+ icon: json['icon'] as String,
+ deliveryPrice: (json['delivery_price'] as num).toInt(),
+ isDeliveryDefault: (json['is_delivery_default'] as num).toInt(),
+ hasDelivery: (json['has_delivery'] as num).toInt(),
+ events: (json['events'] as List<dynamic>)
+ .map(
+ (e) => PropertyEventDto.fromJson(Map<String, dynamic>.from(e as Map)),
+ )
+ .toList(),
+ skipLocation: json['skip_location'] as bool,
+ categories: (json['categories'] as List<dynamic>)
+ .map((e) => AreaCategoryDto.fromJson(Map<String, dynamic>.from(e as Map)))
+ .toList(),
+ promotion: PromotionDto.fromJson(
+ Map<String, dynamic>.from(json['promotion'] as Map),
+ ),
+ deliveryTimes: json['delivery_times'] as List<dynamic>,
+ supportItems: json['support_items'] as List<dynamic>,
+);
+
+Map<String, dynamic> _$AreaDetailsDtoToJson(AreaDetailsDto instance) =>
+ <String, dynamic>{
+ 'id': instance.id,
+ 'name': instance.name,
+ 'pickup_text': instance.pickupText,
+ 'delivery_text': instance.deliveryText,
+ 'is_open': instance.isOpen,
+ 'is_meeting_area': instance.isMeetingArea,
+ 'is_guest_area': instance.isGuestArea,
+ 'is_prearrival_area': instance.isPrearrivalArea,
+ 'theme': instance.theme,
+ 'icon': instance.icon,
+ 'delivery_price': instance.deliveryPrice,
+ 'is_delivery_default': instance.isDeliveryDefault,
+ 'has_delivery': instance.hasDelivery,
+ 'events': instance.events.map((e) => e.toJson()).toList(),
+ 'skip_location': instance.skipLocation,
+ 'categories': instance.categories.map((e) => e.toJson()).toList(),
+ 'promotion': instance.promotion.toJson(),
+ 'delivery_times': instance.deliveryTimes,
+ 'support_items': instance.supportItems,
+ };
diff --git a/concierge/lib/_generated/data/remote/models/area_sub_category_dto.g.dart b/concierge/lib/_generated/data/remote/models/area_sub_category_dto.g.dart
new file mode 100644
index 00000000..41479a9c
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/area_sub_category_dto.g.dart
@@ -0,0 +1,32 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/area_sub_category_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+AreaSubCategoryDto _$AreaSubCategoryDtoFromJson(Map json) => AreaSubCategoryDto(
+ id: (json['id'] as num).toInt(),
+ name: json['name'] as String,
+ subTitle: json['sub_title'] as String,
+ heroTitle: json['hero_title'] as String,
+ description: json['description'] as String,
+ heroImage: MediaImageDto.fromJson(
+ Map<String, dynamic>.from(json['hero_image'] as Map),
+ ),
+ products: (json['products'] as List<dynamic>)
+ .map((e) => (e as num).toInt())
+ .toList(),
+);
+
+Map<String, dynamic> _$AreaSubCategoryDtoToJson(AreaSubCategoryDto instance) =>
+ <String, dynamic>{
+ 'id': instance.id,
+ 'name': instance.name,
+ 'sub_title': instance.subTitle,
+ 'hero_title': instance.heroTitle,
+ 'description': instance.description,
+ 'hero_image': instance.heroImage.toJson(),
+ 'products': instance.products,
+ };
diff --git a/concierge/lib/_generated/data/remote/models/get_area_response_dto.g.dart b/concierge/lib/_generated/data/remote/models/get_area_response_dto.g.dart
new file mode 100644
index 00000000..6527ac18
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/get_area_response_dto.g.dart
@@ -0,0 +1,14 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/get_area_response_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+GetAreaResponseDto _$GetAreaResponseDtoFromJson(Map json) => GetAreaResponseDto(
+ data: AreaDetailsDto.fromJson(Map<String, dynamic>.from(json['data'] as Map)),
+);
+
+Map<String, dynamic> _$GetAreaResponseDtoToJson(GetAreaResponseDto instance) =>
+ <String, dynamic>{'data': instance.data.toJson()};
diff --git a/concierge/lib/_generated/data/remote/models/get_property_response_dto.g.dart b/concierge/lib/_generated/data/remote/models/get_property_response_dto.g.dart
new file mode 100644
index 00000000..c2f532fd
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/get_property_response_dto.g.dart
@@ -0,0 +1,18 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/get_property_response_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+GetPropertyResponseDto _$GetPropertyResponseDtoFromJson(Map json) =>
+ GetPropertyResponseDto(
+ data: PropertyDto.fromJson(
+ Map<String, dynamic>.from(json['data'] as Map),
+ ),
+ );
+
+Map<String, dynamic> _$GetPropertyResponseDtoToJson(
+ GetPropertyResponseDto instance,
+) => <String, dynamic>{'data': instance.data.toJson()};
diff --git a/concierge/lib/_generated/data/remote/models/media_image_dto.g.dart b/concierge/lib/_generated/data/remote/models/media_image_dto.g.dart
new file mode 100644
index 00000000..0d8c7691
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/media_image_dto.g.dart
@@ -0,0 +1,24 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/media_image_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+MediaImageDto _$MediaImageDtoFromJson(Map json) => MediaImageDto(
+ url: json['url'] as String,
+ preload: json['preload'] as String,
+ thumbnail: json['thumbnail'] as String,
+ productHero: json['product_hero'] as String,
+ alt: json['alt'] as String,
+);
+
+Map<String, dynamic> _$MediaImageDtoToJson(MediaImageDto instance) =>
+ <String, dynamic>{
+ 'url': instance.url,
+ 'preload': instance.preload,
+ 'thumbnail': instance.thumbnail,
+ 'product_hero': instance.productHero,
+ 'alt': instance.alt,
+ };
diff --git a/concierge/lib/_generated/data/remote/models/promotion_dto.g.dart b/concierge/lib/_generated/data/remote/models/promotion_dto.g.dart
new file mode 100644
index 00000000..cd08e382
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/promotion_dto.g.dart
@@ -0,0 +1,17 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/promotion_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+PromotionDto _$PromotionDtoFromJson(Map json) => PromotionDto(
+ title: json['title'] as String,
+ products: (json['products'] as List<dynamic>)
+ .map((e) => (e as num).toInt())
+ .toList(),
+);
+
+Map<String, dynamic> _$PromotionDtoToJson(PromotionDto instance) =>
+ <String, dynamic>{'title': instance.title, 'products': instance.products};
diff --git a/concierge/lib/_generated/data/remote/models/property_area_dto.g.dart b/concierge/lib/_generated/data/remote/models/property_area_dto.g.dart
new file mode 100644
index 00000000..10b08b7e
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/property_area_dto.g.dart
@@ -0,0 +1,48 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/property_area_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+PropertyAreaDto _$PropertyAreaDtoFromJson(Map json) => PropertyAreaDto(
+ id: (json['id'] as num).toInt(),
+ name: json['name'] as String,
+ pickupText: json['pickup_text'] as String,
+ deliveryText: json['delivery_text'] as String,
+ isOpen: (json['is_open'] as num).toInt(),
+ isMeetingArea: (json['is_meeting_area'] as num).toInt(),
+ isGuestArea: (json['is_guest_area'] as num).toInt(),
+ isPrearrivalArea: (json['is_prearrival_area'] as num).toInt(),
+ theme: json['theme'] as String,
+ icon: json['icon'] as String,
+ deliveryPrice: (json['delivery_price'] as num?)?.toInt(),
+ isDeliveryDefault: (json['is_delivery_default'] as num).toInt(),
+ hasDelivery: (json['has_delivery'] as num).toInt(),
+ events: (json['events'] as List<dynamic>)
+ .map(
+ (e) => PropertyEventDto.fromJson(Map<String, dynamic>.from(e as Map)),
+ )
+ .toList(),
+ skipLocation: json['skip_location'] as bool,
+);
+
+Map<String, dynamic> _$PropertyAreaDtoToJson(PropertyAreaDto instance) =>
+ <String, dynamic>{
+ 'id': instance.id,
+ 'name': instance.name,
+ 'pickup_text': instance.pickupText,
+ 'delivery_text': instance.deliveryText,
+ 'is_open': instance.isOpen,
+ 'is_meeting_area': instance.isMeetingArea,
+ 'is_guest_area': instance.isGuestArea,
+ 'is_prearrival_area': instance.isPrearrivalArea,
+ 'theme': instance.theme,
+ 'icon': instance.icon,
+ 'delivery_price': instance.deliveryPrice,
+ 'is_delivery_default': instance.isDeliveryDefault,
+ 'has_delivery': instance.hasDelivery,
+ 'events': instance.events.map((e) => e.toJson()).toList(),
+ 'skip_location': instance.skipLocation,
+ };
diff --git a/concierge/lib/_generated/data/remote/models/property_dto.g.dart b/concierge/lib/_generated/data/remote/models/property_dto.g.dart
new file mode 100644
index 00000000..73781b53
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/property_dto.g.dart
@@ -0,0 +1,63 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/property_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+PropertyDto _$PropertyDtoFromJson(Map json) => PropertyDto(
+ id: (json['id'] as num).toInt(),
+ name: json['name'] as String,
+ domain: json['domain'] as String,
+ languageCode: json['language_code'] as String,
+ currency: json['currency'] as String,
+ hotline: json['hotline'] as String,
+ showInMenu: (json['show_in_menu'] as num).toInt(),
+ helpBody: json['help_body'] as String,
+ splashVideo: json['splash_video'] as String?,
+ areas: (json['areas'] as List<dynamic>)
+ .map((e) => PropertyAreaDto.fromJson(Map<String, dynamic>.from(e as Map)))
+ .toList(),
+ heroImages: (json['hero_images'] as List<dynamic>)
+ .map((e) => MediaImageDto.fromJson(Map<String, dynamic>.from(e as Map)))
+ .toList(),
+ logoImage: MediaImageDto.fromJson(
+ Map<String, dynamic>.from(json['logo_image'] as Map),
+ ),
+ profileImage: MediaImageDto.fromJson(
+ Map<String, dynamic>.from(json['profile_image'] as Map),
+ ),
+ intros: (json['intros'] as List<dynamic>)
+ .map(
+ (e) => PropertyIntroDto.fromJson(Map<String, dynamic>.from(e as Map)),
+ )
+ .toList(),
+ menuItems: json['menu_items'] as List<dynamic>,
+ rejectMessages: (json['reject_messages'] as List<dynamic>)
+ .map(
+ (e) => RejectMessageDto.fromJson(Map<String, dynamic>.from(e as Map)),
+ )
+ .toList(),
+);
+
+Map<String, dynamic> _$PropertyDtoToJson(
+ PropertyDto instance,
+) => <String, dynamic>{
+ 'id': instance.id,
+ 'name': instance.name,
+ 'domain': instance.domain,
+ 'language_code': instance.languageCode,
+ 'currency': instance.currency,
+ 'hotline': instance.hotline,
+ 'show_in_menu': instance.showInMenu,
+ 'help_body': instance.helpBody,
+ 'splash_video': instance.splashVideo,
+ 'areas': instance.areas.map((e) => e.toJson()).toList(),
+ 'hero_images': instance.heroImages.map((e) => e.toJson()).toList(),
+ 'logo_image': instance.logoImage.toJson(),
+ 'profile_image': instance.profileImage.toJson(),
+ 'intros': instance.intros.map((e) => e.toJson()).toList(),
+ 'menu_items': instance.menuItems,
+ 'reject_messages': instance.rejectMessages.map((e) => e.toJson()).toList(),
+};
diff --git a/concierge/lib/_generated/data/remote/models/property_event_dto.g.dart b/concierge/lib/_generated/data/remote/models/property_event_dto.g.dart
new file mode 100644
index 00000000..65e9cd8d
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/property_event_dto.g.dart
@@ -0,0 +1,26 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/property_event_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+PropertyEventDto _$PropertyEventDtoFromJson(Map json) => PropertyEventDto(
+ id: (json['id'] as num).toInt(),
+ title: json['title'] as String,
+ subTitle: json['sub_title'] as String,
+ ctaLink: json['cta_link'] as String?,
+ heroImage: MediaImageDto.fromJson(
+ Map<String, dynamic>.from(json['hero_image'] as Map),
+ ),
+);
+
+Map<String, dynamic> _$PropertyEventDtoToJson(PropertyEventDto instance) =>
+ <String, dynamic>{
+ 'id': instance.id,
+ 'title': instance.title,
+ 'sub_title': instance.subTitle,
+ 'cta_link': instance.ctaLink,
+ 'hero_image': instance.heroImage.toJson(),
+ };
diff --git a/concierge/lib/_generated/data/remote/models/property_intro_dto.g.dart b/concierge/lib/_generated/data/remote/models/property_intro_dto.g.dart
new file mode 100644
index 00000000..c2718b06
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/property_intro_dto.g.dart
@@ -0,0 +1,26 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/property_intro_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+PropertyIntroDto _$PropertyIntroDtoFromJson(Map json) => PropertyIntroDto(
+ id: (json['id'] as num).toInt(),
+ title: json['title'] as String,
+ description: json['description'] as String,
+ theme: json['theme'] as String,
+ heroImage: MediaImageDto.fromJson(
+ Map<String, dynamic>.from(json['hero_image'] as Map),
+ ),
+);
+
+Map<String, dynamic> _$PropertyIntroDtoToJson(PropertyIntroDto instance) =>
+ <String, dynamic>{
+ 'id': instance.id,
+ 'title': instance.title,
+ 'description': instance.description,
+ 'theme': instance.theme,
+ 'hero_image': instance.heroImage.toJson(),
+ };
diff --git a/concierge/lib/_generated/data/remote/models/reject_message_dto.g.dart b/concierge/lib/_generated/data/remote/models/reject_message_dto.g.dart
new file mode 100644
index 00000000..37c1e7d7
--- /dev/null
+++ b/concierge/lib/_generated/data/remote/models/reject_message_dto.g.dart
@@ -0,0 +1,15 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../data/remote/models/reject_message_dto.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+RejectMessageDto _$RejectMessageDtoFromJson(Map json) => RejectMessageDto(
+ title: json['title'] as String,
+ text: json['text'] as String,
+);
+
+Map<String, dynamic> _$RejectMessageDtoToJson(RejectMessageDto instance) =>
+ <String, dynamic>{'title': instance.title, 'text': instance.text};
diff --git a/concierge/lib/_generated/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.freezed.dart b/concierge/lib/_generated/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.freezed.dart
new file mode 100644
index 00000000..ff1a9da5
--- /dev/null
+++ b/concierge/lib/_generated/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.freezed.dart
@@ -0,0 +1,280 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// coverage:ignore-file
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of '../../../../../presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity<T>(T value) => value;
+/// @nodoc
+mixin _$HotelOverviewPageState {
+
+ bool get isLoading; String get errorMessage; Property? get property; int? get selectedAreaId;
+/// Create a copy of HotelOverviewPageState
+/// with the given fields replaced by the non-null parameter values.
+@JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+$HotelOverviewPageStateCopyWith<HotelOverviewPageState> get copyWith => _$HotelOverviewPageStateCopyWithImpl<HotelOverviewPageState>(this as HotelOverviewPageState, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is HotelOverviewPageState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.property, property) || other.property == property)&&(identical(other.selectedAreaId, selectedAreaId) || other.selectedAreaId == selectedAreaId));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,errorMessage,property,selectedAreaId);
+
+@override
+String toString() {
+ return 'HotelOverviewPageState(isLoading: $isLoading, errorMessage: $errorMessage, property: $property, selectedAreaId: $selectedAreaId)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class $HotelOverviewPageStateCopyWith<$Res> {
+ factory $HotelOverviewPageStateCopyWith(HotelOverviewPageState value, $Res Function(HotelOverviewPageState) _then) = _$HotelOverviewPageStateCopyWithImpl;
+@useResult
+$Res call({
+ bool isLoading, String errorMessage, Property? property, int? selectedAreaId
+});
+
+
+
+
+}
+/// @nodoc
+class _$HotelOverviewPageStateCopyWithImpl<$Res>
+ implements $HotelOverviewPageStateCopyWith<$Res> {
+ _$HotelOverviewPageStateCopyWithImpl(this._self, this._then);
+
+ final HotelOverviewPageState _self;
+ final $Res Function(HotelOverviewPageState) _then;
+
+/// Create a copy of HotelOverviewPageState
+/// with the given fields replaced by the non-null parameter values.
+@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? errorMessage = null,Object? property = freezed,Object? selectedAreaId = freezed,}) {
+ return _then(_self.copyWith(
+isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
+as String,property: freezed == property ? _self.property : property // ignore: cast_nullable_to_non_nullable
+as Property?,selectedAreaId: freezed == selectedAreaId ? _self.selectedAreaId : selectedAreaId // ignore: cast_nullable_to_non_nullable
+as int?,
+ ));
+}
+
+}
+
+
+/// Adds pattern-matching-related methods to [HotelOverviewPageState].
+extension HotelOverviewPageStatePatterns on HotelOverviewPageState {
+/// A variant of `map` that fallback to returning `orElse`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _HotelOverviewPageState value)? $default,{required TResult orElse(),}){
+final _that = this;
+switch (_that) {
+case _HotelOverviewPageState() when $default != null:
+return $default(_that);case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// Callbacks receives the raw object, upcasted.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case final Subclass2 value:
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _HotelOverviewPageState value) $default,){
+final _that = this;
+switch (_that) {
+case _HotelOverviewPageState():
+return $default(_that);case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `map` that fallback to returning `null`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _HotelOverviewPageState value)? $default,){
+final _that = this;
+switch (_that) {
+case _HotelOverviewPageState() when $default != null:
+return $default(_that);case _:
+ return null;
+
+}
+}
+/// A variant of `when` that fallback to an `orElse` callback.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, String errorMessage, Property? property, int? selectedAreaId)? $default,{required TResult orElse(),}) {final _that = this;
+switch (_that) {
+case _HotelOverviewPageState() when $default != null:
+return $default(_that.isLoading,_that.errorMessage,_that.property,_that.selectedAreaId);case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// As opposed to `map`, this offers destructuring.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case Subclass2(:final field2):
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, String errorMessage, Property? property, int? selectedAreaId) $default,) {final _that = this;
+switch (_that) {
+case _HotelOverviewPageState():
+return $default(_that.isLoading,_that.errorMessage,_that.property,_that.selectedAreaId);case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `when` that fallback to returning `null`
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, String errorMessage, Property? property, int? selectedAreaId)? $default,) {final _that = this;
+switch (_that) {
+case _HotelOverviewPageState() when $default != null:
+return $default(_that.isLoading,_that.errorMessage,_that.property,_that.selectedAreaId);case _:
+ return null;
+
+}
+}
+
+}
+
+/// @nodoc
+
+
+class _HotelOverviewPageState implements HotelOverviewPageState {
+ const _HotelOverviewPageState({this.isLoading = false, this.errorMessage = "", this.property, this.selectedAreaId});
+
+
+@override@JsonKey() final bool isLoading;
+@override@JsonKey() final String errorMessage;
+@override final Property? property;
+@override final int? selectedAreaId;
+
+/// Create a copy of HotelOverviewPageState
+/// with the given fields replaced by the non-null parameter values.
+@override @JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+_$HotelOverviewPageStateCopyWith<_HotelOverviewPageState> get copyWith => __$HotelOverviewPageStateCopyWithImpl<_HotelOverviewPageState>(this, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _HotelOverviewPageState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.property, property) || other.property == property)&&(identical(other.selectedAreaId, selectedAreaId) || other.selectedAreaId == selectedAreaId));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,errorMessage,property,selectedAreaId);
+
+@override
+String toString() {
+ return 'HotelOverviewPageState(isLoading: $isLoading, errorMessage: $errorMessage, property: $property, selectedAreaId: $selectedAreaId)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class _$HotelOverviewPageStateCopyWith<$Res> implements $HotelOverviewPageStateCopyWith<$Res> {
+ factory _$HotelOverviewPageStateCopyWith(_HotelOverviewPageState value, $Res Function(_HotelOverviewPageState) _then) = __$HotelOverviewPageStateCopyWithImpl;
+@override @useResult
+$Res call({
+ bool isLoading, String errorMessage, Property? property, int? selectedAreaId
+});
+
+
+
+
+}
+/// @nodoc
+class __$HotelOverviewPageStateCopyWithImpl<$Res>
+ implements _$HotelOverviewPageStateCopyWith<$Res> {
+ __$HotelOverviewPageStateCopyWithImpl(this._self, this._then);
+
+ final _HotelOverviewPageState _self;
+ final $Res Function(_HotelOverviewPageState) _then;
+
+/// Create a copy of HotelOverviewPageState
+/// with the given fields replaced by the non-null parameter values.
+@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? errorMessage = null,Object? property = freezed,Object? selectedAreaId = freezed,}) {
+ return _then(_HotelOverviewPageState(
+isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
+as String,property: freezed == property ? _self.property : property // ignore: cast_nullable_to_non_nullable
+as Property?,selectedAreaId: freezed == selectedAreaId ? _self.selectedAreaId : selectedAreaId // ignore: cast_nullable_to_non_nullable
+as int?,
+ ));
+}
+
+
+}
+
+// dart format on
diff --git a/concierge/lib/_generated/presentation/screens/hotel_overview_page/hotel_overview_page_route.g.dart b/concierge/lib/_generated/presentation/screens/hotel_overview_page/hotel_overview_page_route.g.dart
new file mode 100644
index 00000000..131325e9
--- /dev/null
+++ b/concierge/lib/_generated/presentation/screens/hotel_overview_page/hotel_overview_page_route.g.dart
@@ -0,0 +1,35 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../presentation/screens/hotel_overview_page/hotel_overview_page_route.dart';
+
+// **************************************************************************
+// GoRouterGenerator
+// **************************************************************************
+
+List<RouteBase> get $appRoutes => [$hotelOverviewPageRoute];
+
+RouteBase get $hotelOverviewPageRoute => GoRouteData.$route(
+ path: '/hotel-overview-page',
+ factory: $HotelOverviewPageRoute._fromState,
+);
+
+mixin $HotelOverviewPageRoute on GoRouteData {
+ static HotelOverviewPageRoute _fromState(GoRouterState state) =>
+ HotelOverviewPageRoute();
+
+ @override
+ String get location => GoRouteData.$location('/hotel-overview-page');
+
+ @override
+ void go(BuildContext context) => context.go(location);
+
+ @override
+ Future<T?> push<T>(BuildContext context) => context.push<T>(location);
+
+ @override
+ void pushReplacement(BuildContext context) =>
+ context.pushReplacement(location);
+
+ @override
+ void replace(BuildContext context) => context.replace(location);
+}
diff --git a/concierge/lib/concierge.dart b/concierge/lib/concierge.dart
new file mode 100644
index 00000000..0eee4d68
--- /dev/null
+++ b/concierge/lib/concierge.dart
@@ -0,0 +1,64 @@
+import 'package:flutter/widgets.dart';
+
+import 'concierge_config.dart';
+import 'concierge_platform_interface.dart';
+import 'flavors.dart';
+import 'presentation/app/concierge_hotel_overview.dart';
+
+export 'concierge_config.dart';
+export 'flavors.dart';
+export 'presentation/app/concierge_hotel_overview.dart';
+
+class Concierge {
+ Future<String?> getPlatformVersion() {
+ return ConciergePlatform.instance.getPlatformVersion();
+ }
+
+ /// Host-app entry point: build the Hotel Overview UI for a given hotel + user token.
+ ///
+ /// Typical usage from the host app:
+ ///
+ /// Using `go_router`:
+ /// ```dart
+ /// // routes:
+ /// // GoRoute(
+ /// // path: '/concierge/:hotelCode',
+ /// // builder: (context, state) => Concierge.hotelOverview(
+ /// // flavor: Flavor.stage,
+ /// // hotelCode: state.pathParameters['hotelCode']!,
+ /// // userToken: '<token>',
+ /// // ),
+ /// // ),
+ /// //
+ /// // open from an image button:
+ /// context.push('/concierge/ccp');
+ /// ```
+ ///
+ /// Using `Navigator`:
+ /// ```dart
+ /// Navigator.of(context).push(
+ /// MaterialPageRoute(
+ /// builder: (_) => Concierge.hotelOverview(
+ /// flavor: Flavor.stage,
+ /// hotelCode: "ccp",
+ /// userToken: token,
+ /// ),
+ /// ),
+ /// );
+ /// ```
+ static Widget hotelOverview({
+ required Flavor flavor,
+ required String hotelCode,
+ String? userToken,
+ }) {
+ return ConciergeHotelOverview(
+ config: ConciergeConfig(
+ flavor: flavor,
+ hotelCode: hotelCode,
+ userToken: userToken,
+ ),
+ );
+ }
+}
+
+
diff --git a/concierge/lib/concierge_config.dart b/concierge/lib/concierge_config.dart
new file mode 100644
index 00000000..908c82da
--- /dev/null
+++ b/concierge/lib/concierge_config.dart
@@ -0,0 +1,15 @@
+import 'package:concierge/flavors.dart';
+
+class ConciergeConfig {
+ final Flavor flavor;
+ final String hotelCode;
+ final String? userToken;
+
+ const ConciergeConfig({
+ required this.flavor,
+ required this.hotelCode,
+ this.userToken,
+ });
+}
+
+
diff --git a/concierge/lib/concierge_method_channel.dart b/concierge/lib/concierge_method_channel.dart
new file mode 100644
index 00000000..233b00c7
--- /dev/null
+++ b/concierge/lib/concierge_method_channel.dart
@@ -0,0 +1,17 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+
+import 'concierge_platform_interface.dart';
+
+class MethodChannelConcierge extends ConciergePlatform {
+ @visibleForTesting
+ final methodChannel = const MethodChannel('concierge');
+
+ @override
+ Future<String?> getPlatformVersion() async {
+ final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
+ return version;
+ }
+}
+
+
diff --git a/concierge/lib/concierge_platform_interface.dart b/concierge/lib/concierge_platform_interface.dart
new file mode 100644
index 00000000..82989ba6
--- /dev/null
+++ b/concierge/lib/concierge_platform_interface.dart
@@ -0,0 +1,22 @@
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+import 'concierge_method_channel.dart';
+
+abstract class ConciergePlatform extends PlatformInterface {
+ ConciergePlatform() : super(token: _token);
+
+ static final Object _token = Object();
+
+ static ConciergePlatform _instance = MethodChannelConcierge();
+
+ static ConciergePlatform get instance => _instance;
+
+ static set instance(ConciergePlatform instance) {
+ PlatformInterface.verifyToken(instance, _token);
+ _instance = instance;
+ }
+
+ Future<String?> getPlatformVersion();
+}
+
+
diff --git a/concierge/lib/data/local/secure_storage/concierge_secure_storage.dart b/concierge/lib/data/local/secure_storage/concierge_secure_storage.dart
new file mode 100644
index 00000000..47b6e80d
--- /dev/null
+++ b/concierge/lib/data/local/secure_storage/concierge_secure_storage.dart
@@ -0,0 +1,23 @@
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+
+class ConciergeSecureStorage {
+ static const _tokenKey = 'concierge_user_token';
+
+ final FlutterSecureStorage _storage;
+
+ const ConciergeSecureStorage(this._storage);
+
+ Future<void> setUserToken(String token) async {
+ await _storage.write(key: _tokenKey, value: token);
+ }
+
+ Future<String?> getUserToken() async {
+ return _storage.read(key: _tokenKey);
+ }
+
+ Future<void> clearUserToken() async {
+ await _storage.delete(key: _tokenKey);
+ }
+}
+
+
diff --git a/concierge/lib/data/remote/api/concierge_service.dart b/concierge/lib/data/remote/api/concierge_service.dart
new file mode 100644
index 00000000..302a947d
--- /dev/null
+++ b/concierge/lib/data/remote/api/concierge_service.dart
@@ -0,0 +1,38 @@
+import 'package:dio/dio.dart';
+import 'package:retrofit/retrofit.dart';
+
+import 'package:concierge/data/remote/models/get_property_response_dto.dart';
+import 'package:concierge/data/remote/models/get_area_response_dto.dart';
+
+part '../../../_generated/data/remote/api/concierge_service.g.dart';
+
+@RestApi()
+abstract class ConciergeService {
+ factory ConciergeService(Dio dio, {String? baseUrl}) = _ConciergeService;
+
+ @GET("/hotels/domains/{hotelcode}")
+ Future<GetPropertyResponseDto> getHotelOverview(
+ @Path("hotelcode") String hotelcode);
+
+ // TODO: Add DTOs/models for the endpoints below (Area/Product/Order/etc) and
+ // re-enable them.
+ //
+ @GET("/areas/{areaid}")
+ Future<GetAreaResponseDto> getArea(@Path("areaid") String areaid);
+ //
+ // @GET("/products/{productid}")
+ // Future<Product> getProduct(@Path("productid") String productid);
+ //
+ // @POST("/orders/review")
+ // Future<OrderReview> getOrderReview(@Body() OrderReviewDto orderReviewDto);
+ //
+ // @POST("/orders/")
+ // Future<Order> createOrder(@Body() OrderDto orderDto);
+ //
+ // @GET("/orders/{orderid}")
+ // Future<Order> getOrder(@Path("orderid") String orderid);
+ //
+ // @GET("/customers/me/orders")
+ // Future<List<Order>> getCustomerOrders();
+
+}
\ No newline at end of file
diff --git a/concierge/lib/data/remote/models/area_category_dto.dart b/concierge/lib/data/remote/models/area_category_dto.dart
new file mode 100644
index 00000000..feefbe61
--- /dev/null
+++ b/concierge/lib/data/remote/models/area_category_dto.dart
@@ -0,0 +1,26 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'area_sub_category_dto.dart';
+
+part '../../../_generated/data/remote/models/area_category_dto.g.dart';
+
+@JsonSerializable()
+class AreaCategoryDto {
+ final int id;
+ final String name;
+ @JsonKey(name: 'sub_categories')
+ final List<AreaSubCategoryDto> subCategories;
+
+ AreaCategoryDto({
+ required this.id,
+ required this.name,
+ required this.subCategories,
+ });
+
+ factory AreaCategoryDto.fromJson(Map<String, dynamic> json) =>
+ _$AreaCategoryDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$AreaCategoryDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/area_details_dto.dart b/concierge/lib/data/remote/models/area_details_dto.dart
new file mode 100644
index 00000000..fb06d3c4
--- /dev/null
+++ b/concierge/lib/data/remote/models/area_details_dto.dart
@@ -0,0 +1,75 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'area_category_dto.dart';
+import 'promotion_dto.dart';
+import 'property_event_dto.dart';
+
+part '../../../_generated/data/remote/models/area_details_dto.g.dart';
+
+@JsonSerializable()
+class AreaDetailsDto {
+ final int id;
+ final String name;
+ @JsonKey(name: 'pickup_text')
+ final String pickupText;
+ @JsonKey(name: 'delivery_text')
+ final String deliveryText;
+ @JsonKey(name: 'is_open')
+ final int isOpen;
+ @JsonKey(name: 'is_meeting_area')
+ final int isMeetingArea;
+ @JsonKey(name: 'is_guest_area')
+ final int isGuestArea;
+ @JsonKey(name: 'is_prearrival_area')
+ final int isPrearrivalArea;
+ final String theme;
+
+ /// Note: in some endpoints this is a string like "BED"; in others it may be a URL.
+ final String icon;
+
+ @JsonKey(name: 'delivery_price')
+ final int deliveryPrice;
+ @JsonKey(name: 'is_delivery_default')
+ final int isDeliveryDefault;
+ @JsonKey(name: 'has_delivery')
+ final int hasDelivery;
+ final List<PropertyEventDto> events;
+ @JsonKey(name: 'skip_location')
+ final bool skipLocation;
+
+ final List<AreaCategoryDto> categories;
+ final PromotionDto promotion;
+ @JsonKey(name: 'delivery_times')
+ final List<dynamic> deliveryTimes;
+ @JsonKey(name: 'support_items')
+ final List<dynamic> supportItems;
+
+ AreaDetailsDto({
+ required this.id,
+ required this.name,
+ required this.pickupText,
+ required this.deliveryText,
+ required this.isOpen,
+ required this.isMeetingArea,
+ required this.isGuestArea,
+ required this.isPrearrivalArea,
+ required this.theme,
+ required this.icon,
+ required this.deliveryPrice,
+ required this.isDeliveryDefault,
+ required this.hasDelivery,
+ required this.events,
+ required this.skipLocation,
+ required this.categories,
+ required this.promotion,
+ required this.deliveryTimes,
+ required this.supportItems,
+ });
+
+ factory AreaDetailsDto.fromJson(Map<String, dynamic> json) =>
+ _$AreaDetailsDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$AreaDetailsDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/area_sub_category_dto.dart b/concierge/lib/data/remote/models/area_sub_category_dto.dart
new file mode 100644
index 00000000..f65536bd
--- /dev/null
+++ b/concierge/lib/data/remote/models/area_sub_category_dto.dart
@@ -0,0 +1,38 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'media_image_dto.dart';
+
+part '../../../_generated/data/remote/models/area_sub_category_dto.g.dart';
+
+@JsonSerializable()
+class AreaSubCategoryDto {
+ final int id;
+ final String name;
+ @JsonKey(name: 'sub_title')
+ final String subTitle;
+ @JsonKey(name: 'hero_title')
+ final String heroTitle;
+ final String description;
+ @JsonKey(name: 'hero_image')
+ final MediaImageDto heroImage;
+
+ /// Product ids (as delivered by backend)
+ final List<int> products;
+
+ AreaSubCategoryDto({
+ required this.id,
+ required this.name,
+ required this.subTitle,
+ required this.heroTitle,
+ required this.description,
+ required this.heroImage,
+ required this.products,
+ });
+
+ factory AreaSubCategoryDto.fromJson(Map<String, dynamic> json) =>
+ _$AreaSubCategoryDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$AreaSubCategoryDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/get_area_response_dto.dart b/concierge/lib/data/remote/models/get_area_response_dto.dart
new file mode 100644
index 00000000..d245b670
--- /dev/null
+++ b/concierge/lib/data/remote/models/get_area_response_dto.dart
@@ -0,0 +1,19 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'area_details_dto.dart';
+
+part '../../../_generated/data/remote/models/get_area_response_dto.g.dart';
+
+@JsonSerializable()
+class GetAreaResponseDto {
+ final AreaDetailsDto data;
+
+ GetAreaResponseDto({required this.data});
+
+ factory GetAreaResponseDto.fromJson(Map<String, dynamic> json) =>
+ _$GetAreaResponseDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$GetAreaResponseDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/get_property_response_dto.dart b/concierge/lib/data/remote/models/get_property_response_dto.dart
new file mode 100644
index 00000000..34467b47
--- /dev/null
+++ b/concierge/lib/data/remote/models/get_property_response_dto.dart
@@ -0,0 +1,19 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'property_dto.dart';
+
+part '../../../_generated/data/remote/models/get_property_response_dto.g.dart';
+
+@JsonSerializable()
+class GetPropertyResponseDto {
+ final PropertyDto data;
+
+ GetPropertyResponseDto({required this.data});
+
+ factory GetPropertyResponseDto.fromJson(Map<String, dynamic> json) =>
+ _$GetPropertyResponseDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$GetPropertyResponseDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/media_image_dto.dart b/concierge/lib/data/remote/models/media_image_dto.dart
new file mode 100644
index 00000000..1dbd37ce
--- /dev/null
+++ b/concierge/lib/data/remote/models/media_image_dto.dart
@@ -0,0 +1,28 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part '../../../_generated/data/remote/models/media_image_dto.g.dart';
+
+@JsonSerializable()
+class MediaImageDto {
+ final String url;
+ final String preload;
+ final String thumbnail;
+ @JsonKey(name: 'product_hero')
+ final String productHero;
+ final String alt;
+
+ MediaImageDto({
+ required this.url,
+ required this.preload,
+ required this.thumbnail,
+ required this.productHero,
+ required this.alt,
+ });
+
+ factory MediaImageDto.fromJson(Map<String, dynamic> json) =>
+ _$MediaImageDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$MediaImageDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/promotion_dto.dart b/concierge/lib/data/remote/models/promotion_dto.dart
new file mode 100644
index 00000000..08306ff0
--- /dev/null
+++ b/concierge/lib/data/remote/models/promotion_dto.dart
@@ -0,0 +1,20 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part '../../../_generated/data/remote/models/promotion_dto.g.dart';
+
+@JsonSerializable()
+class PromotionDto {
+ final String title;
+
+ /// Product ids (often empty in response examples).
+ final List<int> products;
+
+ PromotionDto({required this.title, required this.products});
+
+ factory PromotionDto.fromJson(Map<String, dynamic> json) =>
+ _$PromotionDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$PromotionDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/property_area_dto.dart b/concierge/lib/data/remote/models/property_area_dto.dart
new file mode 100644
index 00000000..d02945d6
--- /dev/null
+++ b/concierge/lib/data/remote/models/property_area_dto.dart
@@ -0,0 +1,59 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'property_event_dto.dart';
+
+part '../../../_generated/data/remote/models/property_area_dto.g.dart';
+
+@JsonSerializable()
+class PropertyAreaDto {
+ final int id;
+ final String name;
+ @JsonKey(name: 'pickup_text')
+ final String pickupText;
+ @JsonKey(name: 'delivery_text')
+ final String deliveryText;
+ @JsonKey(name: 'is_open')
+ final int isOpen;
+ @JsonKey(name: 'is_meeting_area')
+ final int isMeetingArea;
+ @JsonKey(name: 'is_guest_area')
+ final int isGuestArea;
+ @JsonKey(name: 'is_prearrival_area')
+ final int isPrearrivalArea;
+ final String theme;
+ final String icon;
+ @JsonKey(name: 'delivery_price')
+ final int? deliveryPrice;
+ @JsonKey(name: 'is_delivery_default')
+ final int isDeliveryDefault;
+ @JsonKey(name: 'has_delivery')
+ final int hasDelivery;
+ final List<PropertyEventDto> events;
+ @JsonKey(name: 'skip_location')
+ final bool skipLocation;
+
+ PropertyAreaDto({
+ required this.id,
+ required this.name,
+ required this.pickupText,
+ required this.deliveryText,
+ required this.isOpen,
+ required this.isMeetingArea,
+ required this.isGuestArea,
+ required this.isPrearrivalArea,
+ required this.theme,
+ required this.icon,
+ required this.deliveryPrice,
+ required this.isDeliveryDefault,
+ required this.hasDelivery,
+ required this.events,
+ required this.skipLocation,
+ });
+
+ factory PropertyAreaDto.fromJson(Map<String, dynamic> json) =>
+ _$PropertyAreaDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$PropertyAreaDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/property_dto.dart b/concierge/lib/data/remote/models/property_dto.dart
new file mode 100644
index 00000000..8e9a0eb1
--- /dev/null
+++ b/concierge/lib/data/remote/models/property_dto.dart
@@ -0,0 +1,61 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'media_image_dto.dart';
+import 'property_area_dto.dart';
+import 'property_intro_dto.dart';
+import 'reject_message_dto.dart';
+
+part '../../../_generated/data/remote/models/property_dto.g.dart';
+
+@JsonSerializable()
+class PropertyDto {
+ final int id;
+ final String name;
+ final String domain;
+ @JsonKey(name: 'language_code')
+ final String languageCode;
+ final String currency;
+ final String hotline;
+ @JsonKey(name: 'show_in_menu')
+ final int showInMenu;
+ @JsonKey(name: 'help_body')
+ final String helpBody;
+ @JsonKey(name: 'splash_video')
+ final String? splashVideo;
+ final List<PropertyAreaDto> areas;
+ @JsonKey(name: 'hero_images')
+ final List<MediaImageDto> heroImages;
+ @JsonKey(name: 'logo_image')
+ final MediaImageDto logoImage;
+ @JsonKey(name: 'profile_image')
+ final MediaImageDto profileImage;
+ final List<PropertyIntroDto> intros;
+ @JsonKey(name: 'menu_items')
+ final List<dynamic> menuItems;
+ @JsonKey(name: 'reject_messages')
+ final List<RejectMessageDto> rejectMessages;
+
+ PropertyDto({
+ required this.id,
+ required this.name,
+ required this.domain,
+ required this.languageCode,
+ required this.currency,
+ required this.hotline,
+ required this.showInMenu,
+ required this.helpBody,
+ required this.splashVideo,
+ required this.areas,
+ required this.heroImages,
+ required this.logoImage,
+ required this.profileImage,
+ required this.intros,
+ required this.menuItems,
+ required this.rejectMessages,
+ });
+
+ factory PropertyDto.fromJson(Map<String, dynamic> json) =>
+ _$PropertyDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$PropertyDtoToJson(this);
+}
diff --git a/concierge/lib/data/remote/models/property_event_dto.dart b/concierge/lib/data/remote/models/property_event_dto.dart
new file mode 100644
index 00000000..df1c381d
--- /dev/null
+++ b/concierge/lib/data/remote/models/property_event_dto.dart
@@ -0,0 +1,32 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'media_image_dto.dart';
+
+part '../../../_generated/data/remote/models/property_event_dto.g.dart';
+
+@JsonSerializable()
+class PropertyEventDto {
+ final int id;
+ final String title;
+ @JsonKey(name: 'sub_title')
+ final String subTitle;
+ @JsonKey(name: 'cta_link')
+ final String? ctaLink;
+ @JsonKey(name: 'hero_image')
+ final MediaImageDto heroImage;
+
+ PropertyEventDto({
+ required this.id,
+ required this.title,
+ required this.subTitle,
+ required this.ctaLink,
+ required this.heroImage,
+ });
+
+ factory PropertyEventDto.fromJson(Map<String, dynamic> json) =>
+ _$PropertyEventDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$PropertyEventDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/property_intro_dto.dart b/concierge/lib/data/remote/models/property_intro_dto.dart
new file mode 100644
index 00000000..2da277c2
--- /dev/null
+++ b/concierge/lib/data/remote/models/property_intro_dto.dart
@@ -0,0 +1,30 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'media_image_dto.dart';
+
+part '../../../_generated/data/remote/models/property_intro_dto.g.dart';
+
+@JsonSerializable()
+class PropertyIntroDto {
+ final int id;
+ final String title;
+ final String description;
+ final String theme;
+ @JsonKey(name: 'hero_image')
+ final MediaImageDto heroImage;
+
+ PropertyIntroDto({
+ required this.id,
+ required this.title,
+ required this.description,
+ required this.theme,
+ required this.heroImage,
+ });
+
+ factory PropertyIntroDto.fromJson(Map<String, dynamic> json) =>
+ _$PropertyIntroDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$PropertyIntroDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/remote/models/reject_message_dto.dart b/concierge/lib/data/remote/models/reject_message_dto.dart
new file mode 100644
index 00000000..ca0a36b6
--- /dev/null
+++ b/concierge/lib/data/remote/models/reject_message_dto.dart
@@ -0,0 +1,18 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part '../../../_generated/data/remote/models/reject_message_dto.g.dart';
+
+@JsonSerializable()
+class RejectMessageDto {
+ final String title;
+ final String text;
+
+ RejectMessageDto({required this.title, required this.text});
+
+ factory RejectMessageDto.fromJson(Map<String, dynamic> json) =>
+ _$RejectMessageDtoFromJson(json);
+
+ Map<String, dynamic> toJson() => _$RejectMessageDtoToJson(this);
+}
+
+
diff --git a/concierge/lib/data/repositories/property_repository_impl.dart b/concierge/lib/data/repositories/property_repository_impl.dart
new file mode 100644
index 00000000..cf4a97f1
--- /dev/null
+++ b/concierge/lib/data/repositories/property_repository_impl.dart
@@ -0,0 +1,18 @@
+import 'package:concierge/data/remote/api/concierge_service.dart';
+import 'package:concierge/domain/mappers/property_mapper.dart';
+import 'package:concierge/domain/models/property.dart';
+import 'package:concierge/domain/repositories/property_repository.dart';
+
+class PropertyRepositoryImpl implements PropertyRepository {
+ final ConciergeService _service;
+
+ PropertyRepositoryImpl(this._service);
+
+ @override
+ Future<Property> getHotelOverview({required String hotelCode}) async {
+ final response = await _service.getHotelOverview(hotelCode);
+ return response.toProperty();
+ }
+}
+
+
diff --git a/concierge/lib/domain/mappers/area_details_mapper.dart b/concierge/lib/domain/mappers/area_details_mapper.dart
new file mode 100644
index 00000000..0339c7de
--- /dev/null
+++ b/concierge/lib/domain/mappers/area_details_mapper.dart
@@ -0,0 +1,99 @@
+import 'package:concierge/data/remote/models/area_category_dto.dart';
+import 'package:concierge/data/remote/models/area_details_dto.dart';
+import 'package:concierge/data/remote/models/area_sub_category_dto.dart';
+import 'package:concierge/data/remote/models/get_area_response_dto.dart';
+import 'package:concierge/data/remote/models/media_image_dto.dart';
+import 'package:concierge/data/remote/models/promotion_dto.dart';
+import 'package:concierge/data/remote/models/property_event_dto.dart';
+import 'package:concierge/domain/models/area_category.dart';
+import 'package:concierge/domain/models/area_details.dart';
+import 'package:concierge/domain/models/area_sub_category.dart';
+import 'package:concierge/domain/models/media_image.dart';
+import 'package:concierge/domain/models/promotion.dart';
+import 'package:concierge/domain/models/property_event.dart';
+
+bool _intFlagToBool(int value) => value == 1;
+
+extension GetAreaResponseDtoMapper on GetAreaResponseDto {
+ AreaDetails toAreaDetails() => data.toAreaDetails();
+}
+
+extension AreaDetailsDtoMapper on AreaDetailsDto {
+ AreaDetails toAreaDetails() {
+ return AreaDetails(
+ id: id,
+ name: name,
+ pickupText: pickupText,
+ deliveryText: deliveryText,
+ isOpen: _intFlagToBool(isOpen),
+ isMeetingArea: _intFlagToBool(isMeetingArea),
+ isGuestArea: _intFlagToBool(isGuestArea),
+ isPrearrivalArea: _intFlagToBool(isPrearrivalArea),
+ theme: theme,
+ icon: icon,
+ deliveryPrice: deliveryPrice,
+ isDeliveryDefault: _intFlagToBool(isDeliveryDefault),
+ hasDelivery: _intFlagToBool(hasDelivery),
+ events: events.map((e) => e.toPropertyEvent()).toList(),
+ skipLocation: skipLocation,
+ categories: categories.map((c) => c.toAreaCategory()).toList(),
+ promotion: promotion.toPromotion(),
+ deliveryTimes: deliveryTimes,
+ supportItems: supportItems,
+ );
+ }
+}
+
+extension AreaCategoryDtoMapper on AreaCategoryDto {
+ AreaCategory toAreaCategory() {
+ return AreaCategory(
+ id: id,
+ name: name,
+ subCategories: subCategories.map((s) => s.toAreaSubCategory()).toList(),
+ );
+ }
+}
+
+extension AreaSubCategoryDtoMapper on AreaSubCategoryDto {
+ AreaSubCategory toAreaSubCategory() {
+ return AreaSubCategory(
+ id: id,
+ name: name,
+ subTitle: subTitle,
+ heroTitle: heroTitle,
+ description: description,
+ heroImage: heroImage.toMediaImage(),
+ products: products,
+ );
+ }
+}
+
+extension PromotionDtoMapper on PromotionDto {
+ Promotion toPromotion() => Promotion(title: title, products: products);
+}
+
+extension PropertyEventDtoMapper on PropertyEventDto {
+ PropertyEvent toPropertyEvent() {
+ return PropertyEvent(
+ id: id,
+ title: title,
+ subTitle: subTitle,
+ ctaLink: ctaLink,
+ heroImage: heroImage.toMediaImage(),
+ );
+ }
+}
+
+extension MediaImageDtoMapper on MediaImageDto {
+ MediaImage toMediaImage() {
+ return MediaImage(
+ url: url,
+ preload: preload,
+ thumbnail: thumbnail,
+ productHero: productHero,
+ alt: alt,
+ );
+ }
+}
+
+
diff --git a/concierge/lib/domain/mappers/property_mapper.dart b/concierge/lib/domain/mappers/property_mapper.dart
new file mode 100644
index 00000000..9d5722df
--- /dev/null
+++ b/concierge/lib/domain/mappers/property_mapper.dart
@@ -0,0 +1,106 @@
+import 'package:concierge/data/remote/models/get_property_response_dto.dart';
+import 'package:concierge/data/remote/models/media_image_dto.dart';
+import 'package:concierge/data/remote/models/property_dto.dart';
+import 'package:concierge/data/remote/models/property_area_dto.dart';
+import 'package:concierge/data/remote/models/property_event_dto.dart';
+import 'package:concierge/data/remote/models/property_intro_dto.dart';
+import 'package:concierge/data/remote/models/reject_message_dto.dart';
+import 'package:concierge/domain/models/media_image.dart';
+import 'package:concierge/domain/models/property.dart';
+import 'package:concierge/domain/models/property_area.dart';
+import 'package:concierge/domain/models/property_event.dart';
+import 'package:concierge/domain/models/property_intro.dart';
+import 'package:concierge/domain/models/reject_message.dart';
+
+bool _intFlagToBool(int value) => value == 1;
+
+extension GetPropertyResponseDtoMapper on GetPropertyResponseDto {
+ Property toProperty() => data.toProperty();
+}
+
+extension PropertyDtoMapper on PropertyDto {
+ Property toProperty() {
+ return Property(
+ id: id,
+ name: name,
+ domain: domain,
+ languageCode: languageCode,
+ currency: currency,
+ hotline: hotline,
+ showInMenu: _intFlagToBool(showInMenu),
+ helpBody: helpBody,
+ splashVideo: splashVideo,
+ areas: areas.map((a) => a.toPropertyArea()).toList(),
+ heroImages: heroImages.map((i) => i.toMediaImage()).toList(),
+ logoImage: logoImage.toMediaImage(),
+ profileImage: profileImage.toMediaImage(),
+ intros: intros.map((i) => i.toPropertyIntro()).toList(),
+ menuItems: menuItems,
+ rejectMessages: rejectMessages.map((m) => m.toRejectMessage()).toList(),
+ );
+ }
+}
+
+extension PropertyAreaDtoMapper on PropertyAreaDto {
+ PropertyArea toPropertyArea() {
+ return PropertyArea(
+ id: id,
+ name: name,
+ pickupText: pickupText,
+ deliveryText: deliveryText,
+ isOpen: _intFlagToBool(isOpen),
+ isMeetingArea: _intFlagToBool(isMeetingArea),
+ isGuestArea: _intFlagToBool(isGuestArea),
+ isPrearrivalArea: _intFlagToBool(isPrearrivalArea),
+ theme: theme,
+ icon: icon,
+ deliveryPrice: deliveryPrice ?? 0,
+ isDeliveryDefault: _intFlagToBool(isDeliveryDefault),
+ hasDelivery: _intFlagToBool(hasDelivery),
+ events: events.map((e) => e.toPropertyEvent()).toList(),
+ skipLocation: skipLocation,
+ );
+ }
+}
+
+extension PropertyEventDtoMapper on PropertyEventDto {
+ PropertyEvent toPropertyEvent() {
+ return PropertyEvent(
+ id: id,
+ title: title,
+ subTitle: subTitle,
+ ctaLink: ctaLink,
+ heroImage: heroImage.toMediaImage(),
+ );
+ }
+}
+
+extension PropertyIntroDtoMapper on PropertyIntroDto {
+ PropertyIntro toPropertyIntro() {
+ return PropertyIntro(
+ id: id,
+ title: title,
+ description: description,
+ theme: theme,
+ heroImage: heroImage.toMediaImage(),
+ );
+ }
+}
+
+extension RejectMessageDtoMapper on RejectMessageDto {
+ RejectMessage toRejectMessage() => RejectMessage(title: title, text: text);
+}
+
+extension MediaImageDtoMapper on MediaImageDto {
+ MediaImage toMediaImage() {
+ return MediaImage(
+ url: url,
+ preload: preload,
+ thumbnail: thumbnail,
+ productHero: productHero,
+ alt: alt,
+ );
+ }
+}
+
+
diff --git a/concierge/lib/domain/models/area_category.dart b/concierge/lib/domain/models/area_category.dart
new file mode 100644
index 00000000..f813a6c5
--- /dev/null
+++ b/concierge/lib/domain/models/area_category.dart
@@ -0,0 +1,15 @@
+import 'area_sub_category.dart';
+
+class AreaCategory {
+ final int id;
+ final String name;
+ final List<AreaSubCategory> subCategories;
+
+ AreaCategory({
+ required this.id,
+ required this.name,
+ required this.subCategories,
+ });
+}
+
+
diff --git a/concierge/lib/domain/models/area_details.dart b/concierge/lib/domain/models/area_details.dart
new file mode 100644
index 00000000..20682cdc
--- /dev/null
+++ b/concierge/lib/domain/models/area_details.dart
@@ -0,0 +1,50 @@
+import 'package:concierge/domain/models/property_event.dart';
+
+import 'area_category.dart';
+import 'promotion.dart';
+
+class AreaDetails {
+ final int id;
+ final String name;
+ final String pickupText;
+ final String deliveryText;
+ final bool isOpen;
+ final bool isMeetingArea;
+ final bool isGuestArea;
+ final bool isPrearrivalArea;
+ final String theme;
+ final String icon;
+ final int deliveryPrice;
+ final bool isDeliveryDefault;
+ final bool hasDelivery;
+ final List<PropertyEvent> events;
+ final bool skipLocation;
+ final List<AreaCategory> categories;
+ final Promotion promotion;
+ final List<dynamic> deliveryTimes;
+ final List<dynamic> supportItems;
+
+ AreaDetails({
+ required this.id,
+ required this.name,
+ required this.pickupText,
+ required this.deliveryText,
+ required this.isOpen,
+ required this.isMeetingArea,
+ required this.isGuestArea,
+ required this.isPrearrivalArea,
+ required this.theme,
+ required this.icon,
+ required this.deliveryPrice,
+ required this.isDeliveryDefault,
+ required this.hasDelivery,
+ required this.events,
+ required this.skipLocation,
+ required this.categories,
+ required this.promotion,
+ required this.deliveryTimes,
+ required this.supportItems,
+ });
+}
+
+
diff --git a/concierge/lib/domain/models/area_sub_category.dart b/concierge/lib/domain/models/area_sub_category.dart
new file mode 100644
index 00000000..f171cf1c
--- /dev/null
+++ b/concierge/lib/domain/models/area_sub_category.dart
@@ -0,0 +1,23 @@
+import 'media_image.dart';
+
+class AreaSubCategory {
+ final int id;
+ final String name;
+ final String subTitle;
+ final String heroTitle;
+ final String description;
+ final MediaImage heroImage;
+ final List<int> products;
+
+ AreaSubCategory({
+ required this.id,
+ required this.name,
+ required this.subTitle,
+ required this.heroTitle,
+ required this.description,
+ required this.heroImage,
+ required this.products,
+ });
+}
+
+
diff --git a/concierge/lib/domain/models/media_image.dart b/concierge/lib/domain/models/media_image.dart
new file mode 100644
index 00000000..df940ae1
--- /dev/null
+++ b/concierge/lib/domain/models/media_image.dart
@@ -0,0 +1,17 @@
+class MediaImage {
+ final String url;
+ final String preload;
+ final String thumbnail;
+ final String productHero;
+ final String alt;
+
+ MediaImage({
+ required this.url,
+ required this.preload,
+ required this.thumbnail,
+ required this.productHero,
+ required this.alt,
+ });
+}
+
+
diff --git a/concierge/lib/domain/models/promotion.dart b/concierge/lib/domain/models/promotion.dart
new file mode 100644
index 00000000..83aca927
--- /dev/null
+++ b/concierge/lib/domain/models/promotion.dart
@@ -0,0 +1,8 @@
+class Promotion {
+ final String title;
+ final List<int> products;
+
+ Promotion({required this.title, required this.products});
+}
+
+
diff --git a/concierge/lib/domain/models/property.dart b/concierge/lib/domain/models/property.dart
new file mode 100644
index 00000000..19f4ae60
--- /dev/null
+++ b/concierge/lib/domain/models/property.dart
@@ -0,0 +1,42 @@
+import 'media_image.dart';
+import 'property_area.dart';
+import 'property_intro.dart';
+import 'reject_message.dart';
+
+class Property {
+ final int id;
+ final String name;
+ final String domain;
+ final String languageCode;
+ final String currency;
+ final String hotline;
+ final bool showInMenu;
+ final String helpBody;
+ final String? splashVideo;
+ final List<PropertyArea> areas;
+ final List<MediaImage> heroImages;
+ final MediaImage logoImage;
+ final MediaImage profileImage;
+ final List<PropertyIntro> intros;
+ final List<dynamic> menuItems;
+ final List<RejectMessage> rejectMessages;
+
+ Property({
+ required this.id,
+ required this.name,
+ required this.domain,
+ required this.languageCode,
+ required this.currency,
+ required this.hotline,
+ required this.showInMenu,
+ required this.helpBody,
+ required this.splashVideo,
+ required this.areas,
+ required this.heroImages,
+ required this.logoImage,
+ required this.profileImage,
+ required this.intros,
+ required this.menuItems,
+ required this.rejectMessages,
+ });
+}
diff --git a/concierge/lib/domain/models/property_area.dart b/concierge/lib/domain/models/property_area.dart
new file mode 100644
index 00000000..95b7f014
--- /dev/null
+++ b/concierge/lib/domain/models/property_area.dart
@@ -0,0 +1,39 @@
+import 'property_event.dart';
+
+class PropertyArea {
+ final int id;
+ final String name;
+ final String pickupText;
+ final String deliveryText;
+ final bool isOpen;
+ final bool isMeetingArea;
+ final bool isGuestArea;
+ final bool isPrearrivalArea;
+ final String theme;
+ final String icon;
+ final int deliveryPrice;
+ final bool isDeliveryDefault;
+ final bool hasDelivery;
+ final List<PropertyEvent> events;
+ final bool skipLocation;
+
+ PropertyArea({
+ required this.id,
+ required this.name,
+ required this.pickupText,
+ required this.deliveryText,
+ required this.isOpen,
+ required this.isMeetingArea,
+ required this.isGuestArea,
+ required this.isPrearrivalArea,
+ required this.theme,
+ required this.icon,
+ required this.deliveryPrice,
+ required this.isDeliveryDefault,
+ required this.hasDelivery,
+ required this.events,
+ required this.skipLocation,
+ });
+}
+
+
diff --git a/concierge/lib/domain/models/property_event.dart b/concierge/lib/domain/models/property_event.dart
new file mode 100644
index 00000000..19b08d4c
--- /dev/null
+++ b/concierge/lib/domain/models/property_event.dart
@@ -0,0 +1,19 @@
+import 'media_image.dart';
+
+class PropertyEvent {
+ final int id;
+ final String title;
+ final String subTitle;
+ final String? ctaLink;
+ final MediaImage heroImage;
+
+ PropertyEvent({
+ required this.id,
+ required this.title,
+ required this.subTitle,
+ required this.ctaLink,
+ required this.heroImage,
+ });
+}
+
+
diff --git a/concierge/lib/domain/models/property_intro.dart b/concierge/lib/domain/models/property_intro.dart
new file mode 100644
index 00000000..c1433a6c
--- /dev/null
+++ b/concierge/lib/domain/models/property_intro.dart
@@ -0,0 +1,19 @@
+import 'media_image.dart';
+
+class PropertyIntro {
+ final int id;
+ final String title;
+ final String description;
+ final String theme;
+ final MediaImage heroImage;
+
+ PropertyIntro({
+ required this.id,
+ required this.title,
+ required this.description,
+ required this.theme,
+ required this.heroImage,
+ });
+}
+
+
diff --git a/concierge/lib/domain/models/reject_message.dart b/concierge/lib/domain/models/reject_message.dart
new file mode 100644
index 00000000..af1492e0
--- /dev/null
+++ b/concierge/lib/domain/models/reject_message.dart
@@ -0,0 +1,8 @@
+class RejectMessage {
+ final String title;
+ final String text;
+
+ RejectMessage({required this.title, required this.text});
+}
+
+
diff --git a/concierge/lib/domain/repositories/property_repository.dart b/concierge/lib/domain/repositories/property_repository.dart
new file mode 100644
index 00000000..48709c78
--- /dev/null
+++ b/concierge/lib/domain/repositories/property_repository.dart
@@ -0,0 +1,7 @@
+import 'package:concierge/domain/models/property.dart';
+
+abstract class PropertyRepository {
+ Future<Property> getHotelOverview({required String hotelCode});
+}
+
+
diff --git a/concierge/lib/flavors.dart b/concierge/lib/flavors.dart
new file mode 100644
index 00000000..3ffa0e21
--- /dev/null
+++ b/concierge/lib/flavors.dart
@@ -0,0 +1,28 @@
+enum Flavor {
+ stage,
+ production,
+}
+
+class F {
+ static late final Flavor appFlavor;
+
+ static String get name => appFlavor.name;
+
+ static String get title {
+ switch (appFlavor) {
+ case Flavor.stage:
+ return 'LuxPlus [DEV]';
+ case Flavor.production:
+ return 'Luxplus';
+ }
+ }
+
+ static String get dotEnvFileName {
+ switch (appFlavor) {
+ case Flavor.stage:
+ return "dotenv/.stage.env";
+ case Flavor.production:
+ return "dotenv/.production.env";
+ }
+ }
+}
diff --git a/concierge/lib/presentation/app/concierge_hotel_overview.dart b/concierge/lib/presentation/app/concierge_hotel_overview.dart
new file mode 100644
index 00000000..40a6e10e
--- /dev/null
+++ b/concierge/lib/presentation/app/concierge_hotel_overview.dart
@@ -0,0 +1,42 @@
+import 'package:concierge/concierge_config.dart';
+import 'package:concierge/data/local/secure_storage/concierge_secure_storage.dart';
+import 'package:concierge/di/getit.dart';
+import 'package:concierge/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart';
+import 'package:concierge/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+/// Entry widget to embed the Concierge hotel overview screen in a host app.
+/// The host app must pass [hotelCode] and [userToken] (and baseUrl) when opening the plugin.
+class ConciergeHotelOverview extends StatefulWidget {
+ final ConciergeConfig config;
+
+ const ConciergeHotelOverview({super.key, required this.config});
+
+ @override
+ State<ConciergeHotelOverview> createState() => _ConciergeHotelOverviewState();
+}
+
+class _ConciergeHotelOverviewState extends State<ConciergeHotelOverview> {
+ @override
+ void initState() {
+ super.initState();
+ setupDependencies(widget.config);
+
+ // Token is NOT used for Concierge API calls; we only persist it for later use.
+ final token = widget.config.userToken;
+ if (token != null && token.isNotEmpty) {
+ getIt<ConciergeSecureStorage>().setUserToken(token);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocProvider(
+ create: (_) => HotelOverviewPageCubit(),
+ child: const HotelOverviewPageScreen(),
+ );
+ }
+}
+
+
diff --git a/concierge/lib/presentation/base/base_cubit.dart b/concierge/lib/presentation/base/base_cubit.dart
new file mode 100644
index 00000000..8607809b
--- /dev/null
+++ b/concierge/lib/presentation/base/base_cubit.dart
@@ -0,0 +1,15 @@
+import 'package:bloc/bloc.dart';
+import 'package:flutter/foundation.dart';
+
+abstract class BaseCubit<T> extends Cubit<T> {
+ BaseCubit(super.initialState);
+
+ @protected
+ void handleError(Object e, StackTrace st) {
+ if (kDebugMode) print("qqq error: $e, stackTrace: $st");
+ }
+
+ void safeEmit(T state) {
+ if (!isClosed) emit(state);
+ }
+}
diff --git a/concierge/lib/presentation/navigation/app_routes.dart b/concierge/lib/presentation/navigation/app_routes.dart
new file mode 100644
index 00000000..9a467268
--- /dev/null
+++ b/concierge/lib/presentation/navigation/app_routes.dart
@@ -0,0 +1,7 @@
+class AppRoutes {
+ const AppRoutes._();
+
+
+ static const hotelOverviewPage = "/";
+
+}
\ No newline at end of file
diff --git a/concierge/lib/presentation/navigation/router.dart b/concierge/lib/presentation/navigation/router.dart
new file mode 100644
index 00000000..9a92725b
--- /dev/null
+++ b/concierge/lib/presentation/navigation/router.dart
@@ -0,0 +1,28 @@
+import 'package:concierge/presentation/navigation/app_routes.dart';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+import 'package:go_router/go_router.dart';
+
+import '../screens/hotel_overview_page/hotel_overview_page_route.dart';
+
+final rootNavigatorKey = GlobalKey<NavigatorState>();
+final bottomNavNavigatorKey = GlobalKey<NavigatorState>();
+final filterNavigatorKey = GlobalKey<NavigatorState>();
+
+
+
+final router = GoRouter(
+ navigatorKey: rootNavigatorKey,
+ observers:[],
+ initialLocation: AppRoutes.hotelOverviewPage,
+ debugLogDiagnostics: kDebugMode,
+ redirect: (context, state) {
+ final uri = state.uri;
+ debugPrint("qqq uri=$uri");
+ return null;
+ },
+ routes: [
+ $hotelOverviewPageRoute,
+ ],
+);
\ No newline at end of file
diff --git a/concierge/lib/presentation/navigation/transitions/slide_in_transition.dart b/concierge/lib/presentation/navigation/transitions/slide_in_transition.dart
new file mode 100644
index 00000000..9e2a9326
--- /dev/null
+++ b/concierge/lib/presentation/navigation/transitions/slide_in_transition.dart
@@ -0,0 +1,58 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+Page slideInTransition({required GoRouterState state, required Widget child, bool withEnterAnimation = true, bool withExitAnimation = true}) {
+ return CustomTransitionPage(
+ key: state.pageKey,
+ child: child,
+ transitionsBuilder: (context, enterAnimation, exitAnimation, child) {
+ final curvedEnterAnimation = CurvedAnimation(parent: enterAnimation, curve: Curves.easeInOutCubic);
+ final curvedExitAnimation = CurvedAnimation(parent: exitAnimation, curve: Curves.easeInOutCubic);
+
+ Widget result = child;
+
+ // Exit animation (when a new page is pushed on top of this one)
+ if (withExitAnimation) {
+ result = SlideTransition(
+ position: Tween<Offset>(
+ begin: Offset.zero,
+ end: const Offset(-1.0, 0.0),
+ ).animate(curvedExitAnimation),
+ child: result,
+ );
+ }
+
+ // Enter animation (when this page enters or when going back from this page)
+ if (withEnterAnimation) {
+ result = SlideTransition(
+ position: Tween<Offset>(
+ begin: const Offset(1.0, 0.0),
+ end: Offset.zero,
+ ).animate(curvedEnterAnimation),
+ child: result,
+ );
+ } else {
+ // Only animate when going back (reverse), not when entering (forward)
+ result = AnimatedBuilder(
+ animation: enterAnimation,
+ builder: (context, animatedChild) {
+ if (enterAnimation.status == AnimationStatus.reverse) {
+ final offset = Tween<Offset>(
+ begin: const Offset(1.0, 0.0),
+ end: Offset.zero,
+ ).evaluate(curvedEnterAnimation);
+ return FractionalTranslation(
+ translation: offset,
+ child: animatedChild,
+ );
+ }
+ return animatedChild!;
+ },
+ child: result,
+ );
+ }
+
+ return result;
+ },
+ );
+}
\ No newline at end of file
diff --git a/concierge/lib/presentation/navigation/transitions/slide_up_transition.dart b/concierge/lib/presentation/navigation/transitions/slide_up_transition.dart
new file mode 100644
index 00000000..1b25403f
--- /dev/null
+++ b/concierge/lib/presentation/navigation/transitions/slide_up_transition.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+Page slideUpTransition({
+ required GoRouterState state,
+ required Widget child,
+}) {
+ return CustomTransitionPage(
+ key: state.pageKey,
+ transitionDuration: Duration(milliseconds: 300),
+ child: child,
+ transitionsBuilder: (context, animation, secondaryAnimation, child) {
+ return SlideTransition(
+ position: Tween<Offset>(
+ begin: const Offset(0, 1),
+ end: Offset.zero,
+ ).animate(animation),
+ child: child,
+ );
+ },
+ );
+}
diff --git a/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart b/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart
new file mode 100644
index 00000000..6dc2dd69
--- /dev/null
+++ b/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart
@@ -0,0 +1,34 @@
+import 'package:concierge/presentation/base/base_cubit.dart';
+import 'package:concierge/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart';
+import 'package:concierge/di/getit.dart';
+import 'package:concierge/concierge_config.dart';
+import 'package:concierge/domain/repositories/property_repository.dart';
+
+class HotelOverviewPageCubit extends BaseCubit<HotelOverviewPageState> {
+ HotelOverviewPageCubit() : super(const HotelOverviewPageState()) {
+ init();
+ }
+
+ Future<void> init() async {
+ safeEmit(state.copyWith(isLoading: true, errorMessage: ""));
+ try {
+ final repo = getIt<PropertyRepository>();
+ final config = getIt<ConciergeConfig>();
+ final property = await repo.getHotelOverview(hotelCode: config.hotelCode);
+ final firstAreaId =
+ property.areas.isNotEmpty ? property.areas.first.id : null;
+ safeEmit(state.copyWith(
+ isLoading: false,
+ property: property,
+ selectedAreaId: firstAreaId,
+ ));
+ } catch (e, st) {
+ handleError(e, st);
+ safeEmit(state.copyWith(isLoading: false, errorMessage: e.toString()));
+ }
+ }
+
+ void selectAreaId(int areaId) {
+ safeEmit(state.copyWith(selectedAreaId: areaId));
+ }
+}
diff --git a/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart b/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart
new file mode 100644
index 00000000..4ace40a1
--- /dev/null
+++ b/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart
@@ -0,0 +1,15 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+import 'package:concierge/domain/models/property.dart';
+
+part '../../../../_generated/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.freezed.dart';
+
+@freezed
+abstract class HotelOverviewPageState with _$HotelOverviewPageState {
+ const factory HotelOverviewPageState({
+ @Default(false) bool isLoading,
+ @Default("") String errorMessage,
+ Property? property,
+ int? selectedAreaId,
+ }) = _HotelOverviewPageState;
+}
\ No newline at end of file
diff --git a/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_route.dart b/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_route.dart
new file mode 100644
index 00000000..801b704c
--- /dev/null
+++ b/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_route.dart
@@ -0,0 +1,26 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:concierge/presentation/navigation/app_routes.dart';
+import 'package:go_router/go_router.dart';
+import 'package:concierge/presentation/navigation/transitions/slide_up_transition.dart';
+import 'package:concierge/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart';
+import 'package:concierge/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart';
+
+part '../../../_generated/presentation/screens/hotel_overview_page/hotel_overview_page_route.g.dart';
+
+@TypedGoRoute<HotelOverviewPageRoute>(
+ path: "/hotel-overview-page", // add me to AppRoutes: static const hotelOverviewPage = "/hotel-overview-page";
+)
+class HotelOverviewPageRoute extends GoRouteData with $HotelOverviewPageRoute {
+
+ @override
+ Page<void> buildPage(BuildContext context, GoRouterState state) {
+ return slideUpTransition(
+ state: state,
+ child: BlocProvider(
+ create: (context) => HotelOverviewPageCubit(),
+ child: HotelOverviewPageScreen(),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart b/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart
new file mode 100644
index 00000000..55335512
--- /dev/null
+++ b/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart
@@ -0,0 +1,45 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:concierge/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart';
+import 'package:concierge/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart';
+import 'package:concierge/presentation/widgets/hotel_overview_app_bar.dart';
+
+class HotelOverviewPageScreen extends StatelessWidget {
+ const HotelOverviewPageScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder<HotelOverviewPageCubit, HotelOverviewPageState>(
+ builder: (context, state) {
+ final cubit = context.read<HotelOverviewPageCubit>();
+ return MultiBlocListener(
+ listeners: [
+ BlocListener<HotelOverviewPageCubit, HotelOverviewPageState>(
+ listenWhen: (prev, curr) =>
+ prev.isLoading && !curr.isLoading && curr.errorMessage.isNotEmpty,
+ listener: (context, state) {
+
+ },
+ )
+ ],
+ child: Scaffold(
+ appBar: HotelOverviewAppBar(
+ areas: state.property?.areas ?? const [],
+ selectedAreaId: state.selectedAreaId,
+ onAreaPressed: (area) {
+ cubit.selectAreaId(area.id);
+ },
+ ),
+ body: Center(
+ child: Column(
+ children: [
+
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
\ No newline at end of file
diff --git a/concierge/lib/presentation/theme/app_borders.dart b/concierge/lib/presentation/theme/app_borders.dart
new file mode 100644
index 00000000..bca2fff8
--- /dev/null
+++ b/concierge/lib/presentation/theme/app_borders.dart
@@ -0,0 +1,8 @@
+import 'package:flutter/cupertino.dart';
+
+abstract class AppBorders {
+ static final borderRadiusLarge1x = BorderRadius.circular(32);
+ static final borderRadiusMedium = BorderRadius.circular(16);
+ static final borderRadiusSmall1x = BorderRadius.circular(12);
+ static final borderRadiusSmall2x = BorderRadius.circular(8);
+}
diff --git a/concierge/lib/presentation/theme/app_button_styles.dart b/concierge/lib/presentation/theme/app_button_styles.dart
new file mode 100644
index 00000000..0ffcaf58
--- /dev/null
+++ b/concierge/lib/presentation/theme/app_button_styles.dart
@@ -0,0 +1,311 @@
+import 'package:flutter/material.dart';
+import 'package:concierge/presentation/theme/app_color_scheme.dart';
+import 'package:concierge/presentation/theme/app_textstyles.dart';
+
+import 'app_colors.dart';
+
+class AppButtonStyles {
+ final AppColorScheme colorScheme;
+
+ AppButtonStyles({required this.colorScheme});
+
+ static final defaultShape = WidgetStatePropertyAll(
+ RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.circular(16)),
+ );
+ static final smallShape = WidgetStatePropertyAll(
+ RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.circular(8)),
+ );
+ static final linkShape = WidgetStatePropertyAll(
+ RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.circular(2)),
+ );
+ static final defaultMinSize = WidgetStatePropertyAll(Size(0, 48));
+ static final defaultMinSizeLarge = WidgetStatePropertyAll(Size(240, 48));
+ static final smallMinSize = WidgetStatePropertyAll(Size(0, 32));
+ static final squareMinSize = WidgetStatePropertyAll(Size(36, 36));
+ static final squareMinSizeLarge = WidgetStatePropertyAll(Size(0, 48));
+ static final defaultPadding = WidgetStatePropertyAll(
+ EdgeInsets.symmetric(horizontal: 16),
+ );
+ static final smallPadding = WidgetStatePropertyAll(
+ EdgeInsets.symmetric(horizontal: 12),
+ );
+ static final squarePadding = WidgetStatePropertyAll(EdgeInsets.all(4));
+
+ static final defaultTextStyle = WidgetStatePropertyAll(
+ AppTextStyles.b2Highlight,
+ );
+
+ static final secondaryTextStyle = WidgetStatePropertyAll(
+ AppTextStyles.textHighlightSemiBold,
+ );
+
+ late final primaryMainDefault = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfacePrimary,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ textStyle: defaultTextStyle,
+ minimumSize: defaultMinSize,
+ padding: defaultPadding,
+ shape: WidgetStateProperty.fromMap({
+ WidgetState.disabled: defaultShape.value,
+ WidgetState.any: defaultShape.value.copyWith(
+ side: BorderSide(color: AppColors.colorPrimaryText, width: 1),
+ ),
+ }),
+ );
+
+ late final transparentDefault = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: Colors.transparent,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ minimumSize: defaultMinSize,
+ textStyle: defaultTextStyle,
+ padding: defaultPadding,
+ shape: defaultShape,
+ );
+
+ late final greyDefault = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfaceSecondary,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ minimumSize: defaultMinSize,
+ textStyle: defaultTextStyle,
+ padding: defaultPadding,
+ shape: defaultShape,
+ );
+
+ late final greyDefaultLarge = greyDefault.copyWith(
+ minimumSize: defaultMinSizeLarge,
+ );
+
+ late final bottomNav = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.selected: colorScheme.surfacePrimary,
+ WidgetState.any: colorScheme.background,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ minimumSize: defaultMinSize,
+ textStyle: defaultTextStyle,
+ padding: defaultPadding,
+ shape: defaultShape,
+ );
+
+ late final invertDefault = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfaceInvert,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textInvertPrimary,
+ }),
+ minimumSize: defaultMinSize,
+ textStyle: secondaryTextStyle,
+ padding: smallPadding,
+ shape: defaultShape,
+ );
+
+ late final invertDefaultLarge = invertDefault.copyWith(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfaceInvert,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textInvertPrimary,
+ }),
+ minimumSize: defaultMinSizeLarge,
+ padding: defaultPadding,
+ shape: defaultShape,
+ );
+
+ late final mainHighlight = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfaceHighlight,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ minimumSize: defaultMinSize,
+ textStyle: defaultTextStyle,
+ shape: defaultShape,
+ padding: defaultPadding,
+ );
+
+ late final outlineDefault = ButtonStyle(
+ backgroundColor: WidgetStatePropertyAll(Colors.transparent),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ shape: WidgetStateProperty.fromMap({
+ WidgetState.disabled: RoundedRectangleBorder(
+ borderRadius: BorderRadiusGeometry.circular(16),
+ side: BorderSide(color: AppColors.colorDivider, width: 1),
+ ),
+ WidgetState.any: RoundedRectangleBorder(
+ borderRadius: BorderRadiusGeometry.circular(16),
+ side: BorderSide(color: AppColors.colorDivider, width: 1),
+ ),
+ }),
+ minimumSize: defaultMinSize,
+ textStyle: defaultTextStyle,
+ padding: defaultPadding,
+ );
+
+ late final outlineHighlight = ButtonStyle(
+ backgroundColor: WidgetStatePropertyAll(Colors.transparent),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ shape: WidgetStateProperty.fromMap({
+ WidgetState.disabled: RoundedRectangleBorder(
+ borderRadius: BorderRadiusGeometry.circular(16),
+ side: BorderSide(color: AppColors.colorDivider, width: 1),
+ ),
+ WidgetState.any: RoundedRectangleBorder(
+ borderRadius: BorderRadiusGeometry.circular(16),
+ side: BorderSide(color: colorScheme.borderHighlight, width: 1),
+ ),
+ }),
+ minimumSize: defaultMinSize,
+ padding: defaultPadding,
+ textStyle: defaultTextStyle,
+ );
+
+ late final textLinkDefault = ButtonStyle(
+ textStyle: WidgetStatePropertyAll(
+ AppTextStyles.textRegular.copyWith(decoration: TextDecoration.underline),
+ ),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.textSecondary,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ backgroundColor: WidgetStatePropertyAll(Colors.transparent),
+ padding: WidgetStatePropertyAll(EdgeInsets.zero),
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ visualDensity: VisualDensity.compact,
+ shape: linkShape,
+ );
+
+ late final textLinkGrey = ButtonStyle(
+ textStyle: WidgetStatePropertyAll(
+ AppTextStyles.textRegular.copyWith(decoration: TextDecoration.underline),
+ ),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.textSecondary,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ backgroundColor: WidgetStatePropertyAll(Colors.transparent),
+ padding: WidgetStatePropertyAll(EdgeInsets.zero),
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ visualDensity: VisualDensity.compact,
+ shape: linkShape,
+ );
+
+ late final textLinkHighLight = ButtonStyle(
+ textStyle: WidgetStatePropertyAll(
+ TextStyle(decoration: TextDecoration.underline),
+ ),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.textSecondary,
+ WidgetState.any: colorScheme.textHighlight,
+ }),
+ backgroundColor: WidgetStatePropertyAll(Colors.transparent),
+ padding: WidgetStatePropertyAll(EdgeInsets.zero),
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ visualDensity: VisualDensity.compact,
+ shape: linkShape,
+ );
+
+ late final secondaryMainDefault = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfacePrimary,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ padding: smallPadding,
+ textStyle: secondaryTextStyle,
+ shape: smallShape,
+ minimumSize: smallMinSize,
+ );
+
+ late final secondaryMainDefaultLarge = secondaryMainDefault.copyWith(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfacePrimary,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ minimumSize: defaultMinSizeLarge,
+ padding: defaultPadding,
+ shape: defaultShape,
+ );
+
+ late final squareIconButton = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfacePrimary,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ padding: squarePadding,
+ shape: smallShape,
+ minimumSize: squareMinSize,
+ );
+
+ late final invertSquareIconButton = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfaceInvert,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textInvertPrimary,
+ }),
+ padding: squarePadding,
+ shape: smallShape,
+ minimumSize: squareMinSize,
+ );
+
+ late final squareIconButtonLarge = ButtonStyle(
+ backgroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: colorScheme.borderDefault,
+ WidgetState.any: colorScheme.surfacePrimary,
+ }),
+ foregroundColor: WidgetStateProperty.fromMap({
+ WidgetState.disabled: AppColors.disabledText,
+ WidgetState.any: colorScheme.textPrimary,
+ }),
+ padding: squarePadding,
+ shape: defaultShape,
+ minimumSize: squareMinSizeLarge,
+ );
+}
diff --git a/concierge/lib/presentation/theme/app_color_scheme.dart b/concierge/lib/presentation/theme/app_color_scheme.dart
new file mode 100644
index 00000000..659aaf60
--- /dev/null
+++ b/concierge/lib/presentation/theme/app_color_scheme.dart
@@ -0,0 +1,77 @@
+import 'package:flutter/material.dart';
+import 'package:concierge/presentation/theme/app_colors.dart';
+
+class AppColorScheme {
+ final Brightness brightness;
+ final Color textPrimary;
+ final Color textSecondary;
+ final Color textHighlight;
+ final Color textInvertPrimary;
+ final Color textInvertSecondary;
+
+ final Color surfacePrimary;
+ final Color surfaceSecondary;
+ final Color surfaceTertiary;
+ final Color surfaceHighlight;
+ final Color surfaceInvert;
+
+ final Color borderDefault;
+ final Color borderInvert;
+ final Color borderHighlight;
+
+ final Color background;
+
+ AppColorScheme({
+ required this.brightness,
+ required this.textPrimary,
+ required this.textSecondary,
+ required this.textHighlight,
+ required this.textInvertPrimary,
+ required this.textInvertSecondary,
+ required this.surfacePrimary,
+ required this.surfaceSecondary,
+ required this.surfaceTertiary,
+ required this.surfaceHighlight,
+ required this.surfaceInvert,
+ required this.borderDefault,
+ required this.borderInvert,
+ required this.borderHighlight,
+ required this.background,
+ });
+
+ factory AppColorScheme.light() => AppColorScheme(
+ brightness: Brightness.light,
+ textPrimary: AppColors.colorPrimaryText,
+ textSecondary: AppColors.secondaryText,
+ textHighlight: AppColors.colorPrimary,
+ textInvertPrimary: AppColors.colorOnPrimaryTextColor,
+ textInvertSecondary: AppColors.colorOnPrimaryTextColor,
+ surfacePrimary: AppColors.colorSecondary,
+ surfaceSecondary: AppColors.sandColor[20]!,
+ surfaceTertiary: AppColors.sandColor[10]!,
+ surfaceHighlight: AppColors.colorPrimary,
+ surfaceInvert: AppColors.colorPrimaryText,
+ borderDefault: AppColors.colorDivider,
+ borderInvert: AppColors.colorBackground,
+ borderHighlight: AppColors.colorPrimary,
+ background: AppColors.colorBackground,
+ );
+
+ factory AppColorScheme.dark() => AppColorScheme(
+ brightness: Brightness.dark,
+ textPrimary: AppColors.colorOnPrimaryTextColor,
+ textSecondary: AppColors.disabledText,
+ textHighlight: AppColors.sandColor[100]!,
+ textInvertPrimary: AppColors.colorPrimaryText,
+ textInvertSecondary: AppColors.colorPrimaryText,
+ surfacePrimary: AppColors.earthColor[100]!,
+ surfaceSecondary: AppColors.earthColor[80]!,
+ surfaceTertiary: AppColors.earthColor[60]!,
+ surfaceHighlight: AppColors.sandColor[100]!,
+ surfaceInvert: AppColors.colorBackground,
+ borderDefault: AppColors.earthColor[40]!,
+ borderInvert: AppColors.colorBackground,
+ borderHighlight: AppColors.sandColor[100]!,
+ background: AppColors.earthColor[100]!,
+ );
+}
diff --git a/concierge/lib/presentation/theme/app_colors.dart b/concierge/lib/presentation/theme/app_colors.dart
new file mode 100644
index 00000000..b9e7fc93
--- /dev/null
+++ b/concierge/lib/presentation/theme/app_colors.dart
@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+
+abstract class AppColors {
+ // Base colors
+ static const colorPrimary = Color(0xFF677169);
+ static const colorSecondary = Color(0xffF0EAE2);
+ static const colorBackground = Color(0xFFFFFFFF);
+
+ // Text colors
+ static const colorPrimaryText = Color(0xFF000000);
+ static const colorSecondaryText = Color(0xFF000000);
+ static const colorOnPrimaryTextColor = Color(0xFFFFFFFF);
+
+ // System / semantic colors
+ static const colorPrimarySystem = Color(0xFF000000);
+ static const colorSecondarySystem = Color.fromARGB(237, 227, 216, 1);
+ static const colorTertiary = Color(0xFF000000);
+ static const colorTertiaryText = Color(0xFF000000);
+ static const colorTertiarySystem = Color(0xFF000000);
+
+ static const colorShadow = Color(0xFF000000);
+ static const colorDivider = Color(0xFFE0E0E0);
+ static const disabledButtonColor = Color(0xFFF0F0F0);
+
+ static const error = Color(0xFFEB0026);
+
+ // Greys used by components
+ static const secondaryText = Color(0xFF888888);
+ static const disabledText = Color(0xFFCCCCCC);
+
+ // Material palettes
+ static const colorBlack = MaterialColor(0xFF000000, {
+ 75: Color(0xB3000000),
+ 65: Color(0xA6000000),
+ });
+
+ static const int _earthColor = 0xFF677169;
+ static const earthColor = MaterialColor(_earthColor, <int, Color>{
+ 100: Color(_earthColor),
+ 80: Color.fromRGBO(128, 139, 130, 1.0),
+ 60: Color.fromRGBO(160, 171, 163, 1.0),
+ 40: Color.fromRGBO(192, 200, 194, 1.0),
+ 20: Color.fromRGBO(227, 230, 227, 1.0),
+ 10: Color.fromRGBO(238, 239, 238, 1.0),
+ });
+
+ static const int _sandColor = 0xFFAA8D65;
+ static const sandColor = MaterialColor(_sandColor, <int, Color>{
+ 100: Color(_sandColor),
+ 80: Color.fromRGBO(190, 161, 121, 1.0),
+ 60: Color.fromRGBO(215, 201, 185, 1.0),
+ 40: Color.fromRGBO(237, 227, 216, 1.0),
+ 20: Color.fromRGBO(240, 234, 226, 1.0),
+ 10: Color.fromRGBO(249, 246, 242, 1.0),
+ });
+
+ /// Maps backend "theme" strings to a palette.
+ /// Accepts both older lowercase values and newer uppercase values.
+ static MaterialColor fromTheme(String theme) {
+ final t = theme.trim().toUpperCase();
+ return switch (t) {
+ 'EARTH' || 'EARTH_COLOR' => earthColor,
+ 'SAND' || 'SAND_COLOR' => sandColor,
+ _ => earthColor,
+ };
+ }
+}
+
+extension CustomColors on ColorScheme {
+ Color get secondaryText => AppColors.secondaryText;
+ Color get disabledText => AppColors.disabledText;
+}
diff --git a/concierge/lib/presentation/theme/app_textstyles.dart b/concierge/lib/presentation/theme/app_textstyles.dart
new file mode 100644
index 00000000..297b4cc2
--- /dev/null
+++ b/concierge/lib/presentation/theme/app_textstyles.dart
@@ -0,0 +1,60 @@
+import 'package:concierge/presentation/theme/app_colors.dart';
+import 'package:flutter/cupertino.dart';
+
+abstract class AppTextStyles {
+ static const fontFamilyKaio = 'Kaio';
+ static const fontFamilyRedHatText = 'RedHatText';
+ static const fontFamilyRedHatDisplay = 'RedHatDisplay';
+
+ static const _heading = TextStyle(
+ letterSpacing: 0,
+ fontFamily: fontFamilyKaio,
+ );
+
+ static const _body = TextStyle(
+ letterSpacing: 0,
+ fontFamily: fontFamilyRedHatText,
+ fontWeight: FontWeight.w400,
+ color: AppColors.colorPrimaryText,
+ );
+
+ static const _text = TextStyle(
+ fontSize: 12,
+ fontFamily: fontFamilyRedHatText,
+ color: AppColors.colorPrimaryText,
+ );
+
+ static final h1 = _heading.copyWith(fontSize: 48);
+ static final h2 = _heading.copyWith(fontSize: 40);
+ static final h3 = _heading.copyWith(fontSize: 32);
+ static final h4 = _heading.copyWith(fontSize: 24);
+ static final h5 = _heading.copyWith(fontSize: 20);
+ static final h6 = _heading.copyWith(fontSize: 16);
+
+ static final b1 = _body.copyWith(fontSize: 16);
+ static final b1Highlight = b1.copyWith(fontWeight: FontWeight.w500);
+
+ static final b2Regular = _body.copyWith(fontSize: 14, height: 1.29);
+ static final b2Highlight = b2Regular.copyWith(fontWeight: FontWeight.w600);
+ static final b3 = _body.copyWith(fontSize: 12);
+
+ static final textRegular = _text.copyWith(fontWeight: FontWeight.w400);
+ static final textSemiBold = _text.copyWith(fontWeight: FontWeight.w500);
+ static final textRegularSmall = _text.copyWith(fontSize: 12);
+ static final textHighlightSemiBold = _text.copyWith(
+ fontSize: 12,
+ fontWeight: FontWeight.w600,
+ );
+
+ // Should always be UPPERCASE
+ static final label = TextStyle(
+ fontSize: 10,
+ fontWeight: FontWeight.w700,
+ fontFamily: fontFamilyKaio,
+ );
+
+ static final legal = TextStyle(
+ fontSize: 10,
+ fontFamily: fontFamilyRedHatText,
+ );
+}
diff --git a/concierge/lib/presentation/theme/dimens.dart b/concierge/lib/presentation/theme/dimens.dart
new file mode 100644
index 00000000..3e235f32
--- /dev/null
+++ b/concierge/lib/presentation/theme/dimens.dart
@@ -0,0 +1,25 @@
+abstract class AppDimens {
+ static const double size02 = 2;
+ static const double size04 = 4;
+ static const double size08 = 8;
+ static const double size12 = 12;
+ static const double size16 = 16;
+ static const double size24 = 24;
+ static const double size32 = 32;
+ static const double size40 = 40;
+ static const double size48 = 48;
+ static const double size56 = 56;
+ static const double size60 = 60;
+ static const double size100 = 100;
+
+ static const double large4x = 96;
+ static const double large3x = 64;
+ static const double large2x = 48;
+ static const double large1x = 32;
+ static const double medium = 24;
+ static const double medium16 = 16;
+ static const double small1x = 12;
+ static const double small2x = 8;
+ static const double small3x = 4;
+ static const double small4x = 2;
+}
diff --git a/concierge/lib/presentation/theme/spaces.dart b/concierge/lib/presentation/theme/spaces.dart
new file mode 100644
index 00000000..e3af46d7
--- /dev/null
+++ b/concierge/lib/presentation/theme/spaces.dart
@@ -0,0 +1,30 @@
+import 'package:flutter/cupertino.dart';
+import 'package:gap/gap.dart';
+import 'dimens.dart';
+
+abstract class AppSpaces {
+ static const gap02 = Gap(AppDimens.size02);
+ static const gap04 = Gap(AppDimens.size04);
+ static const gap08 = Gap(AppDimens.size08);
+ static const gap12 = Gap(AppDimens.size12);
+ static const gap16 = Gap(AppDimens.size16);
+ static const gap24 = Gap(AppDimens.size24);
+ static const gap32 = Gap(AppDimens.size32);
+ static const gap40 = Gap(AppDimens.size40);
+ static const gap48 = Gap(AppDimens.size48);
+ static const gap56 = Gap(AppDimens.size56);
+ static const gap60 = Gap(AppDimens.size60);
+
+ static const large4x = Gap(AppDimens.large4x);
+ static const large3x = Gap(AppDimens.large3x);
+ static const large2x = Gap(AppDimens.large2x);
+ static const large1x = Gap(AppDimens.large1x);
+ static const medium = Gap(AppDimens.medium);
+ static const medium16 = Gap(AppDimens.medium16);
+ static const small1x = Gap(AppDimens.small1x);
+ static const small2x = Gap(AppDimens.small2x);
+ static const small3x = Gap(AppDimens.small3x);
+ static const small4x = Gap(AppDimens.small4x);
+ static const gapBottomNav = Gap(AppDimens.size100);
+ static const expandWidth = SizedBox(width: double.infinity);
+}
diff --git a/concierge/lib/presentation/widgets/hotel_overview_app_bar.dart b/concierge/lib/presentation/widgets/hotel_overview_app_bar.dart
new file mode 100644
index 00000000..32017298
--- /dev/null
+++ b/concierge/lib/presentation/widgets/hotel_overview_app_bar.dart
@@ -0,0 +1,297 @@
+import 'package:concierge/presentation/navigation/app_routes.dart';
+import 'package:concierge/presentation/theme/app_colors.dart';
+import 'package:concierge/presentation/theme/app_textstyles.dart';
+import 'package:concierge/presentation/widgets/round_icon_button.dart';
+import 'package:concierge/domain/models/property_area.dart';
+import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+import 'package:go_router/go_router.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+class HotelOverviewAppBar extends StatelessWidget implements PreferredSizeWidget {
+ static const double height = 200;
+ static const double _areaItemWidth = 55;
+ static const double _areaItemHeight = 46;
+
+ final bool shouldShowAppBar;
+ final bool shouldShowBackButton;
+ final bool shouldShowProfileButton;
+ final VoidCallback? onBackPressed;
+ final VoidCallback? onProfilePressed;
+
+ final List<PropertyArea> areas;
+ final ValueChanged<PropertyArea>? onAreaPressed;
+ final int? selectedAreaId;
+
+ /// Asset paths for the SVG icons.
+ final String backIconAsset;
+ final String profileIconAsset;
+
+ const HotelOverviewAppBar({
+ super.key,
+ this.shouldShowAppBar = true,
+ this.shouldShowBackButton = true,
+ this.shouldShowProfileButton = true,
+ this.onBackPressed,
+ this.onProfilePressed,
+ this.areas = const [],
+ this.onAreaPressed,
+ this.selectedAreaId,
+ this.backIconAsset = 'assets/icons/arrow-right.svg',
+ this.profileIconAsset = 'assets/icons/user-open.svg',
+ });
+
+ bool _isHttpUrl(String value) {
+ final uri = Uri.tryParse(value);
+ return uri != null &&
+ uri.hasAbsolutePath &&
+ (uri.scheme == 'http' || uri.scheme == 'https');
+ }
+
+ String? _areaIconUrl(PropertyArea area) {
+ // If backend sends the icon as a URL (string), use it.
+ if (_isHttpUrl(area.icon)) return area.icon;
+
+ // Otherwise, fall back to a representative area image if available.
+ if (area.events.isNotEmpty) {
+ // Prefer the smallest suitable image.
+ return area.events.first.heroImage.thumbnail;
+ }
+
+ return null;
+ }
+
+ String? _areaIconAsset(PropertyArea area) {
+ final name = area.name.trim().toLowerCase();
+ final icon = area.icon.trim().toUpperCase();
+
+ // Primary mapping (by backend icon code)
+ switch (icon) {
+ case 'COCKTAIL':
+ return 'assets/icons/cocktail.svg'; // Bar
+ case 'BED':
+ return 'assets/icons/single-bed.svg'; // Room
+ case 'DROP':
+ return 'assets/icons/drop.svg'; // Spa
+ case 'PEOPLE':
+ return 'assets/icons/group-medium.svg'; // Co-Work
+ case 'FLOWER':
+ return 'assets/icons/sun.svg'; // Terrasse
+ }
+
+ // Secondary mapping (by area name)
+ if (name.contains('bar')) return 'assets/icons/cocktail.svg';
+ if (name.contains('værelse') || name.contains('room')) {
+ return 'assets/icons/single-bed.svg';
+ }
+ if (name.contains('spa')) return 'assets/icons/drop.svg';
+ if (name.contains('co-work') || name.contains('cowork')) {
+ return 'assets/icons/group-medium.svg';
+ }
+ if (name.contains('terrasse') || name.contains('terrace')) {
+ return 'assets/icons/sun.svg';
+ }
+
+ return null;
+ }
+
+ Widget _buildAreaIcon(PropertyArea area, {required Color tintColor}) {
+ final asset = _areaIconAsset(area);
+ if (asset != null) {
+ return SvgPicture.asset(
+ asset,
+ package: 'concierge',
+ width: 24,
+ height: 24,
+ fit: BoxFit.contain,
+ colorFilter: ColorFilter.mode(tintColor, BlendMode.srcIn),
+ );
+ }
+
+ final iconUrl = _areaIconUrl(area);
+ if (iconUrl != null) {
+ return ColorFiltered(
+ colorFilter: ColorFilter.mode(tintColor, BlendMode.srcIn),
+ child: Image.network(
+ iconUrl,
+ fit: BoxFit.contain,
+ errorBuilder: (context, _, __) => const SizedBox.shrink(),
+ loadingBuilder: (context, child, loadingProgress) {
+ if (loadingProgress == null) return child;
+ return const Center(
+ child: SizedBox(
+ height: 14,
+ width: 14,
+ child: CircularProgressIndicator(strokeWidth: 2),
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ return const SizedBox.shrink();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (!shouldShowAppBar) return const SizedBox.shrink();
+
+ return Container(
+ decoration: BoxDecoration(
+ color: AppColors.sandColor[20]!,
+ borderRadius: const BorderRadius.only(
+ bottomLeft: Radius.circular(24),
+ bottomRight: Radius.circular(24),
+ ),
+ ),
+ height: preferredSize.height,
+ child: Padding(
+ padding: const EdgeInsets.only(bottom: 16, left: 8, right: 8, top: 50),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ Visibility(
+ visible: shouldShowBackButton,
+ child: RoundIconButton(
+ color: Colors.white,
+ icon: backIconAsset,
+ onPressed: () {
+ if (onBackPressed != null) {
+ onBackPressed!.call();
+ return;
+ }
+ try {
+ final goRouter = GoRouter.of(context);
+ if (goRouter.canPop()) {
+ context.pop();
+ } else {
+ context.go(AppRoutes.hotelOverviewPage);
+ }
+ } catch (_) {
+ context.go(AppRoutes.hotelOverviewPage);
+ }
+ },
+ ),
+ ),
+ Visibility(
+ visible: shouldShowProfileButton,
+ child: RoundIconButton(
+ color: Colors.white,
+ icon: profileIconAsset,
+ onPressed: () {
+ if (onProfilePressed != null) {
+ onProfilePressed!.call();
+ }
+ },
+ ),
+ ),
+ ],
+ ),
+ const Gap(12),
+ Expanded(
+ child: Align(
+ alignment: Alignment.bottomCenter,
+ child: SizedBox(
+ height: _areaItemHeight,
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ if (areas.isEmpty) return const SizedBox.shrink();
+
+ const spacing = 10.0;
+ const horizontalPadding = 16.0; // 8 left + 8 right
+ final requiredWidth = (areas.length * _areaItemWidth) +
+ ((areas.length - 1) * spacing) +
+ horizontalPadding;
+ final fitsWithoutScroll = requiredWidth <= constraints.maxWidth;
+
+ Widget buildItem(PropertyArea area) {
+ final isSelected =
+ selectedAreaId != null && area.id == selectedAreaId;
+ final unselectedColor =
+ AppColors.colorPrimaryText.withValues(alpha: 0.35);
+ final selectedColor = AppColors.sandColor[100]!;
+ final tintColor =
+ isSelected ? selectedColor : unselectedColor;
+
+ return InkWell(
+ borderRadius: BorderRadius.circular(12),
+ onTap: onAreaPressed == null
+ ? null
+ : () => onAreaPressed!(area),
+ child: SizedBox(
+ width: _areaItemWidth,
+ height: _areaItemHeight,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SizedBox(
+ height: 24,
+ width: 24,
+ child:
+ _buildAreaIcon(area, tintColor: tintColor),
+ ),
+ const Gap(4),
+ SizedBox(
+ height: 18,
+ child: Text(
+ area.name,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ textAlign: TextAlign.center,
+ style: AppTextStyles.textRegularSmall.copyWith(
+ color: tintColor,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ if (fitsWithoutScroll) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8),
+ child: Row(
+ children: [
+ for (var i = 0; i < areas.length; i++) ...[
+ buildItem(areas[i]),
+ if (i != areas.length - 1)
+ const SizedBox(width: spacing),
+ ],
+ ],
+ ),
+ );
+ }
+
+ return ListView.separated(
+ scrollDirection: Axis.horizontal,
+ physics: const BouncingScrollPhysics(),
+ padding: const EdgeInsets.symmetric(horizontal: 8),
+ itemCount: areas.length,
+ separatorBuilder: (_, __) => const SizedBox(width: spacing),
+ itemBuilder: (context, index) => buildItem(areas[index]),
+ );
+ },
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ @override
+ Size get preferredSize =>
+ Size.fromHeight(shouldShowAppBar ? height : 0);
+}
+
+
diff --git a/concierge/lib/presentation/widgets/round_icon_button.dart b/concierge/lib/presentation/widgets/round_icon_button.dart
new file mode 100644
index 00000000..db501555
--- /dev/null
+++ b/concierge/lib/presentation/widgets/round_icon_button.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class RoundIconButton extends StatelessWidget {
+ final String icon;
+ final Function onPressed;
+ final Color color;
+ final String? assetPackage;
+ const RoundIconButton({
+ required this.icon,
+ required this.onPressed,
+ required this.color,
+ this.assetPackage = 'concierge',
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return ElevatedButton(
+ onPressed: () {
+ onPressed();
+ },
+ style: ElevatedButton.styleFrom(
+ minimumSize: const Size(45, 45),
+ elevation: 0,
+ backgroundColor: color,
+ shape: const CircleBorder(side: BorderSide(style: BorderStyle.none)),
+ ),
+ child: SvgPicture.asset(icon, package: assetPackage),
+ );
+ }
+}
+
+
diff --git a/concierge/pubspec.yaml b/concierge/pubspec.yaml
new file mode 100644
index 00000000..117701f3
--- /dev/null
+++ b/concierge/pubspec.yaml
@@ -0,0 +1,99 @@
+name: concierge
+description: "A new Flutter plugin project."
+version: 0.0.1
+
+environment:
+ sdk: ^3.8.1
+ flutter: '>=3.3.0'
+
+dependencies:
+ flutter:
+ sdk: flutter
+ plugin_platform_interface: ^2.0.2
+ equatable: ^2.0.5
+ json_annotation: ^4.9.0
+ easy_localization: ^3.0.7
+ dio: ^5.8.0+1
+ retrofit: ^4.6.0
+ freezed_annotation: ^3.1.0
+ bloc: ^9.1.0
+ flutter_bloc: ^9.1.1
+ shimmer: ^3.0.0
+ lottie: ^3.1.2
+ flutter_dotenv: ^6.0.0
+ go_router: ^16.2.0
+ get_it: ^8.0.3
+ pretty_dio_logger: any
+ gap: ^3.0.1
+ go_router_builder: ^4.1.0
+ flutter_svg: ^2.2.1
+ flutter_secure_storage: ^9.2.4
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^5.0.0
+ build_runner: ^2.4.13
+ json_serializable: ^6.9.0
+ change_case: ^2.2.0
+ retrofit_generator: ^10.0.5
+ freezed: ^3.0.6
+ json_pretty: ^1.0.4
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+ # This section identifies this Flutter project as a plugin project.
+ # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
+ # which should be registered in the plugin registry. This is required for
+ # using method channels.
+ # The Android 'package' specifies package in which the registered class is.
+ # This is required for using method channels on Android.
+ # The 'ffiPlugin' specifies that native code should be built and bundled.
+ # This is required for using `dart:ffi`.
+ # All these are used by the tooling to maintain consistency when
+ # adding or updating assets for this project.
+ plugin:
+ platforms:
+ android:
+ package: com.example.concierge
+ pluginClass: ConciergePlugin
+ ios:
+ pluginClass: ConciergePlugin
+
+ # To add assets to your plugin package, add an assets section, like this:
+ assets:
+ - assets/images/
+ - assets/icons/
+ - assets/
+ - lib/data/mock/
+ - dotenv/
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+ #
+ # For details regarding assets in packages, see
+ # https://flutter.dev/to/asset-from-package
+ #
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/to/resolution-aware-images
+
+ # To add custom fonts to your plugin package, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts in packages, see
+ # https://flutter.dev/to/font-from-package
diff --git a/concierge/scripts/build_ios.sh b/concierge/scripts/build_ios.sh
new file mode 100644
index 00000000..10ae812e
--- /dev/null
+++ b/concierge/scripts/build_ios.sh
@@ -0,0 +1 @@
+fvm flutter build ios
\ No newline at end of file
diff --git a/concierge/scripts/dart/new_feature.dart b/concierge/scripts/dart/new_feature.dart
new file mode 100644
index 00000000..6e536c59
--- /dev/null
+++ b/concierge/scripts/dart/new_feature.dart
@@ -0,0 +1,149 @@
+import 'package:change_case/change_case.dart';
+
+import 'utils.dart';
+
+Future<void> main(List<String> args) async {
+ // create files
+ if (args.isEmpty) throw Exception("Missing args");
+ if (args.length > 1) throw Exception("Too many arguments");
+ final featureName = args[0];
+ final className = featureName.toPascalCase();
+ final snakeCase = featureName.toSnakeCase();
+ final blocFile = await createFile(
+ "lib/presentation/screens/$featureName/bloc/${featureName}_cubit.dart");
+ final stateFile = await createFile(
+ "lib/presentation/screens/$featureName/bloc/${featureName}_state.dart");
+ final screenFile = await createFile(
+ "lib/presentation/screens/$featureName/${featureName}_screen.dart");
+ final routeFile = await createFile(
+ "lib/presentation/screens/$featureName/${featureName}_route.dart");
+
+ // write files
+ await writeToFile(screenFile, screenTemplate(snakeCase, className));
+ await writeToFile(stateFile, stateTemplate(snakeCase, className));
+ await writeToFile(blocFile, blocTemplate(snakeCase, className));
+ await writeToFile(routeFile, routeTemplate(snakeCase, className));
+ print("$featureName created successfully");
+}
+
+String screenTemplate(String snakeCase, String className) {
+ final cubitName = "${className}Cubit";
+ final screenName = "${className}Screen";
+ final stateName = "${className}State";
+ return """
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:concierge/presentation/screens/$snakeCase/bloc/${snakeCase}_cubit.dart';
+import 'package:concierge/presentation/screens/$snakeCase/bloc/${snakeCase}_state.dart';
+import 'package:concierge/presentation/utils/snackbar_utils.dart';
+
+class $screenName extends StatelessWidget {
+ const $screenName({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder<$cubitName, $stateName>(
+ builder: (context, state) {
+ final cubit = context.read<$cubitName>();
+ return MultiBlocListener(
+ listeners: [
+ BlocListener<$cubitName, $stateName>(
+ listenWhen: (prev, curr) =>
+ prev.isLoading && !curr.isLoading && curr.errorMessage.isNotEmpty,
+ listener: (context, state) {
+ context.showErrorSnackBar(state.errorMessage);
+ },
+ )
+ ],
+ child: Scaffold(
+ appBar: AppBar(),
+ body: Center(
+ child: Column(
+ children: [
+ Text("$className"),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
+"""
+ .trim();
+}
+
+String stateTemplate(String snakeCase, String className) {
+ final stateName = "${className}State";
+ return """
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part '../../../../_generated/presentation/screens/$snakeCase/bloc/${snakeCase}_state.freezed.dart';
+
+@freezed
+abstract class $stateName with _\$$stateName {
+ const factory $stateName({
+ @Default(false) bool isLoading,
+ @Default("") String errorMessage,
+ }) = _$stateName;
+}
+"""
+ .trim();
+}
+
+String blocTemplate(String snakeCase, String className) {
+ final cubitName = "${className}Cubit";
+ final stateName = "${className}State";
+ return """
+import 'package:concierge/presentation/base/base_cubit.dart';
+import 'package:concierge/presentation/screens/$snakeCase/bloc/${snakeCase}_state.dart';
+
+class $cubitName extends BaseCubit<$stateName> {
+ $cubitName() : super(const $stateName()) {
+ init();
+ }
+
+ Future<void> init() async {
+
+ }
+}
+ """
+ .trim();
+}
+
+String routeTemplate(String snakeCase, String className) {
+ final routeName = "${className}Route";
+ final pathName = "static const ${className.toCamelCase()} = \"/${className.toKebabCase()}\";";
+ final cubitName = "${className}Cubit";
+ final screenName = "${className}Screen";
+
+ return """
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:concierge/presentation/navigation/app_routes.dart';
+import 'package:go_router/go_router.dart';
+import 'package:concierge/presentation/navigation/transitions/slide_up_transition.dart';
+import 'package:concierge/presentation/screens/$snakeCase/bloc/${snakeCase}_cubit.dart';
+import 'package:concierge/presentation/screens/$snakeCase/${snakeCase}_screen.dart';
+
+part '../../../_generated/presentation/screens/$snakeCase/${snakeCase}_route.g.dart';
+
+@TypedGoRoute<$routeName>(
+ path: "/${className.toKebabCase()}", // add me to AppRoutes: $pathName
+)
+class $routeName extends GoRouteData with \$$routeName {
+
+ @override
+ Page<void> buildPage(BuildContext context, GoRouterState state) {
+ return slideUpTransition(
+ state: state,
+ child: BlocProvider(
+ create: (context) => $cubitName(),
+ child: $screenName(),
+ ),
+ );
+ }
+}"""
+ .trim();
+}
diff --git a/concierge/scripts/dart/new_model.dart b/concierge/scripts/dart/new_model.dart
new file mode 100644
index 00000000..dbf052ce
--- /dev/null
+++ b/concierge/scripts/dart/new_model.dart
@@ -0,0 +1,144 @@
+import 'package:change_case/change_case.dart';
+
+import 'utils.dart';
+
+Future<void> main(List<String> args) async {
+ // create files
+ if (args.isEmpty) throw Exception("Missing args");
+ if (args.length > 1) throw Exception("Too many arguments");
+ final modelName = args[0];
+ final className = modelName.toPascalCase();
+ final dbClassName = "${className}Db";
+ final dtoClassName = "${className}Dto";
+ final presentationFileName = className.toSnakeCase();
+ final dbFileName = dbClassName.toSnakeCase();
+ final dtoFileName = dtoClassName.toSnakeCase();
+ final tableClassName = "${className}Table";
+
+ final presentationFile =
+ await createFile("lib/presentation/models/$presentationFileName.dart");
+ final dbFile = await createFile("lib/data/local/tables/$dbFileName.dart");
+ final dtoFile = await createFile("lib/data/remote/models/$dtoFileName.dart");
+ final mapperFile = await createFile(
+ "lib/domain/mappers/${className.toSnakeCase()}_mapper.dart");
+
+ // write files
+ await writeToFile(presentationFile, presentationFileContent(className));
+ await writeToFile(dbFile, dbFileContent(dbClassName, tableClassName));
+ await writeToFile(dtoFile, dtoFileContent(dtoClassName, dtoFileName));
+ await writeToFile(
+ mapperFile,
+ mapperFileContent(
+ dbClassName: dbClassName,
+ dbFileName: dbFileName,
+ dtoClassName: dtoClassName,
+ dtoFileName: dtoFileName,
+ presClassName: className,
+ presentationFileName: presentationFileName,
+ ));
+ print("$className created successfully");
+ print("Please add $tableClassName to tables-list in @DriftDatabase(tables: [..., $tableClassName]) and the build runner");
+}
+
+String presentationFileContent(String className) {
+ return """
+class $className {
+ final String id;
+
+ $className({
+ required this.id,
+ });
+}
+ """
+ .trim();
+}
+
+String dtoFileContent(String className, String fileName) {
+ return """
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:concierge/presentation/utils/json_utils.dart';
+
+part '../../../_generated/data/remote/models/$fileName.g.dart';
+
+@JsonSerializable()
+class $className {
+ final String id;
+
+ $className({
+ required this.id,
+ });
+
+ factory $className.fromJson(Json json) => _\$${className}FromJson(json);
+
+ Json toJson() => _\$${className}ToJson(this);
+}
+ """
+ .trim();
+}
+
+String dbFileContent(String className, String tableName) {
+ return """
+import 'package:drift/drift.dart';
+
+@DataClassName("$className")
+class $tableName extends Table {
+ TextColumn get uid => text().unique()();
+
+ TextColumn get json => text()();
+}
+ """
+ .trim();
+}
+
+String mapperFileContent({
+ required String presClassName,
+ required String dtoClassName,
+ required String dbClassName,
+ required String dbFileName,
+ required String dtoFileName,
+ required String presentationFileName,
+}) {
+ return """
+import 'dart:convert';
+
+import 'package:concierge/data/local/database/concierge_database.dart';
+import 'package:concierge/data/remote/models/$dtoFileName.dart';
+import 'package:concierge/presentation/models/$presentationFileName.dart';
+
+extension ${dtoClassName}Mapper on $dtoClassName {
+
+ $presClassName to$presClassName() {
+ return $presClassName(id: id);
+ }
+
+ $dbClassName toDb() {
+ final json = jsonEncode(toJson());
+ return $dbClassName(uid: id, json: json);
+ }
+}
+
+extension ${dtoClassName}ListMapper on Iterable<$dtoClassName> {
+ Iterable<$presClassName> to${presClassName}List() {
+ return map((productDto) => productDto.to$presClassName());
+ }
+
+ Iterable<$dbClassName> toDbList() {
+ return map((productDto) => productDto.toDb());
+ }
+}
+
+
+extension ${dbClassName}Mapper on $dbClassName {
+ $presClassName to$presClassName() {
+ return $dtoClassName.fromJson(jsonDecode(this.json)).to$presClassName();
+ }
+}
+
+extension ${dbClassName}ListMapper on Iterable<$dbClassName> {
+ Iterable<$presClassName> to${presClassName}List() {
+ return map((db) => db.to$presClassName());
+ }
+}
+ """
+ .trim();
+}
diff --git a/concierge/scripts/dart/utils.dart b/concierge/scripts/dart/utils.dart
new file mode 100644
index 00000000..77dae9b1
--- /dev/null
+++ b/concierge/scripts/dart/utils.dart
@@ -0,0 +1,15 @@
+import 'dart:io';
+
+Future<File> createFile(String path) async {
+ final file = File(path);
+ if (file.existsSync()) throw Exception("File already exists");
+ await file.create(recursive: true);
+ return file;
+}
+
+Future<void> writeToFile(File file, String content) async {
+ var sink = file.openWrite();
+ sink.write(content);
+ await sink.flush();
+ await sink.close();
+}
\ No newline at end of file
diff --git a/concierge/scripts/dart/write_arb.dart b/concierge/scripts/dart/write_arb.dart
new file mode 100644
index 00000000..40fd219f
--- /dev/null
+++ b/concierge/scripts/dart/write_arb.dart
@@ -0,0 +1,52 @@
+import 'dart:convert';
+import 'dart:io';
+import 'package:change_case/change_case.dart';
+import 'package:json_pretty/json_pretty.dart';
+
+Future<void> main(List<String> args) async {
+ if (args.isEmpty) throw Exception("Missing args");
+ if (args.length > 1) throw Exception("Too many args");
+ final keys = args.first.split(",");
+ final enArb = File("lib/_localisation/intl_en.arb");
+ final daArb = File("lib/_localisation/intl_da.arb");
+ final svArb = File("lib/_localisation/intl_sv.arb");
+ final noArb = File("lib/_localisation/intl_no.arb");
+
+ final enContent = await enArb.readAsString();
+ final json = jsonDecode(enContent) as Map;
+
+ final argsFormatted = keys.map((arg) => arg.trim().toSnakeCase()).toList();
+ for (var key in argsFormatted) {
+ if (json.containsKey(key)) {
+ throw Exception("Key $key already exists");
+ }
+ }
+
+ printKeys(argsFormatted);
+ await _addKeysToArb(enArb, argsFormatted);
+ await _addKeysToArb(daArb, argsFormatted);
+ await _addKeysToArb(svArb, argsFormatted);
+ await _addKeysToArb(noArb, argsFormatted);
+ print("Keys added successfully");
+}
+
+Future<void> _addKeysToArb(File arbFile, List<String> keys) async {
+ final content = await arbFile.readAsString();
+ final json = jsonDecode(content) as Map;
+ for (var key in keys) {
+ json[key] = "";
+ }
+
+ final encoded = jsonEncode(json);
+ final pretty = prettyPrintJson(encoded);
+ await arbFile.writeAsString(pretty);
+}
+
+Future<void> printKeys(List<String> args) async {
+ final json = <String, dynamic>{};
+ for (var arg in args) {
+ json[arg] = "";
+ }
+ print("Adding keys:");
+ print(prettyPrintJson(jsonEncode(json)));
+}
diff --git a/concierge/scripts/gen.sh b/concierge/scripts/gen.sh
new file mode 100644
index 00000000..291e0047
--- /dev/null
+++ b/concierge/scripts/gen.sh
@@ -0,0 +1,2 @@
+fvm dart run build_runner build --delete-conflicting-outputs
+#fvm flutter gen-l10n
\ No newline at end of file
diff --git a/concierge/scripts/new_feature.sh b/concierge/scripts/new_feature.sh
new file mode 100644
index 00000000..5de45518
--- /dev/null
+++ b/concierge/scripts/new_feature.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+FEATURE_NAME="${1:-}"
+if [[ -z "${FEATURE_NAME}" ]]; then
+ echo "Usage: $(basename "$0") <feature_name>"
+ echo "Example: $(basename "$0") hotel_overview_page"
+ exit 2
+fi
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
+
+cd "${REPO_ROOT}"
+
+fvm dart run scripts/dart/new_feature.dart "${FEATURE_NAME}"
+bash scripts/gen.sh
diff --git a/concierge/scripts/new_translations.sh b/concierge/scripts/new_translations.sh
new file mode 100644
index 00000000..d4eed874
--- /dev/null
+++ b/concierge/scripts/new_translations.sh
@@ -0,0 +1,3 @@
+# USAGE: sh scripts/new_translations.sh key1,key2,key3,key4
+fvm dart scripts/dart/write_arb.dart $1
+fvm flutter gen-l10n
\ No newline at end of file
diff --git a/concierge/scripts/run_dev.sh b/concierge/scripts/run_dev.sh
new file mode 100644
index 00000000..0284b8a7
--- /dev/null
+++ b/concierge/scripts/run_dev.sh
@@ -0,0 +1 @@
+fvm flutter run --flavor develop $1$2
\ No newline at end of file
diff --git a/concierge/scripts/run_prod.sh b/concierge/scripts/run_prod.sh
new file mode 100644
index 00000000..6de6974c
--- /dev/null
+++ b/concierge/scripts/run_prod.sh
@@ -0,0 +1 @@
+fvm flutter run --flavor production $1$2
\ No newline at end of file
diff --git a/concierge/test/concierge_method_channel_test.dart b/concierge/test/concierge_method_channel_test.dart
new file mode 100644
index 00000000..63a4be91
--- /dev/null
+++ b/concierge/test/concierge_method_channel_test.dart
@@ -0,0 +1,27 @@
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:concierge/concierge_method_channel.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ MethodChannelConcierge platform = MethodChannelConcierge();
+ const MethodChannel channel = MethodChannel('concierge');
+
+ setUp(() {
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
+ channel,
+ (MethodCall methodCall) async {
+ return '42';
+ },
+ );
+ });
+
+ tearDown(() {
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
+ });
+
+ test('getPlatformVersion', () async {
+ expect(await platform.getPlatformVersion(), '42');
+ });
+}
diff --git a/concierge/test/concierge_test.dart b/concierge/test/concierge_test.dart
new file mode 100644
index 00000000..244f0305
--- /dev/null
+++ b/concierge/test/concierge_test.dart
@@ -0,0 +1,29 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:concierge/concierge.dart';
+import 'package:concierge/concierge_platform_interface.dart';
+import 'package:concierge/concierge_method_channel.dart';
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+class MockConciergePlatform
+ with MockPlatformInterfaceMixin
+ implements ConciergePlatform {
+
+ @override
+ Future<String?> getPlatformVersion() => Future.value('42');
+}
+
+void main() {
+ final ConciergePlatform initialPlatform = ConciergePlatform.instance;
+
+ test('$MethodChannelConcierge is the default instance', () {
+ expect(initialPlatform, isInstanceOf<MethodChannelConcierge>());
+ });
+
+ test('getPlatformVersion', () async {
+ Concierge conciergePlugin = Concierge();
+ MockConciergePlatform fakePlatform = MockConciergePlatform();
+ ConciergePlatform.instance = fakePlatform;
+
+ expect(await conciergePlugin.getPlatformVersion(), '42');
+ });
+}
diff --git a/payment_plugin/lib/_generated/data/remote/models/api_response.g.dart b/payment_plugin/lib/_generated/data/remote/models/api_response.g.dart
index 528f108e..fd8655df 100644
--- a/payment_plugin/lib/_generated/data/remote/models/api_response.g.dart
+++ b/payment_plugin/lib/_generated/data/remote/models/api_response.g.dart
@@ -10,3 +10,4 @@ ApiResponse<T> _$ApiResponseFromJson<T>(
Map json,
T Function(Object? json) fromJsonT,
) => ApiResponse<T>(data: fromJsonT(json['data']));
+
diff --git a/payment_plugin/lib/payment_plugin_core.dart b/payment_plugin/lib/payment_plugin_core.dart
index 38586cda..437dfee9 100644
--- a/payment_plugin/lib/payment_plugin_core.dart
+++ b/payment_plugin/lib/payment_plugin_core.dart
@@ -83,3 +83,4 @@ class PaymentPlugin {
}
+