diff --git a/apps/analytics_options.yaml b/apps/analytics_options.yaml new file mode 100644 index 00000000..e69de29b diff --git a/apps/apps/client/.gitignore b/apps/apps/client/.gitignore new file mode 100644 index 00000000..3820a95c --- /dev/null +++ b/apps/apps/client/.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-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# 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/apps/apps/client/.metadata b/apps/apps/client/.metadata new file mode 100644 index 00000000..08c24780 --- /dev/null +++ b/apps/apps/client/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: android + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: ios + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: linux + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: macos + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: web + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: windows + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + + # 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/apps/apps/client/analysis_options.yaml b/apps/apps/client/analysis_options.yaml new file mode 100644 index 00000000..856be8f8 --- /dev/null +++ b/apps/apps/client/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analytics_options.yaml \ No newline at end of file diff --git a/apps/apps/client/android/.gitignore b/apps/apps/client/android/.gitignore new file mode 100644 index 00000000..be3943c9 --- /dev/null +++ b/apps/apps/client/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/apps/apps/client/android/app/build.gradle.kts b/apps/apps/client/android/app/build.gradle.kts new file mode 100644 index 00000000..1df6dbf9 --- /dev/null +++ b/apps/apps/client/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.krow_client" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.krowwithus.client" + // 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/apps/apps/client/android/app/src/debug/AndroidManifest.xml b/apps/apps/client/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/apps/apps/client/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/apps/apps/client/android/app/src/main/AndroidManifest.xml b/apps/apps/client/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2b7a964a --- /dev/null +++ b/apps/apps/client/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/client/android/app/src/main/kotlin/com/example/krow_client/MainActivity.kt b/apps/apps/client/android/app/src/main/kotlin/com/example/krow_client/MainActivity.kt new file mode 100644 index 00000000..419b3bd4 --- /dev/null +++ b/apps/apps/client/android/app/src/main/kotlin/com/example/krow_client/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.krow_client + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/apps/apps/client/android/app/src/main/res/drawable-v21/launch_background.xml b/apps/apps/client/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/apps/apps/client/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/apps/client/android/app/src/main/res/drawable/launch_background.xml b/apps/apps/client/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/apps/apps/client/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/apps/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/apps/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/apps/apps/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/apps/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/apps/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/apps/apps/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/apps/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/apps/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/apps/apps/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/apps/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/apps/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/apps/apps/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/apps/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/apps/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/apps/apps/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/apps/client/android/app/src/main/res/values-night/styles.xml b/apps/apps/client/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/apps/apps/client/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/apps/client/android/app/src/main/res/values/styles.xml b/apps/apps/client/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/apps/apps/client/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/apps/client/android/app/src/profile/AndroidManifest.xml b/apps/apps/client/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/apps/apps/client/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/apps/apps/client/android/build.gradle.kts b/apps/apps/client/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/apps/apps/client/android/build.gradle.kts @@ -0,0 +1,24 @@ +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("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/apps/apps/client/android/gradle.properties b/apps/apps/client/android/gradle.properties new file mode 100644 index 00000000..fbee1d8c --- /dev/null +++ b/apps/apps/client/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/apps/apps/client/android/gradle/wrapper/gradle-wrapper.properties b/apps/apps/client/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e4ef43fb --- /dev/null +++ b/apps/apps/client/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.14-all.zip diff --git a/apps/apps/client/android/settings.gradle.kts b/apps/apps/client/android/settings.gradle.kts new file mode 100644 index 00000000..ca7fe065 --- /dev/null +++ b/apps/apps/client/android/settings.gradle.kts @@ -0,0 +1,26 @@ +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.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/apps/apps/client/ios/.gitignore b/apps/apps/client/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/apps/apps/client/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/apps/apps/client/ios/Flutter/AppFrameworkInfo.plist b/apps/apps/client/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..1dc6cf76 --- /dev/null +++ b/apps/apps/client/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/apps/apps/client/ios/Flutter/Debug.xcconfig b/apps/apps/client/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/apps/apps/client/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/apps/apps/client/ios/Flutter/Release.xcconfig b/apps/apps/client/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/apps/apps/client/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/apps/apps/client/ios/Podfile b/apps/apps/client/ios/Podfile new file mode 100644 index 00000000..620e46eb --- /dev/null +++ b/apps/apps/client/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.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/apps/apps/client/ios/Runner.xcodeproj/project.pbxproj b/apps/apps/client/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..6da39393 --- /dev/null +++ b/apps/apps/client/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 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 = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* 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 = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.krowwithus.client; + 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.krowwithus.client; + 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.krowwithus.client; + 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.krowwithus.client; + 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 = 13.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 = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.krowwithus.client; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.krowwithus.client; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/apps/apps/client/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/apps/apps/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/apps/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..e3773d42 --- /dev/null +++ b/apps/apps/client/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/client/ios/Runner.xcworkspace/contents.xcworkspacedata b/apps/apps/client/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/apps/apps/client/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/apps/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/client/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/apps/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/apps/apps/client/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/apps/apps/client/ios/Runner/AppDelegate.swift b/apps/apps/client/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..62666446 --- /dev/null +++ b/apps/apps/client/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/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/apps/apps/client/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/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/apps/apps/client/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/apps/apps/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/apps/apps/client/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/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/apps/apps/client/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/apps/apps/client/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/apps/apps/client/ios/Runner/Base.lproj/LaunchScreen.storyboard b/apps/apps/client/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/apps/apps/client/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/client/ios/Runner/Base.lproj/Main.storyboard b/apps/apps/client/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/apps/apps/client/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/client/ios/Runner/Info.plist b/apps/apps/client/ios/Runner/Info.plist new file mode 100644 index 00000000..e7dfbac0 --- /dev/null +++ b/apps/apps/client/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + krowwithus_client + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + krowwithus_client + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/apps/apps/client/ios/Runner/Runner-Bridging-Header.h b/apps/apps/client/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/apps/apps/client/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/apps/apps/client/ios/RunnerTests/RunnerTests.swift b/apps/apps/client/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/apps/apps/client/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/apps/apps/client/lib/main.dart b/apps/apps/client/lib/main.dart new file mode 100644 index 00000000..8e8cf837 --- /dev/null +++ b/apps/apps/client/lib/main.dart @@ -0,0 +1,65 @@ +import 'package:core_localization/core_localization.dart' as core_localization; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:client_authentication/client_authentication.dart' + as client_authentication; +import 'package:client_home/client_home.dart' as client_home; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + runApp(ModularApp(module: AppModule(), child: const AppWidget())); +} + +/// The main application module for the Client app. +class AppModule extends Module { + @override + List get imports => [core_localization.LocalizationModule()]; + + @override + void routes(r) { + // Initial route points to the client authentication flow + r.module('/', module: client_authentication.ClientAuthenticationModule()); + + // Client home route + r.module('/client-home', module: client_home.ClientHomeModule()); + } +} + +class AppWidget extends StatelessWidget { + const AppWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + Modular.get() + ..add(const core_localization.LoadLocale()), + child: + BlocBuilder< + core_localization.LocaleBloc, + core_localization.LocaleState + >( + builder: (context, state) { + return core_localization.TranslationProvider( + child: MaterialApp.router( + debugShowCheckedModeBanner: false, + title: "Krow Client", + theme: UiTheme.light, + routerConfig: Modular.routerConfig, + locale: state.locale, + supportedLocales: state.supportedLocales, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + ), + ); + }, + ), + ); + } +} diff --git a/apps/apps/client/linux/.gitignore b/apps/apps/client/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/apps/apps/client/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/apps/apps/client/linux/CMakeLists.txt b/apps/apps/client/linux/CMakeLists.txt new file mode 100644 index 00000000..6f1df0fe --- /dev/null +++ b/apps/apps/client/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "krow_client") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.krow_client") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/apps/apps/client/linux/flutter/CMakeLists.txt b/apps/apps/client/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/apps/apps/client/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/apps/apps/client/linux/flutter/generated_plugin_registrant.cc b/apps/apps/client/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/apps/apps/client/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/apps/apps/client/linux/flutter/generated_plugin_registrant.h b/apps/apps/client/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/apps/apps/client/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/apps/client/linux/flutter/generated_plugins.cmake b/apps/apps/client/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..2e1de87a --- /dev/null +++ b/apps/apps/client/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/apps/client/linux/runner/CMakeLists.txt b/apps/apps/client/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/apps/apps/client/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/apps/apps/client/linux/runner/main.cc b/apps/apps/client/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/apps/apps/client/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/apps/apps/client/linux/runner/my_application.cc b/apps/apps/client/linux/runner/my_application.cc new file mode 100644 index 00000000..d5fe45fd --- /dev/null +++ b/apps/apps/client/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "krow_client"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "krow_client"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/apps/apps/client/linux/runner/my_application.h b/apps/apps/client/linux/runner/my_application.h new file mode 100644 index 00000000..db16367a --- /dev/null +++ b/apps/apps/client/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/apps/apps/client/macos/.gitignore b/apps/apps/client/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/apps/apps/client/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/apps/apps/client/macos/Flutter/Flutter-Debug.xcconfig b/apps/apps/client/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/apps/apps/client/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/apps/apps/client/macos/Flutter/Flutter-Release.xcconfig b/apps/apps/client/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/apps/apps/client/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/apps/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..724bb2ac --- /dev/null +++ b/apps/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import shared_preferences_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) +} diff --git a/apps/apps/client/macos/Podfile b/apps/apps/client/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/apps/apps/client/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_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_macos_build_settings(target) + end +end diff --git a/apps/apps/client/macos/Runner.xcodeproj/project.pbxproj b/apps/apps/client/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..613f0631 --- /dev/null +++ b/apps/apps/client/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* krow_client.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "krow_client.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* krow_client.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* krow_client.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowClient.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_client.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_client"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowClient.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_client.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_client"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowClient.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_client.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_client"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/apps/apps/client/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/client/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/client/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/apps/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..0e92a56b --- /dev/null +++ b/apps/apps/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/client/macos/Runner.xcworkspace/contents.xcworkspacedata b/apps/apps/client/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/apps/apps/client/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/apps/client/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/client/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/client/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/client/macos/Runner/AppDelegate.swift b/apps/apps/client/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/apps/apps/client/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/apps/apps/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/apps/apps/client/macos/Runner/Base.lproj/MainMenu.xib b/apps/apps/client/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/apps/apps/client/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/client/macos/Runner/Configs/AppInfo.xcconfig b/apps/apps/client/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..3dceff21 --- /dev/null +++ b/apps/apps/client/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = krow_client + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.krowClient + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/apps/apps/client/macos/Runner/Configs/Debug.xcconfig b/apps/apps/client/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/apps/apps/client/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/apps/apps/client/macos/Runner/Configs/Release.xcconfig b/apps/apps/client/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/apps/apps/client/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/apps/apps/client/macos/Runner/Configs/Warnings.xcconfig b/apps/apps/client/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/apps/apps/client/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/apps/apps/client/macos/Runner/DebugProfile.entitlements b/apps/apps/client/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/apps/apps/client/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/apps/apps/client/macos/Runner/Info.plist b/apps/apps/client/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/apps/apps/client/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/apps/apps/client/macos/Runner/MainFlutterWindow.swift b/apps/apps/client/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/apps/apps/client/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/apps/apps/client/macos/Runner/Release.entitlements b/apps/apps/client/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/apps/apps/client/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/apps/apps/client/macos/RunnerTests/RunnerTests.swift b/apps/apps/client/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/apps/apps/client/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/apps/apps/client/pubspec.yaml b/apps/apps/client/pubspec.yaml new file mode 100644 index 00000000..8550ff66 --- /dev/null +++ b/apps/apps/client/pubspec.yaml @@ -0,0 +1,34 @@ +name: krowwithus_client +description: "Krow Client Application" +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.10.7 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.8 + design_system: + path: ../../packages/design_system + client_authentication: + path: ../../packages/features/client/authentication + client_home: + path: ../../packages/features/client/home + core_localization: + path: ../../packages/core_localization + flutter_modular: ^6.3.2 + flutter_bloc: ^8.1.3 + flutter_localizations: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + rename: ^3.1.0 + +flutter: + uses-material-design: true diff --git a/apps/apps/client/test/widget_test.dart b/apps/apps/client/test/widget_test.dart new file mode 100644 index 00000000..1888d2ab --- /dev/null +++ b/apps/apps/client/test/widget_test.dart @@ -0,0 +1,30 @@ +// 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:client/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/apps/apps/client/web/favicon.png b/apps/apps/client/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/apps/apps/client/web/favicon.png differ diff --git a/apps/apps/client/web/icons/Icon-192.png b/apps/apps/client/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/apps/apps/client/web/icons/Icon-192.png differ diff --git a/apps/apps/client/web/icons/Icon-512.png b/apps/apps/client/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/apps/apps/client/web/icons/Icon-512.png differ diff --git a/apps/apps/client/web/icons/Icon-maskable-192.png b/apps/apps/client/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/apps/apps/client/web/icons/Icon-maskable-192.png differ diff --git a/apps/apps/client/web/icons/Icon-maskable-512.png b/apps/apps/client/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/apps/apps/client/web/icons/Icon-maskable-512.png differ diff --git a/apps/apps/client/web/index.html b/apps/apps/client/web/index.html new file mode 100644 index 00000000..0b6cda5a --- /dev/null +++ b/apps/apps/client/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + krow_client + + + + + + diff --git a/apps/apps/client/web/manifest.json b/apps/apps/client/web/manifest.json new file mode 100644 index 00000000..3f8812a2 --- /dev/null +++ b/apps/apps/client/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "krow_client", + "short_name": "krow_client", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/apps/apps/client/windows/.gitignore b/apps/apps/client/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/apps/apps/client/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/apps/apps/client/windows/CMakeLists.txt b/apps/apps/client/windows/CMakeLists.txt new file mode 100644 index 00000000..77e16d56 --- /dev/null +++ b/apps/apps/client/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(krow_client LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "krow_client") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/apps/apps/client/windows/flutter/CMakeLists.txt b/apps/apps/client/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/apps/apps/client/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/apps/apps/client/windows/flutter/generated_plugin_registrant.cc b/apps/apps/client/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/apps/apps/client/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/apps/apps/client/windows/flutter/generated_plugin_registrant.h b/apps/apps/client/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/apps/apps/client/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/apps/client/windows/flutter/generated_plugins.cmake b/apps/apps/client/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..b93c4c30 --- /dev/null +++ b/apps/apps/client/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/apps/client/windows/runner/CMakeLists.txt b/apps/apps/client/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/apps/apps/client/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/apps/apps/client/windows/runner/Runner.rc b/apps/apps/client/windows/runner/Runner.rc new file mode 100644 index 00000000..406f018c --- /dev/null +++ b/apps/apps/client/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "krow_client" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "krow_client" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "krow_client.exe" "\0" + VALUE "ProductName", "krow_client" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/apps/apps/client/windows/runner/flutter_window.cpp b/apps/apps/client/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/apps/apps/client/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/apps/apps/client/windows/runner/flutter_window.h b/apps/apps/client/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/apps/apps/client/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/apps/apps/client/windows/runner/main.cpp b/apps/apps/client/windows/runner/main.cpp new file mode 100644 index 00000000..2c445bb3 --- /dev/null +++ b/apps/apps/client/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"krow_client", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/apps/apps/client/windows/runner/resource.h b/apps/apps/client/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/apps/apps/client/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/apps/apps/client/windows/runner/resources/app_icon.ico b/apps/apps/client/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/apps/apps/client/windows/runner/resources/app_icon.ico differ diff --git a/apps/apps/client/windows/runner/runner.exe.manifest b/apps/apps/client/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/apps/apps/client/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/apps/apps/client/windows/runner/utils.cpp b/apps/apps/client/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/apps/apps/client/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/apps/apps/client/windows/runner/utils.h b/apps/apps/client/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/apps/apps/client/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/apps/apps/client/windows/runner/win32_window.cpp b/apps/apps/client/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/apps/apps/client/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/apps/apps/client/windows/runner/win32_window.h b/apps/apps/client/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/apps/apps/client/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/apps/apps/design_system_viewer/.gitignore b/apps/apps/design_system_viewer/.gitignore new file mode 100644 index 00000000..3820a95c --- /dev/null +++ b/apps/apps/design_system_viewer/.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-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# 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/apps/apps/design_system_viewer/.metadata b/apps/apps/design_system_viewer/.metadata new file mode 100644 index 00000000..08c24780 --- /dev/null +++ b/apps/apps/design_system_viewer/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: android + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: ios + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: linux + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: macos + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: web + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: windows + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + + # 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/apps/apps/design_system_viewer/analysis_options.yaml b/apps/apps/design_system_viewer/analysis_options.yaml new file mode 100644 index 00000000..856be8f8 --- /dev/null +++ b/apps/apps/design_system_viewer/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analytics_options.yaml \ No newline at end of file diff --git a/apps/apps/design_system_viewer/android/.gitignore b/apps/apps/design_system_viewer/android/.gitignore new file mode 100644 index 00000000..be3943c9 --- /dev/null +++ b/apps/apps/design_system_viewer/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/apps/apps/design_system_viewer/android/app/build.gradle.kts b/apps/apps/design_system_viewer/android/app/build.gradle.kts new file mode 100644 index 00000000..ba883b72 --- /dev/null +++ b/apps/apps/design_system_viewer/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.krow_design_system_viewer" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.krow_design_system_viewer" + // 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/apps/apps/design_system_viewer/android/app/src/debug/AndroidManifest.xml b/apps/apps/design_system_viewer/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/apps/apps/design_system_viewer/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/apps/apps/design_system_viewer/android/app/src/main/AndroidManifest.xml b/apps/apps/design_system_viewer/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..4ee81882 --- /dev/null +++ b/apps/apps/design_system_viewer/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/design_system_viewer/android/app/src/main/kotlin/com/example/krow_design_system_viewer/MainActivity.kt b/apps/apps/design_system_viewer/android/app/src/main/kotlin/com/example/krow_design_system_viewer/MainActivity.kt new file mode 100644 index 00000000..9f6aa558 --- /dev/null +++ b/apps/apps/design_system_viewer/android/app/src/main/kotlin/com/example/krow_design_system_viewer/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.krow_design_system_viewer + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/drawable-v21/launch_background.xml b/apps/apps/design_system_viewer/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/apps/apps/design_system_viewer/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/drawable/launch_background.xml b/apps/apps/design_system_viewer/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/apps/apps/design_system_viewer/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/apps/apps/design_system_viewer/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/values-night/styles.xml b/apps/apps/design_system_viewer/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/apps/apps/design_system_viewer/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/apps/design_system_viewer/android/app/src/main/res/values/styles.xml b/apps/apps/design_system_viewer/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/apps/apps/design_system_viewer/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/apps/design_system_viewer/android/app/src/profile/AndroidManifest.xml b/apps/apps/design_system_viewer/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/apps/apps/design_system_viewer/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/apps/apps/design_system_viewer/android/build.gradle.kts b/apps/apps/design_system_viewer/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/apps/apps/design_system_viewer/android/build.gradle.kts @@ -0,0 +1,24 @@ +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("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/apps/apps/design_system_viewer/android/gradle.properties b/apps/apps/design_system_viewer/android/gradle.properties new file mode 100644 index 00000000..fbee1d8c --- /dev/null +++ b/apps/apps/design_system_viewer/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/apps/apps/design_system_viewer/android/gradle/wrapper/gradle-wrapper.properties b/apps/apps/design_system_viewer/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e4ef43fb --- /dev/null +++ b/apps/apps/design_system_viewer/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.14-all.zip diff --git a/apps/apps/design_system_viewer/android/settings.gradle.kts b/apps/apps/design_system_viewer/android/settings.gradle.kts new file mode 100644 index 00000000..ca7fe065 --- /dev/null +++ b/apps/apps/design_system_viewer/android/settings.gradle.kts @@ -0,0 +1,26 @@ +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.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/apps/apps/design_system_viewer/ios/.gitignore b/apps/apps/design_system_viewer/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/apps/apps/design_system_viewer/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/apps/apps/design_system_viewer/ios/Flutter/AppFrameworkInfo.plist b/apps/apps/design_system_viewer/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..1dc6cf76 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/apps/apps/design_system_viewer/ios/Flutter/Debug.xcconfig b/apps/apps/design_system_viewer/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/apps/apps/design_system_viewer/ios/Flutter/Release.xcconfig b/apps/apps/design_system_viewer/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.pbxproj b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c782926e --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 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 = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* 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 = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowDesignSystemViewer; + 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.krowDesignSystemViewer.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.krowDesignSystemViewer.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.krowDesignSystemViewer.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 = 13.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 = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowDesignSystemViewer; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowDesignSystemViewer; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/apps/apps/design_system_viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..e3773d42 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/design_system_viewer/ios/Runner.xcworkspace/contents.xcworkspacedata b/apps/apps/design_system_viewer/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/apps/design_system_viewer/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/design_system_viewer/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/design_system_viewer/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/apps/design_system_viewer/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/apps/apps/design_system_viewer/ios/Runner/AppDelegate.swift b/apps/apps/design_system_viewer/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..62666446 --- /dev/null +++ b/apps/apps/design_system_viewer/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/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/apps/apps/design_system_viewer/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/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/apps/apps/design_system_viewer/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/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/apps/apps/design_system_viewer/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/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/apps/apps/design_system_viewer/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/apps/apps/design_system_viewer/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/apps/apps/design_system_viewer/ios/Runner/Base.lproj/LaunchScreen.storyboard b/apps/apps/design_system_viewer/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/design_system_viewer/ios/Runner/Base.lproj/Main.storyboard b/apps/apps/design_system_viewer/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/design_system_viewer/ios/Runner/Info.plist b/apps/apps/design_system_viewer/ios/Runner/Info.plist new file mode 100644 index 00000000..0c4dda83 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Krow Design System Viewer + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + krow_design_system_viewer + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/apps/apps/design_system_viewer/ios/Runner/Runner-Bridging-Header.h b/apps/apps/design_system_viewer/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/apps/apps/design_system_viewer/ios/RunnerTests/RunnerTests.swift b/apps/apps/design_system_viewer/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/apps/apps/design_system_viewer/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/apps/apps/design_system_viewer/lib/main.dart b/apps/apps/design_system_viewer/lib/main.dart new file mode 100644 index 00000000..244a702c --- /dev/null +++ b/apps/apps/design_system_viewer/lib/main.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // TRY THIS: Try running your application with "flutter run". You'll see + // the application has a purple toolbar. Then, without quitting the app, + // try changing the seedColor in the colorScheme below to Colors.green + // and then invoke "hot reload" (save your changes or press the "hot + // reload" button in a Flutter-supported IDE, or press "r" if you used + // the command line to start the app). + // + // Notice that the counter didn't reset back to zero; the application + // state is not lost during the reload. To reset the state, use hot + // restart instead. + // + // This works for code too, not just values: Most code changes can be + // tested with just a hot reload. + colorScheme: .fromSeed(seedColor: Colors.deepPurple), + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // TRY THIS: Try changing the color here to a specific color (to + // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar + // change color while the other colors stay the same. + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + // + // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" + // action in the IDE, or press "p" in the console), to see the + // wireframe for each widget. + mainAxisAlignment: .center, + children: [ + const Text('You have pushed the button this many times:'), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/apps/apps/design_system_viewer/linux/.gitignore b/apps/apps/design_system_viewer/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/apps/apps/design_system_viewer/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/apps/apps/design_system_viewer/linux/CMakeLists.txt b/apps/apps/design_system_viewer/linux/CMakeLists.txt new file mode 100644 index 00000000..2ea8bc94 --- /dev/null +++ b/apps/apps/design_system_viewer/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "krow_design_system_viewer") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.krow_design_system_viewer") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/apps/apps/design_system_viewer/linux/flutter/CMakeLists.txt b/apps/apps/design_system_viewer/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/apps/apps/design_system_viewer/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/apps/apps/design_system_viewer/linux/flutter/generated_plugin_registrant.cc b/apps/apps/design_system_viewer/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/apps/apps/design_system_viewer/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/apps/apps/design_system_viewer/linux/flutter/generated_plugin_registrant.h b/apps/apps/design_system_viewer/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/apps/apps/design_system_viewer/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/apps/design_system_viewer/linux/flutter/generated_plugins.cmake b/apps/apps/design_system_viewer/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..2e1de87a --- /dev/null +++ b/apps/apps/design_system_viewer/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/apps/design_system_viewer/linux/runner/CMakeLists.txt b/apps/apps/design_system_viewer/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/apps/apps/design_system_viewer/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/apps/apps/design_system_viewer/linux/runner/main.cc b/apps/apps/design_system_viewer/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/apps/apps/design_system_viewer/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/apps/apps/design_system_viewer/linux/runner/my_application.cc b/apps/apps/design_system_viewer/linux/runner/my_application.cc new file mode 100644 index 00000000..9a57dad4 --- /dev/null +++ b/apps/apps/design_system_viewer/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "krow_design_system_viewer"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "krow_design_system_viewer"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/apps/apps/design_system_viewer/linux/runner/my_application.h b/apps/apps/design_system_viewer/linux/runner/my_application.h new file mode 100644 index 00000000..db16367a --- /dev/null +++ b/apps/apps/design_system_viewer/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/apps/apps/design_system_viewer/macos/.gitignore b/apps/apps/design_system_viewer/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/apps/apps/design_system_viewer/macos/Flutter/Flutter-Debug.xcconfig b/apps/apps/design_system_viewer/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/apps/apps/design_system_viewer/macos/Flutter/Flutter-Release.xcconfig b/apps/apps/design_system_viewer/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/apps/apps/design_system_viewer/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/apps/design_system_viewer/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..cccf817a --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/apps/apps/design_system_viewer/macos/Runner.xcodeproj/project.pbxproj b/apps/apps/design_system_viewer/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..37a481ad --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* krow_design_system_viewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "krow_design_system_viewer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* krow_design_system_viewer.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* krow_design_system_viewer.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowDesignSystemViewer.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_design_system_viewer.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_design_system_viewer"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowDesignSystemViewer.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_design_system_viewer.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_design_system_viewer"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowDesignSystemViewer.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_design_system_viewer.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_design_system_viewer"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/apps/apps/design_system_viewer/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/design_system_viewer/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/design_system_viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/apps/design_system_viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..bbf718ff --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/design_system_viewer/macos/Runner.xcworkspace/contents.xcworkspacedata b/apps/apps/design_system_viewer/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/apps/design_system_viewer/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/design_system_viewer/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/design_system_viewer/macos/Runner/AppDelegate.swift b/apps/apps/design_system_viewer/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/apps/apps/design_system_viewer/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/apps/apps/design_system_viewer/macos/Runner/Base.lproj/MainMenu.xib b/apps/apps/design_system_viewer/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/design_system_viewer/macos/Runner/Configs/AppInfo.xcconfig b/apps/apps/design_system_viewer/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..b45ff361 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = krow_design_system_viewer + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.krowDesignSystemViewer + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/apps/apps/design_system_viewer/macos/Runner/Configs/Debug.xcconfig b/apps/apps/design_system_viewer/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/apps/apps/design_system_viewer/macos/Runner/Configs/Release.xcconfig b/apps/apps/design_system_viewer/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/apps/apps/design_system_viewer/macos/Runner/Configs/Warnings.xcconfig b/apps/apps/design_system_viewer/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/apps/apps/design_system_viewer/macos/Runner/DebugProfile.entitlements b/apps/apps/design_system_viewer/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/apps/apps/design_system_viewer/macos/Runner/Info.plist b/apps/apps/design_system_viewer/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/apps/apps/design_system_viewer/macos/Runner/MainFlutterWindow.swift b/apps/apps/design_system_viewer/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/apps/apps/design_system_viewer/macos/Runner/Release.entitlements b/apps/apps/design_system_viewer/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/apps/apps/design_system_viewer/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/apps/apps/design_system_viewer/macos/RunnerTests/RunnerTests.swift b/apps/apps/design_system_viewer/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/apps/apps/design_system_viewer/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/apps/apps/design_system_viewer/pubspec.yaml b/apps/apps/design_system_viewer/pubspec.yaml new file mode 100644 index 00000000..c96bbd77 --- /dev/null +++ b/apps/apps/design_system_viewer/pubspec.yaml @@ -0,0 +1,90 @@ +name: design_system_viewer +description: "A new Flutter project." +# 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 + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.10.7 + +# 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 + + # 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: + 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: ^6.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/apps/apps/design_system_viewer/test/widget_test.dart b/apps/apps/design_system_viewer/test/widget_test.dart new file mode 100644 index 00000000..59ec1d1b --- /dev/null +++ b/apps/apps/design_system_viewer/test/widget_test.dart @@ -0,0 +1,30 @@ +// 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:design_system_viewer/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/apps/apps/design_system_viewer/web/favicon.png b/apps/apps/design_system_viewer/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/apps/apps/design_system_viewer/web/favicon.png differ diff --git a/apps/apps/design_system_viewer/web/icons/Icon-192.png b/apps/apps/design_system_viewer/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/apps/apps/design_system_viewer/web/icons/Icon-192.png differ diff --git a/apps/apps/design_system_viewer/web/icons/Icon-512.png b/apps/apps/design_system_viewer/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/apps/apps/design_system_viewer/web/icons/Icon-512.png differ diff --git a/apps/apps/design_system_viewer/web/icons/Icon-maskable-192.png b/apps/apps/design_system_viewer/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/apps/apps/design_system_viewer/web/icons/Icon-maskable-192.png differ diff --git a/apps/apps/design_system_viewer/web/icons/Icon-maskable-512.png b/apps/apps/design_system_viewer/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/apps/apps/design_system_viewer/web/icons/Icon-maskable-512.png differ diff --git a/apps/apps/design_system_viewer/web/index.html b/apps/apps/design_system_viewer/web/index.html new file mode 100644 index 00000000..6105e798 --- /dev/null +++ b/apps/apps/design_system_viewer/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + krow_design_system_viewer + + + + + + diff --git a/apps/apps/design_system_viewer/web/manifest.json b/apps/apps/design_system_viewer/web/manifest.json new file mode 100644 index 00000000..19d97c3e --- /dev/null +++ b/apps/apps/design_system_viewer/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "krow_design_system_viewer", + "short_name": "krow_design_system_viewer", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/apps/apps/design_system_viewer/windows/.gitignore b/apps/apps/design_system_viewer/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/apps/apps/design_system_viewer/windows/CMakeLists.txt b/apps/apps/design_system_viewer/windows/CMakeLists.txt new file mode 100644 index 00000000..e7ce1a4f --- /dev/null +++ b/apps/apps/design_system_viewer/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(krow_design_system_viewer LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "krow_design_system_viewer") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/apps/apps/design_system_viewer/windows/flutter/CMakeLists.txt b/apps/apps/design_system_viewer/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/apps/apps/design_system_viewer/windows/flutter/generated_plugin_registrant.cc b/apps/apps/design_system_viewer/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/apps/apps/design_system_viewer/windows/flutter/generated_plugin_registrant.h b/apps/apps/design_system_viewer/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/apps/design_system_viewer/windows/flutter/generated_plugins.cmake b/apps/apps/design_system_viewer/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..b93c4c30 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/apps/design_system_viewer/windows/runner/CMakeLists.txt b/apps/apps/design_system_viewer/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/apps/apps/design_system_viewer/windows/runner/Runner.rc b/apps/apps/design_system_viewer/windows/runner/Runner.rc new file mode 100644 index 00000000..9447ef0f --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "krow_design_system_viewer" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "krow_design_system_viewer" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "krow_design_system_viewer.exe" "\0" + VALUE "ProductName", "krow_design_system_viewer" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/apps/apps/design_system_viewer/windows/runner/flutter_window.cpp b/apps/apps/design_system_viewer/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/apps/apps/design_system_viewer/windows/runner/flutter_window.h b/apps/apps/design_system_viewer/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/apps/apps/design_system_viewer/windows/runner/main.cpp b/apps/apps/design_system_viewer/windows/runner/main.cpp new file mode 100644 index 00000000..adbbdb30 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"krow_design_system_viewer", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/apps/apps/design_system_viewer/windows/runner/resource.h b/apps/apps/design_system_viewer/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/apps/apps/design_system_viewer/windows/runner/resources/app_icon.ico b/apps/apps/design_system_viewer/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/apps/apps/design_system_viewer/windows/runner/resources/app_icon.ico differ diff --git a/apps/apps/design_system_viewer/windows/runner/runner.exe.manifest b/apps/apps/design_system_viewer/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/apps/apps/design_system_viewer/windows/runner/utils.cpp b/apps/apps/design_system_viewer/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/apps/apps/design_system_viewer/windows/runner/utils.h b/apps/apps/design_system_viewer/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/apps/apps/design_system_viewer/windows/runner/win32_window.cpp b/apps/apps/design_system_viewer/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/apps/apps/design_system_viewer/windows/runner/win32_window.h b/apps/apps/design_system_viewer/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/apps/apps/design_system_viewer/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/apps/apps/staff/.gitignore b/apps/apps/staff/.gitignore new file mode 100644 index 00000000..3820a95c --- /dev/null +++ b/apps/apps/staff/.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-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# 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/apps/apps/staff/.metadata b/apps/apps/staff/.metadata new file mode 100644 index 00000000..10fc6261 --- /dev/null +++ b/apps/apps/staff/.metadata @@ -0,0 +1,30 @@ +# 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: "3b62efc2a3da49882f43c372e0bc53daef7295a6" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + - platform: ios + create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + + # 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/apps/apps/staff/README.md b/apps/apps/staff/README.md new file mode 100644 index 00000000..54a11c2f --- /dev/null +++ b/apps/apps/staff/README.md @@ -0,0 +1,3 @@ +# krowwithus_staff + +A new Flutter project. diff --git a/apps/apps/staff/analysis_options.yaml b/apps/apps/staff/analysis_options.yaml new file mode 100644 index 00000000..856be8f8 --- /dev/null +++ b/apps/apps/staff/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analytics_options.yaml \ No newline at end of file diff --git a/apps/apps/staff/android/.gitignore b/apps/apps/staff/android/.gitignore new file mode 100644 index 00000000..be3943c9 --- /dev/null +++ b/apps/apps/staff/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/apps/apps/staff/android/app/build.gradle.kts b/apps/apps/staff/android/app/build.gradle.kts new file mode 100644 index 00000000..0f75ed01 --- /dev/null +++ b/apps/apps/staff/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.krow_staff" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.krowwithus.staff" + // 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/apps/apps/staff/android/app/src/debug/AndroidManifest.xml b/apps/apps/staff/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/apps/apps/staff/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/apps/apps/staff/android/app/src/main/AndroidManifest.xml b/apps/apps/staff/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a80e0f85 --- /dev/null +++ b/apps/apps/staff/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/staff/android/app/src/main/kotlin/com/example/krow_staff/MainActivity.kt b/apps/apps/staff/android/app/src/main/kotlin/com/example/krow_staff/MainActivity.kt new file mode 100644 index 00000000..13520833 --- /dev/null +++ b/apps/apps/staff/android/app/src/main/kotlin/com/example/krow_staff/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.krow_staff + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/apps/apps/staff/android/app/src/main/kotlin/com/krowwithus/krowwithus_staff/MainActivity.kt b/apps/apps/staff/android/app/src/main/kotlin/com/krowwithus/krowwithus_staff/MainActivity.kt new file mode 100644 index 00000000..994d7695 --- /dev/null +++ b/apps/apps/staff/android/app/src/main/kotlin/com/krowwithus/krowwithus_staff/MainActivity.kt @@ -0,0 +1,5 @@ +package com.krowwithus.krowwithus_staff + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/apps/apps/staff/android/app/src/main/res/drawable-v21/launch_background.xml b/apps/apps/staff/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/apps/apps/staff/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/apps/staff/android/app/src/main/res/drawable/launch_background.xml b/apps/apps/staff/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/apps/apps/staff/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/apps/staff/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/apps/staff/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/apps/apps/staff/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/apps/staff/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/apps/staff/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/apps/apps/staff/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/apps/staff/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/apps/staff/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/apps/apps/staff/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/apps/staff/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/apps/staff/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/apps/apps/staff/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/apps/staff/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/apps/staff/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/apps/apps/staff/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/apps/staff/android/app/src/main/res/values-night/styles.xml b/apps/apps/staff/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/apps/apps/staff/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/apps/staff/android/app/src/main/res/values/styles.xml b/apps/apps/staff/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/apps/apps/staff/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/apps/staff/android/app/src/profile/AndroidManifest.xml b/apps/apps/staff/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/apps/apps/staff/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/apps/apps/staff/android/build.gradle.kts b/apps/apps/staff/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/apps/apps/staff/android/build.gradle.kts @@ -0,0 +1,24 @@ +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("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/apps/apps/staff/android/gradle.properties b/apps/apps/staff/android/gradle.properties new file mode 100644 index 00000000..fbee1d8c --- /dev/null +++ b/apps/apps/staff/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/apps/apps/staff/android/gradle/wrapper/gradle-wrapper.properties b/apps/apps/staff/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e4ef43fb --- /dev/null +++ b/apps/apps/staff/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.14-all.zip diff --git a/apps/apps/staff/android/settings.gradle.kts b/apps/apps/staff/android/settings.gradle.kts new file mode 100644 index 00000000..ca7fe065 --- /dev/null +++ b/apps/apps/staff/android/settings.gradle.kts @@ -0,0 +1,26 @@ +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.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/apps/apps/staff/ios/.gitignore b/apps/apps/staff/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/apps/apps/staff/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/apps/apps/staff/ios/Flutter/AppFrameworkInfo.plist b/apps/apps/staff/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..1dc6cf76 --- /dev/null +++ b/apps/apps/staff/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/apps/apps/staff/ios/Flutter/Debug.xcconfig b/apps/apps/staff/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/apps/apps/staff/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/apps/apps/staff/ios/Flutter/Release.xcconfig b/apps/apps/staff/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/apps/apps/staff/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/apps/apps/staff/ios/Podfile b/apps/apps/staff/ios/Podfile new file mode 100644 index 00000000..620e46eb --- /dev/null +++ b/apps/apps/staff/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.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/apps/apps/staff/ios/Runner.xcodeproj/project.pbxproj b/apps/apps/staff/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..a377333e --- /dev/null +++ b/apps/apps/staff/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 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 = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* 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 = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.krowwithus.staff; + 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.krowwithus.staff; + 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.krowwithus.staff; + 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.krowwithus.staff; + 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 = 13.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 = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.krowwithus.staff; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.krowwithus.staff; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/apps/apps/staff/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/apps/apps/staff/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/apps/staff/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..e3773d42 --- /dev/null +++ b/apps/apps/staff/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/staff/ios/Runner.xcworkspace/contents.xcworkspacedata b/apps/apps/staff/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/apps/apps/staff/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/apps/staff/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/staff/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/staff/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/staff/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/apps/staff/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/apps/apps/staff/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/apps/apps/staff/ios/Runner/AppDelegate.swift b/apps/apps/staff/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..62666446 --- /dev/null +++ b/apps/apps/staff/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/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/apps/apps/staff/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/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/apps/apps/staff/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/apps/apps/staff/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/apps/apps/staff/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/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/apps/apps/staff/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/apps/apps/staff/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/apps/apps/staff/ios/Runner/Base.lproj/LaunchScreen.storyboard b/apps/apps/staff/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/apps/apps/staff/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/staff/ios/Runner/Base.lproj/Main.storyboard b/apps/apps/staff/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/apps/apps/staff/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/staff/ios/Runner/Info.plist b/apps/apps/staff/ios/Runner/Info.plist new file mode 100644 index 00000000..73018413 --- /dev/null +++ b/apps/apps/staff/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + krowwithus_staff + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + krowwithus_staff + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/apps/apps/staff/ios/Runner/Runner-Bridging-Header.h b/apps/apps/staff/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/apps/apps/staff/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/apps/apps/staff/ios/RunnerTests/RunnerTests.swift b/apps/apps/staff/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/apps/apps/staff/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/apps/apps/staff/lib/main.dart b/apps/apps/staff/lib/main.dart new file mode 100644 index 00000000..cbfcaf74 --- /dev/null +++ b/apps/apps/staff/lib/main.dart @@ -0,0 +1,67 @@ +import 'package:core_localization/core_localization.dart' as core_localization; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:staff_authentication/staff_authentication.dart' + as staff_authentication; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + runApp(ModularApp(module: AppModule(), child: const AppWidget())); +} + +/// The main application module. +class AppModule extends Module { + @override + List get imports => [core_localization.LocalizationModule()]; + + @override + void routes(r) { + // Set the initial route to the authentication module + r.module("/", module: staff_authentication.StaffAuthenticationModule()); + + // Placeholder for home route (referenced in auth feature) + r.child( + "/worker-home", + child: (_) => const Scaffold( + body: Center(child: Text("Worker Home - To Be Implemented")), + ), + ); + } +} + +class AppWidget extends StatelessWidget { + const AppWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + Modular.get() + ..add(const core_localization.LoadLocale()), + child: + BlocBuilder< + core_localization.LocaleBloc, + core_localization.LocaleState + >( + builder: (context, state) { + return MaterialApp.router( + title: "KROW Staff", + theme: UiTheme.light, + routerConfig: Modular.routerConfig, + locale: state.locale, + supportedLocales: state.supportedLocales, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + ); + }, + ), + ); + } +} diff --git a/apps/apps/staff/linux/.gitignore b/apps/apps/staff/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/apps/apps/staff/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/apps/apps/staff/linux/CMakeLists.txt b/apps/apps/staff/linux/CMakeLists.txt new file mode 100644 index 00000000..b222a83e --- /dev/null +++ b/apps/apps/staff/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "krow_staff") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.krow_staff") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/apps/apps/staff/linux/flutter/CMakeLists.txt b/apps/apps/staff/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/apps/apps/staff/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/apps/apps/staff/linux/flutter/generated_plugin_registrant.cc b/apps/apps/staff/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/apps/apps/staff/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/apps/apps/staff/linux/flutter/generated_plugin_registrant.h b/apps/apps/staff/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/apps/apps/staff/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/apps/staff/linux/flutter/generated_plugins.cmake b/apps/apps/staff/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..2e1de87a --- /dev/null +++ b/apps/apps/staff/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/apps/staff/linux/runner/CMakeLists.txt b/apps/apps/staff/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/apps/apps/staff/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/apps/apps/staff/linux/runner/main.cc b/apps/apps/staff/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/apps/apps/staff/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/apps/apps/staff/linux/runner/my_application.cc b/apps/apps/staff/linux/runner/my_application.cc new file mode 100644 index 00000000..d0bb4280 --- /dev/null +++ b/apps/apps/staff/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "krow_staff"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "krow_staff"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/apps/apps/staff/linux/runner/my_application.h b/apps/apps/staff/linux/runner/my_application.h new file mode 100644 index 00000000..db16367a --- /dev/null +++ b/apps/apps/staff/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/apps/apps/staff/macos/.gitignore b/apps/apps/staff/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/apps/apps/staff/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/apps/apps/staff/macos/Flutter/Flutter-Debug.xcconfig b/apps/apps/staff/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/apps/apps/staff/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/apps/apps/staff/macos/Flutter/Flutter-Release.xcconfig b/apps/apps/staff/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/apps/apps/staff/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/apps/apps/staff/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/apps/staff/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..724bb2ac --- /dev/null +++ b/apps/apps/staff/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import shared_preferences_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) +} diff --git a/apps/apps/staff/macos/Podfile b/apps/apps/staff/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/apps/apps/staff/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_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_macos_build_settings(target) + end +end diff --git a/apps/apps/staff/macos/Podfile.lock b/apps/apps/staff/macos/Podfile.lock new file mode 100644 index 00000000..1385d0fb --- /dev/null +++ b/apps/apps/staff/macos/Podfile.lock @@ -0,0 +1,23 @@ +PODS: + - FlutterMacOS (1.0.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + +SPEC CHECKSUMS: + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/apps/apps/staff/macos/Runner.xcodeproj/project.pbxproj b/apps/apps/staff/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..7b0274bc --- /dev/null +++ b/apps/apps/staff/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 412DA1D6D757DD2D1DEDC778 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 069E89A5AB8E920696C344DA /* Pods_RunnerTests.framework */; }; + F61791A921ED082C9512CA02 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8AEFAD6205BEEBA5D907529 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 060EC3AF3A2AB752545F2191 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 069E89A5AB8E920696C344DA /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 15C63A664E0150CA184E03A8 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* krow_staff.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = krow_staff.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 6D300BB405A262BD11BC8E17 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + C1453B9ED71BA9085495FCB6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + C24103C2702CAEE0091BFC73 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D8AEFAD6205BEEBA5D907529 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E11FE7E42C3C39C49F50BEB5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 412DA1D6D757DD2D1DEDC778 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F61791A921ED082C9512CA02 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 6ABA80E4B64F392061994EFD /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* krow_staff.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 6ABA80E4B64F392061994EFD /* Pods */ = { + isa = PBXGroup; + children = ( + 6D300BB405A262BD11BC8E17 /* Pods-Runner.debug.xcconfig */, + E11FE7E42C3C39C49F50BEB5 /* Pods-Runner.release.xcconfig */, + 060EC3AF3A2AB752545F2191 /* Pods-Runner.profile.xcconfig */, + 15C63A664E0150CA184E03A8 /* Pods-RunnerTests.debug.xcconfig */, + C24103C2702CAEE0091BFC73 /* Pods-RunnerTests.release.xcconfig */, + C1453B9ED71BA9085495FCB6 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D8AEFAD6205BEEBA5D907529 /* Pods_Runner.framework */, + 069E89A5AB8E920696C344DA /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + B5C2C461D64A106CDA5480B9 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 4EAF5AFAFCC94D6A860EEA53 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 03022428CF28990CD27A7DBC /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* krow_staff.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 03022428CF28990CD27A7DBC /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 4EAF5AFAFCC94D6A860EEA53 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B5C2C461D64A106CDA5480B9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 15C63A664E0150CA184E03A8 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowStaff.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_staff.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_staff"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C24103C2702CAEE0091BFC73 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowStaff.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_staff.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_staff"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C1453B9ED71BA9085495FCB6 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.krowStaff.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/krow_staff.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/krow_staff"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/apps/apps/staff/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/staff/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/staff/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/staff/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/apps/staff/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..23783d1d --- /dev/null +++ b/apps/apps/staff/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/staff/macos/Runner.xcworkspace/contents.xcworkspacedata b/apps/apps/staff/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/apps/apps/staff/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/apps/apps/staff/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/apps/staff/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/apps/staff/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/apps/staff/macos/Runner/AppDelegate.swift b/apps/apps/staff/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/apps/apps/staff/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/apps/apps/staff/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/apps/apps/staff/macos/Runner/Base.lproj/MainMenu.xib b/apps/apps/staff/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/apps/apps/staff/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/apps/staff/macos/Runner/Configs/AppInfo.xcconfig b/apps/apps/staff/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..721d6ca0 --- /dev/null +++ b/apps/apps/staff/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = krow_staff + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.krowStaff + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/apps/apps/staff/macos/Runner/Configs/Debug.xcconfig b/apps/apps/staff/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/apps/apps/staff/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/apps/apps/staff/macos/Runner/Configs/Release.xcconfig b/apps/apps/staff/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/apps/apps/staff/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/apps/apps/staff/macos/Runner/Configs/Warnings.xcconfig b/apps/apps/staff/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/apps/apps/staff/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/apps/apps/staff/macos/Runner/DebugProfile.entitlements b/apps/apps/staff/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/apps/apps/staff/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/apps/apps/staff/macos/Runner/Info.plist b/apps/apps/staff/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/apps/apps/staff/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/apps/apps/staff/macos/Runner/MainFlutterWindow.swift b/apps/apps/staff/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/apps/apps/staff/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/apps/apps/staff/macos/Runner/Release.entitlements b/apps/apps/staff/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/apps/apps/staff/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/apps/apps/staff/macos/RunnerTests/RunnerTests.swift b/apps/apps/staff/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/apps/apps/staff/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/apps/apps/staff/pubspec.yaml b/apps/apps/staff/pubspec.yaml new file mode 100644 index 00000000..659a41ad --- /dev/null +++ b/apps/apps/staff/pubspec.yaml @@ -0,0 +1,30 @@ +name: krowwithus_staff +description: "Krow Staff Application" +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.10.7 +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + cupertino_icons: ^1.0.8 + flutter_modular: ^6.3.0 + design_system: + path: ../../packages/design_system + staff_authentication: + path: ../../packages/features/staff/authentication + core_localization: + path: ../../packages/core_localization + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + rename: ^3.1.0 + +flutter: + uses-material-design: true diff --git a/apps/apps/staff/test/widget_test.dart b/apps/apps/staff/test/widget_test.dart new file mode 100644 index 00000000..1e60e4a3 --- /dev/null +++ b/apps/apps/staff/test/widget_test.dart @@ -0,0 +1,30 @@ +// 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:staff/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/apps/apps/staff/web/favicon.png b/apps/apps/staff/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/apps/apps/staff/web/favicon.png differ diff --git a/apps/apps/staff/web/icons/Icon-192.png b/apps/apps/staff/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/apps/apps/staff/web/icons/Icon-192.png differ diff --git a/apps/apps/staff/web/icons/Icon-512.png b/apps/apps/staff/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/apps/apps/staff/web/icons/Icon-512.png differ diff --git a/apps/apps/staff/web/icons/Icon-maskable-192.png b/apps/apps/staff/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/apps/apps/staff/web/icons/Icon-maskable-192.png differ diff --git a/apps/apps/staff/web/icons/Icon-maskable-512.png b/apps/apps/staff/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/apps/apps/staff/web/icons/Icon-maskable-512.png differ diff --git a/apps/apps/staff/web/index.html b/apps/apps/staff/web/index.html new file mode 100644 index 00000000..e03e65a3 --- /dev/null +++ b/apps/apps/staff/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + krow_staff + + + + + + diff --git a/apps/apps/staff/web/manifest.json b/apps/apps/staff/web/manifest.json new file mode 100644 index 00000000..39e502ff --- /dev/null +++ b/apps/apps/staff/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "krow_staff", + "short_name": "krow_staff", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/apps/apps/staff/windows/.gitignore b/apps/apps/staff/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/apps/apps/staff/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/apps/apps/staff/windows/CMakeLists.txt b/apps/apps/staff/windows/CMakeLists.txt new file mode 100644 index 00000000..95f1489e --- /dev/null +++ b/apps/apps/staff/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(krow_staff LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "krow_staff") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/apps/apps/staff/windows/flutter/CMakeLists.txt b/apps/apps/staff/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/apps/apps/staff/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/apps/apps/staff/windows/flutter/generated_plugin_registrant.cc b/apps/apps/staff/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/apps/apps/staff/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/apps/apps/staff/windows/flutter/generated_plugin_registrant.h b/apps/apps/staff/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/apps/apps/staff/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/apps/staff/windows/flutter/generated_plugins.cmake b/apps/apps/staff/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..b93c4c30 --- /dev/null +++ b/apps/apps/staff/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/apps/staff/windows/runner/CMakeLists.txt b/apps/apps/staff/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/apps/apps/staff/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/apps/apps/staff/windows/runner/Runner.rc b/apps/apps/staff/windows/runner/Runner.rc new file mode 100644 index 00000000..1338842e --- /dev/null +++ b/apps/apps/staff/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "krow_staff" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "krow_staff" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "krow_staff.exe" "\0" + VALUE "ProductName", "krow_staff" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/apps/apps/staff/windows/runner/flutter_window.cpp b/apps/apps/staff/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/apps/apps/staff/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/apps/apps/staff/windows/runner/flutter_window.h b/apps/apps/staff/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/apps/apps/staff/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/apps/apps/staff/windows/runner/main.cpp b/apps/apps/staff/windows/runner/main.cpp new file mode 100644 index 00000000..330b294b --- /dev/null +++ b/apps/apps/staff/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"krow_staff", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/apps/apps/staff/windows/runner/resource.h b/apps/apps/staff/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/apps/apps/staff/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/apps/apps/staff/windows/runner/resources/app_icon.ico b/apps/apps/staff/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/apps/apps/staff/windows/runner/resources/app_icon.ico differ diff --git a/apps/apps/staff/windows/runner/runner.exe.manifest b/apps/apps/staff/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/apps/apps/staff/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/apps/apps/staff/windows/runner/utils.cpp b/apps/apps/staff/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/apps/apps/staff/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/apps/apps/staff/windows/runner/utils.h b/apps/apps/staff/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/apps/apps/staff/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/apps/apps/staff/windows/runner/win32_window.cpp b/apps/apps/staff/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/apps/apps/staff/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/apps/apps/staff/windows/runner/win32_window.h b/apps/apps/staff/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/apps/apps/staff/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/apps/docs/01-architecture-principles.md b/apps/docs/01-architecture-principles.md new file mode 100644 index 00000000..2a38444a --- /dev/null +++ b/apps/docs/01-architecture-principles.md @@ -0,0 +1,134 @@ +# KROW Architecture Principles + +This document is the **AUTHORITATIVE** source of truth for the KROW engineering architecture. +All agents and engineers must adhere strictly to these principles. Deviations are interpreted as errors. + +## 1. High-Level Architecture + +The KROW platform follows a strict **Clean Architecture** implementation within a **Melos Monorepo**. +Dependencies flow **inwards** towards the Domain. + +```mermaid +graph TD + subgraph "Apps (Entry Points)" + ClientApp[apps/client] + StaffApp[apps/staff] + end + + subgraph "Features (Presentation & Application)" + ClientFeature[packages/features/client/jobs] + StaffFeature[packages/features/staff/schedule] + SharedFeature[packages/features/shared/auth] + end + + subgraph "Interface Adapters" + DataConnect[packages/data_connect] + DesignSystem[packages/design_system] + end + + subgraph "Core Domain" + Domain[packages/domain] + Core[packages/core] + end + + %% Dependency Flow + ClientApp --> ClientFeature & SharedFeature + StaffApp --> StaffFeature & SharedFeature + ClientApp --> DataConnect + StaffApp --> DataConnect + + ClientFeature & StaffFeature & SharedFeature --> Domain + ClientFeature & StaffFeature & SharedFeature --> DesignSystem + ClientFeature & StaffFeature & SharedFeature --> Core + + DataConnect --> Domain + DataConnect --> Core + DesignSystem --> Core + Domain --> Core + + %% Strict Barriers + linkStyle default stroke-width:2px,fill:none,stroke:gray +``` + +## 2. Repository Structure & Package Roles + +### 2.1 Apps (`apps/`) +- **Role**: Application entry points and Dependency Injection (DI) roots. +- **Responsibilities**: + - Initialize Flutter Modular. + - Assemble features into a navigation tree. + - Inject concrete implementations (from `data_connect`) into Feature packages. + - Configure environment-specific settings. +- **RESTRICTION**: NO business logic. NO UI widgets (except `App` and `Main`). + +### 2.2 Features (`packages/features//`) +- **Role**: Vertical slices of user-facing functionality. +- **Internal Structure**: + - `domain/`: Feature-specific Use Cases and Repository Interfaces. + - `data/`: Repository Implementations. + - `presentation/`: + - Pages, BLoCs, Widgets. + - For performance make the pages as `StatelessWidget` and move the state management to the BLoC or `StatefulWidget` to an external separate widget file. +- **Responsibilities**: + - **Presentation**: UI Pages, Modular Routes. + - **State Management**: BLoCs / Cubits. + - **Application Logic**: Use Cases. +- **RESTRICTION**: Features MUST NOT import other features. Communication happens via shared domain events. + +### 2.3 Domain (`packages/domain`) +- **Role**: The stable heart of the system. Pure Dart. +- **Responsibilities**: + - **Entities**: Immutable data models (Data Classes). + - **Failures**: Domain-specific error types. +- **RESTRICTION**: NO Flutter dependencies. NO `json_annotation`. NO package dependencies (except `equatable`). + +### 2.4 Data Connect (`packages/data_connect`) +- **Role**: Interface Adapter for Backend Access (Datasource Layer). +- **Responsibilities**: + - Implement low-level Datasources or generated SDK wrappers. + - map Domain Entities to/from Firebase Data Connect generated code. + - Handle Firebase exceptions. + +### 2.5 Design System (`packages/design_system`) +- **Role**: Visual language and component library. +- **Responsibilities**: + - UI components if needed. But mostly try to modify the theme file (packages/design_system/lib/src/ui_theme.dart) so we can directly use the theme in the app, to use the default material widgets. + - If not possible, and if that specific widget is used in multiple features, then try to create a shared widget in the `packages/design_system/widgets`. + - Theme definitions (Colors, Typography). + - Assets (Icons, Images). + - More details on how to use this package is available in the `docs/03-design-system-usage.md`. +- **RESTRICTION**: + - CANNOT change colours or typography. + - Dumb widgets only. NO business logic. NO state management (Bloc). + - More details on how to use this package is available in the `docs/03-design-system-usage.md`. + +### 2.6 Core (`packages/core`) +- **Role**: Cross-cutting concerns. +- **Responsibilities**: + - Extension methods. + - Logger configuration. + - Base classes for Use Cases or Result types (functional error handling). + +## 3. Dependency Direction & Boundaries + +1. **Domain Independence**: `packages/domain` knows NOTHING about the outer world. It defines *what* needs to be done, not *how*. +2. **UI Agnosticism**: `packages/features` depends on `packages/design_system` for looks and `packages/domain` for logic. It does NOT know about Firebase. +3. **Data Isolation**: `packages/data_connect` depends on `packages/domain` to know what interfaces to implement. It does NOT know about the UI. + +## 4. Firebase Data Connect Strategy + +Since Firebase Data Connect code does not yet exist, we adhere to a **Strict Mocking Strategy**: + +1. **Interface First**: All data requirements are first defined as `abstract interface class IRepository` in `packages/domain`. +2. **Mock Implementation**: + - Inside `packages/data_connect`, create a `MockRepository` implementation. + - Use in-memory lists or hardcoded futures to simulate backend responses. + - **CRITICAL**: Do NOT put mocks in `test/` folders if they are needed to run the app in "dev" mode. Put them in `lib/src/mocks/`. +3. **Future Integration**: When Data Connect is ready, we will add `RealRepository` in `packages/data_connect`. +4. **Injection**: `apps/` will inject either `MockRepository` or `RealRepository` based on build flags or environment variables. + +## 5. Feature Isolation + +- **Zero Direct Imports**: `import 'package:feature_a/...'` is FORBIDDEN inside `package:feature_b`. +- **Navigation**: Use string-based routes or a shared route definition module in `core` (if absolutely necessary) to navigate between features. +- **Data Sharing**: Features do not share state directly. They share data via the underlying `Domain` repositories (e.g., both observe the same `User` stream from `AuthRepository`). diff --git a/apps/docs/02-agent-development-rules.md b/apps/docs/02-agent-development-rules.md new file mode 100644 index 00000000..ab212e6f --- /dev/null +++ b/apps/docs/02-agent-development-rules.md @@ -0,0 +1,83 @@ +# Agent Development Rules + +These rules are **NON-NEGOTIABLE**. They are designed to prevent architectural degradation by automated agents. + +## 1. File Creation & Structure + +1. **Feature-First Packaging**: + * **DO**: Create new features as independent packages in `packages/features/`. + * **DO NOT**: Add features to `packages/core` or existing apps directly. +2. **Path Conventions**: + * Entities: `packages/domain/lib/src/entities/.dart` + * Repositories (Interface): `packages//lib/src/domain/repositories/_repository_interface.dart` + * Repositories (Impl): `packages//lib/src/data/repositories_impl/_repository_impl.dart` + * Use Cases: `packages//lib/src/application/_usecase.dart` + * BLoCs: `packages//lib/src/presentation/blocs/_bloc.dart` + * Pages: `packages//lib/src/presentation/pages/_page.dart` +3. **Barrel Files**: + * **DO**: Use `export` in `lib/.dart` only for public APIs. + * **DO NOT**: Export internal implementation details (like mocks or helper widgets) in the main package file. + +## 2. Naming Conventions + +Follow Dart standards strictly. + +| Type | Convention | Example | +| :--- | :--- | :--- | +| **Files** | `snake_case` | `user_profile_page.dart` | +| **Classes** | `PascalCase` | `UserProfilePage` | +| **Variables** | `camelCase` | `userProfile` | +| **Interfaces** | terminate with `Interface` | `AuthRepositoryInterface` | +| **Implementations** | terminate with `Impl` | `FirebaseDataConnectAuthRepositoryImpl` | +| **Mocks** | terminate with `Mock` | `AuthRepositoryMock` | + +## 3. Logic Placement (Strict Boundaries) + +* **Business Rules**: MUST reside in **Use Cases** (Domain/Feature Application layer). + * *Forbidden*: Placing business rules in BLoCs or Widgets. +* **State Logic**: MUST reside in **BLoCs**. + * *Forbidden*: `setState` in Pages (except for purely ephemeral UI animations). +* **Data Transformation**: MUST reside in **Repositories** (Data Connect layer). + * *Forbidden*: Parsing JSON in the UI or Domain. +* **Navigation Logic**: MUST reside in **Modular Routes**. + * *Forbidden*: `Navigator.push` with hardcoded widgets. + +## 4. Data Connect Mocking Strategy + +Since the backend does not exist, you must mock strictly: + +1. **Define Interface**: Create `abstract interface class RepositoryInterface` in `packages//lib/src/domain/repositories/_repository_interface.dart`. +2. **Create Mock**: Create `class MockRepository implements IRepository` in `packages/data_connect/lib/src/mocks/`. +3. **Fake Data**: Return hardcoded `Future`s with realistic dummy entities. +4. **Injection**: Register the `MockRepository` in the `AppModule` (in `apps/client` or `apps/staff`) until the real implementation exists. + +**DO NOT** use `mockito` or `mocktail` for these *runtime* mocks. Use simple fake classes. + +## 5. Prototype Migration Rules + +You have access to `prototypes/` folders. When migrating code: + +1. **Extract Assets**: + * You MAY copy icons, images, and colors. But they should be tailored to the current design system. Do not change the colours and typgorahys in the design system. They are final. And you have to use these in the UI. + * When you matching colous and typography, from the POC match it with the design system and use the colors and typography from the design system. As mentioned in the `docs/03-design-system-usage.md`. +2. **Extract Layouts**: You MAY copy `build` methods for UI structure. +3. **REJECT Architecture**: You MUST **NOT** copy the `GetX`, `Provider`, or `MVC` patterns often found in prototypes. Refactor immediately to **Bloc + Clean Architecture with Flutter Modular and Melos**. + +## 6. Handling Ambiguity + +If a user request is vague: + +1. **STOP**: Do not guess domain fields or workflows. +2. **ANALYZE**: + - For architecture related questions, refer to `docs/01-architecture-principles.md` or existing code. + - For design system related questions, refer to `docs/03-design-system-usage.md` or existing code. +3. **DOCUMENT**: If you must make an assumption to proceed, add a comment `// ASSUMPTION: ` and mention it in your final summary. +4. **ASK**: Prefer asking the user for clarification on business rules (e.g., "Should a 'Job' have a 'status'?"). + +## 7. Dependencies + +* **DO NOT** add 3rd party packages without checking `packages/core` first. +* **DO NOT** add `firebase_auth` or `cloud_firestore` to any Feature package. They belong in `data_connect` only. + +## 8. Follow Clean Code Principles +* Add doc comments to all classes and methods you create. diff --git a/apps/docs/03-design-system-usage.md b/apps/docs/03-design-system-usage.md new file mode 100644 index 00000000..76c05ce5 --- /dev/null +++ b/apps/docs/03-design-system-usage.md @@ -0,0 +1,131 @@ +# 03 - Design System Usage Guide + +This document defines the mandatory standards for designing and implementing user interfaces across all applications and feature packages using the shared `packages/design_system`. + +## 1. Introduction & Purpose + +The Design System is the single source of truth for the visual identity of the project. Its purpose is to ensure UI consistency, reduce development velocity by providing reusable primitives, and eliminate "design drift" across multiple feature teams and applications. + +**All UI implementation MUST consume values ONLY from the `design_system` package.** + +## 2. Design System Ownership & Responsibility + +- **Centralized Authority**: The `packages/design_system` is the owner of all brand assets, colors, typography, and core components. +- **No Local Overrides**: Feature packages (e.g., `staff_authentication`) are consumers. They are prohibited from defining their own global styles or overriding theme values locally. +- **Extension Policy**: If a required style (color, font, or icon) is missing, the developer must first add it to the `design_system` package following existing patterns before using it in a feature. + +## 3. Package Structure Overview (`packages/design_system`) + +The package is organized to separate tokens from implementation: +- `lib/src/ui_colors.dart`: Color tokens and semantic mappings. +- `lib/src/ui_typography.dart`: Text styles and font configurations. +- `lib/src/ui_icons.dart`: Exported icon sets. +- `lib/src/ui_constants.dart`: Spacing, radius, and elevation tokens. +- `lib/src/ui_theme.dart`: Centralized `ThemeData` factory. +- `lib/src/widgets/`: Common "Smart Widgets" and reusable UI building blocks. + +## 4. Colors Usage Rules + +Feature packages **MUST NOT** define custom hex codes or `Color` constants. + +### Usage Protocol +- **Primary Method**:Use `UiColors` from the design system for specific brand accents. +- **Naming Matching**: If an exact color is missing, use the closest existing semantic color (e.g., use `UiColors.mutedForeground` instead of a hardcoded grey). + +```dart +// ❌ ANTI-PATTERN: Hardcoded color +Container(color: Color(0xFF1A2234)) + +// ✅ CORRECT: Design system token +Container(color: UiColors.background) +``` + +## 5. Typography Usage Rules + +Custom `TextStyle` definitions in feature packages are **STRICTLY PROHIBITED**. + +### Usage Protocol +- Use `UiTypography` from the design system for specific brand accents. + +```dart +// ❌ ANTI-PATTERN: Custom TextStyle +Text('Hello', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)) + +// ✅ CORRECT: Design system typography +Text('Hello', style: UiTypography.display1m) +``` + +## 6. Icons Usage Rules + +Feature packages **MUST NOT** import icon libraries (like `lucide_icons`) directly unless specified. They should use the icons exposed via `UiIcons` or the approved central library. + +- **Standardization**: Ensure the same icon is used for the same action across all features (e.g., always use `UiIcons.back` for navigation). +- **Additions**: New icons must be added to the design system (only using the typedef _IconLib = LucideIcons and nothing else) first to ensure they follow the project's stroke weight and sizing standards. + +## 7. UI Constants & Layout Rules + +Hardcoded padding, margins, and radius values are **PROHIBITED**. + +- **Spacing**: Use `UiConstants.spacing` multiplied by tokens (e.g., `S`, `M`, `L`). +- **Border Radius**: Use `UiConstants.borderRadius`. +- **Elevation**: Use `UiConstants.elevation`. + +```dart +// ✅ CORRECT: Spacing and Radius constants +Padding( + padding: EdgeInsets.all(UiConstants.spacingL), + child: Container( + borderRadius: BorderRadius.circular(UiConstants.radiusM), + ), +) +``` + +## 8. Common Smart Widgets Guidelines + +The design system provides "Smart Widgets" – these are high-level UI components that encapsulate both styling and standard behavior. + +- **Standard Widgets**: Prefer standard Flutter Material widgets (e.g., `ElevatedButton`) but styled via the central theme. +- **Custom Components**: Use `design_system` widgets for non-standard elements or wisgets that has similar design across various features, if provided. +- **Composition**: Prefer composing standard widgets over creating deep inheritance hierarchies in features. + +## 9. Theme Configuration & Usage + +Applications (`apps/`) must initialize the theme once in the root `MaterialApp`. + +```dart +MaterialApp.router( + theme: StaffTheme.light, // Mandatory: Consumption of centralized theme + // ... +) +``` +**No application-level theme customization is allowed.** + +## 10. Feature Development Workflow (POC → Themed) + +To bridge the gap between rapid prototyping (POCs) and production-grade code, developers must follow this three-step workflow: + +1. **Step 1: Structural Implementation**: Implement the UI logic and layout **exactly matching the POC**. Hardcoded values from the POC are acceptable in this transient state to ensure visual parity. +2. **Step 2: Logic Refactor**: Immediately refactor the code to: + - Follow the `docs/01-architecture-principles.md` and `docs/02-agent-development-rules.md` to refactor the code. +3. **Step 3: Theme Refactor**: Immediately refactor the code to: + - Replace hex codes with `UiColors`. + - Replace manual `TextStyle` with `UiTypography`. + - Replace hardcoded padding/radius with `UiConstants`. + - Upgrade icons to design system versions. + +## 11. Anti-Patterns & Common Mistakes + +- **"Magic Numbers"**: Hardcoding `EdgeInsets.all(12.0)` instead of using design system constants. +- **Local Themes**: Using `Theme(data: ...)` to override colors for a specific section of a page. +- **Hex Hunting**: Copy-pasting hex codes from Figma or POCs into feature code. +- **Package Bypassing**: Importing `package:flutter/material.dart` and ignoring `package:design_system`. + +## 12. Enforcement & Review Checklist + +Before any UI code is merged, it must pass this checklist: +1. [ ] No hardcoded `Color(...)` or `0xFF...` in the feature package. +2. [ ] No custom `TextStyle(...)` definitions. +3. [ ] All spacing/padding/radius uses `UiConstants`. +4. [ ] All icons are consumed from the approved design system source. +5. [ ] The feature relies on the global `ThemeData` and does not provide local overrides. +6. [ ] The layout matches the POC visual intent and element placement(wireframing and logic) while using the design system primitives. diff --git a/apps/melos.yaml b/apps/melos.yaml new file mode 100644 index 00000000..79ea13b3 --- /dev/null +++ b/apps/melos.yaml @@ -0,0 +1,22 @@ +name: krow_workspace + +packages: + - apps/** + - packages/** + +command: + bootstrap: + usePubspecOverrides: true + +scripts: + gen:l10n: + exec: dart run slang + description: "Generate localization files using Slang across all packages." + packageFilters: + dependsOn: slang + + gen:build: + exec: dart run build_runner build --delete-conflicting-outputs + description: "Run build_runner build across all packages." + packageFilters: + dependsOn: build_runner diff --git a/apps/mobile-client/.keep b/apps/mobile-client/.keep deleted file mode 100644 index 8b137891..00000000 --- a/apps/mobile-client/.keep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/mobile-staff/.keep b/apps/mobile-staff/.keep deleted file mode 100644 index 8b137891..00000000 --- a/apps/mobile-staff/.keep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/packages/core/lib/core.dart b/apps/packages/core/lib/core.dart new file mode 100644 index 00000000..f46af624 --- /dev/null +++ b/apps/packages/core/lib/core.dart @@ -0,0 +1,4 @@ +library core; + +export 'src/domain/arguments/usecase_argument.dart'; +export 'src/domain/usecases/usecase.dart'; diff --git a/apps/packages/core/lib/src/domain/arguments/usecase_argument.dart b/apps/packages/core/lib/src/domain/arguments/usecase_argument.dart new file mode 100644 index 00000000..4936596d --- /dev/null +++ b/apps/packages/core/lib/src/domain/arguments/usecase_argument.dart @@ -0,0 +1,12 @@ +import 'package:equatable/equatable.dart'; + +/// Abstract base class for all use case arguments. +/// +/// Use case arguments are data transfer objects (DTOs) used to pass data +/// into a use case. They must extend [Equatable] to ensure value equality. +abstract class UseCaseArgument extends Equatable { + const UseCaseArgument(); + + @override + List get props => []; +} diff --git a/apps/packages/core/lib/src/domain/usecases/usecase.dart b/apps/packages/core/lib/src/domain/usecases/usecase.dart new file mode 100644 index 00000000..ddc33eba --- /dev/null +++ b/apps/packages/core/lib/src/domain/usecases/usecase.dart @@ -0,0 +1,25 @@ +/// Abstract base class for all use cases in the application. +/// +/// Use cases encapsulate application-specific business rules and orchestrate +/// the flow of data to and from the entities. They are typically invoked +/// from the presentation layer (e.g., BLoCs, ViewModels) and interact with +/// repositories from the data layer. +/// +/// [Input] represents the type of data passed into the use case. +/// [Output] represents the type of data returned by the use case. +abstract class UseCase { + /// Executes the use case with the given [input]. + /// + /// This method should contain the core business logic of the use case. + Future call(Input input); +} + +/// Abstract base class for use cases that do not require any input. +/// +/// [Output] represents the type of data returned by the use case. +abstract class NoInputUseCase { + /// Executes the use case. + /// + /// This method should contain the core business logic of the use case. + Future call(); +} diff --git a/apps/packages/core/pubspec.yaml b/apps/packages/core/pubspec.yaml new file mode 100644 index 00000000..1b14ddda --- /dev/null +++ b/apps/packages/core/pubspec.yaml @@ -0,0 +1,13 @@ +name: krow_core +description: Core utilities and shared logic. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter diff --git a/apps/packages/core_localization/.gitignore b/apps/packages/core_localization/.gitignore new file mode 100644 index 00000000..dd5eb989 --- /dev/null +++ b/apps/packages/core_localization/.gitignore @@ -0,0 +1,31 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins-dependencies +/build/ +/coverage/ diff --git a/apps/packages/core_localization/.metadata b/apps/packages/core_localization/.metadata new file mode 100644 index 00000000..685c30f1 --- /dev/null +++ b/apps/packages/core_localization/.metadata @@ -0,0 +1,10 @@ +# 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: "3b62efc2a3da49882f43c372e0bc53daef7295a6" + channel: "stable" + +project_type: package diff --git a/apps/packages/core_localization/analysis_options.yaml b/apps/packages/core_localization/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/apps/packages/core_localization/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/apps/packages/core_localization/lib/core_localization.dart b/apps/packages/core_localization/lib/core_localization.dart new file mode 100644 index 00000000..8c8c71fc --- /dev/null +++ b/apps/packages/core_localization/lib/core_localization.dart @@ -0,0 +1,10 @@ +export 'src/bloc/locale_bloc.dart'; +export 'src/bloc/locale_event.dart'; +export 'src/bloc/locale_state.dart'; +export 'src/l10n/strings.g.dart'; +export 'src/domain/repositories/locale_repository_interface.dart'; +export 'src/domain/usecases/get_locale_use_case.dart'; +export 'src/domain/usecases/set_locale_use_case.dart'; +export 'src/data/repositories_impl/locale_repository_impl.dart'; +export 'src/data/datasources/locale_local_data_source.dart'; +export 'src/localization_module.dart'; diff --git a/apps/packages/core_localization/lib/src/bloc/locale_bloc.dart b/apps/packages/core_localization/lib/src/bloc/locale_bloc.dart new file mode 100644 index 00000000..4d46b623 --- /dev/null +++ b/apps/packages/core_localization/lib/src/bloc/locale_bloc.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../domain/usecases/get_locale_use_case.dart'; +import '../domain/usecases/set_locale_use_case.dart'; +import '../l10n/strings.g.dart'; +import 'locale_event.dart'; +import 'locale_state.dart'; + +/// A [Bloc] that manages the application's locale state. +/// +/// It coordinates the flow between user language requests and persistent storage +/// using [SetLocaleUseCase] and [GetLocaleUseCase]. +class LocaleBloc extends Bloc { + final GetLocaleUseCase getLocaleUseCase; + final SetLocaleUseCase setLocaleUseCase; + + /// Creates a [LocaleBloc] with the required use cases. + LocaleBloc({required this.getLocaleUseCase, required this.setLocaleUseCase}) + : super(LocaleState.initial()) { + on(_onChangeLocale); + on(_onLoadLocale); + } + + /// Handles the [ChangeLocale] event by saving it via the use case and emitting new state. + Future _onChangeLocale( + ChangeLocale event, + Emitter emit, + ) async { + // 1. Update slang settings + LocaleSettings.setLocaleRaw(event.locale.languageCode); + + // 2. Persist using Use Case + await setLocaleUseCase(event.locale); + + // 3. Emit new state + emit( + LocaleState( + locale: event.locale, + supportedLocales: state.supportedLocales, + ), + ); + } + + /// Handles the [LoadLocale] event by retrieving it via the use case and updating settings. + Future _onLoadLocale( + LoadLocale event, + Emitter emit, + ) async { + final savedLocale = await getLocaleUseCase(); + final locale = const Locale('es'); + + LocaleSettings.setLocaleRaw(locale.languageCode); + + emit(LocaleState(locale: locale, supportedLocales: state.supportedLocales)); + } +} diff --git a/apps/packages/core_localization/lib/src/bloc/locale_event.dart b/apps/packages/core_localization/lib/src/bloc/locale_event.dart new file mode 100644 index 00000000..52a57fbc --- /dev/null +++ b/apps/packages/core_localization/lib/src/bloc/locale_event.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +/// Base class for all locale-related events. +sealed class LocaleEvent { + /// Creates a [LocaleEvent]. + const LocaleEvent(); +} + +/// Event triggered when the user wants to change the application locale. +class ChangeLocale extends LocaleEvent { + /// The new locale to apply. + final Locale locale; + + /// Creates a [ChangeLocale] event. + const ChangeLocale(this.locale); +} + +/// Event triggered to load the saved locale from persistent storage. +class LoadLocale extends LocaleEvent { + /// Creates a [LoadLocale] event. + const LoadLocale(); +} diff --git a/apps/packages/core_localization/lib/src/bloc/locale_state.dart b/apps/packages/core_localization/lib/src/bloc/locale_state.dart new file mode 100644 index 00000000..33219cd1 --- /dev/null +++ b/apps/packages/core_localization/lib/src/bloc/locale_state.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import '../l10n/strings.g.dart'; + +/// Represents the current state of the application's localization. +class LocaleState { + /// The current active locale. + final Locale locale; + + /// The list of supported locales for the application. + final List supportedLocales; + + /// Creates a [LocaleState] with the specified [locale]. + const LocaleState({required this.locale, required this.supportedLocales}); + + /// The initial state of the application, defaulting to English. + factory LocaleState.initial() => LocaleState( + locale: const Locale('es'), + supportedLocales: AppLocaleUtils.supportedLocales, + ); +} diff --git a/apps/packages/core_localization/lib/src/data/datasources/locale_local_data_source.dart b/apps/packages/core_localization/lib/src/data/datasources/locale_local_data_source.dart new file mode 100644 index 00000000..f036b915 --- /dev/null +++ b/apps/packages/core_localization/lib/src/data/datasources/locale_local_data_source.dart @@ -0,0 +1,29 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +/// Interface for the local data source that manages locale persistence. +abstract interface class LocaleLocalDataSource { + /// Saves the language code to local storage. + Future saveLanguageCode(String languageCode); + + /// Retrieves the saved language code from local storage. + Future getLanguageCode(); +} + +/// Implementation of [LocaleLocalDataSource] using [SharedPreferencesAsync]. +class LocaleLocalDataSourceImpl implements LocaleLocalDataSource { + static const String _localeKey = 'app_locale'; + final SharedPreferencesAsync _sharedPreferences; + + /// Creates a [LocaleLocalDataSourceImpl] with the required [SharedPreferencesAsync] instance. + LocaleLocalDataSourceImpl(this._sharedPreferences); + + @override + Future saveLanguageCode(String languageCode) async { + await _sharedPreferences.setString(_localeKey, languageCode); + } + + @override + Future getLanguageCode() async { + return _sharedPreferences.getString(_localeKey); + } +} diff --git a/apps/packages/core_localization/lib/src/data/repositories_impl/locale_repository_impl.dart b/apps/packages/core_localization/lib/src/data/repositories_impl/locale_repository_impl.dart new file mode 100644 index 00000000..b4927061 --- /dev/null +++ b/apps/packages/core_localization/lib/src/data/repositories_impl/locale_repository_impl.dart @@ -0,0 +1,28 @@ +import 'dart:ui'; +import '../../domain/repositories/locale_repository_interface.dart'; +import '../datasources/locale_local_data_source.dart'; + +/// Implementation of [LocaleRepositoryInterface] that coordinates with a local data source. +/// +/// This class handles the mapping between domain [Locale] objects and the raw +/// strings handled by the [LocaleLocalDataSource]. +class LocaleRepositoryImpl implements LocaleRepositoryInterface { + final LocaleLocalDataSource _localDataSource; + + /// Creates a [LocaleRepositoryImpl] with the provided [_localDataSource]. + LocaleRepositoryImpl(this._localDataSource); + + @override + Future saveLocale(Locale locale) { + return _localDataSource.saveLanguageCode(locale.languageCode); + } + + @override + Future getSavedLocale() async { + final languageCode = await _localDataSource.getLanguageCode(); + if (languageCode != null) { + return Locale(languageCode); + } + return null; + } +} diff --git a/apps/packages/core_localization/lib/src/domain/repositories/locale_repository_interface.dart b/apps/packages/core_localization/lib/src/domain/repositories/locale_repository_interface.dart new file mode 100644 index 00000000..604c2d41 --- /dev/null +++ b/apps/packages/core_localization/lib/src/domain/repositories/locale_repository_interface.dart @@ -0,0 +1,17 @@ +import 'dart:ui'; + +/// Interface for the locale repository. +/// +/// This defines the contracts for persisting and retrieving the application's locale. +/// Implementations of this interface should handle the details of the storage mechanism. +abstract interface class LocaleRepositoryInterface { + /// Saves the specified [locale] to persistent storage. + /// + /// Throws a [RepositoryException] if the operation fails. + Future saveLocale(Locale locale); + + /// Retrieves the saved [locale] from persistent storage. + /// + /// Returns `null` if no locale has been previously saved. + Future getSavedLocale(); +} diff --git a/apps/packages/core_localization/lib/src/domain/usecases/get_locale_use_case.dart b/apps/packages/core_localization/lib/src/domain/usecases/get_locale_use_case.dart new file mode 100644 index 00000000..8d29876e --- /dev/null +++ b/apps/packages/core_localization/lib/src/domain/usecases/get_locale_use_case.dart @@ -0,0 +1,19 @@ +import 'dart:ui'; +import 'package:krow_core/core.dart'; +import '../repositories/locale_repository_interface.dart'; + +/// Use case to retrieve the persisted application locale. +/// +/// This class extends [NoInputUseCase] and interacts with [LocaleRepositoryInterface] +/// to fetch the saved locale. +class GetLocaleUseCase extends NoInputUseCase { + final LocaleRepositoryInterface _repository; + + /// Creates a [GetLocaleUseCase] with the required [LocaleRepositoryInterface]. + GetLocaleUseCase(this._repository); + + @override + Future call() { + return _repository.getSavedLocale(); + } +} diff --git a/apps/packages/core_localization/lib/src/domain/usecases/set_locale_use_case.dart b/apps/packages/core_localization/lib/src/domain/usecases/set_locale_use_case.dart new file mode 100644 index 00000000..dcddd0c1 --- /dev/null +++ b/apps/packages/core_localization/lib/src/domain/usecases/set_locale_use_case.dart @@ -0,0 +1,19 @@ +import 'dart:ui'; +import 'package:krow_core/core.dart'; +import '../repositories/locale_repository_interface.dart'; + +/// Use case to save the application locale to persistent storage. +/// +/// This class extends [UseCase] and interacts with [LocaleRepositoryInterface] +/// to save a given locale. +class SetLocaleUseCase extends UseCase { + final LocaleRepositoryInterface _repository; + + /// Creates a [SetLocaleUseCase] with the required [LocaleRepositoryInterface]. + SetLocaleUseCase(this._repository); + + @override + Future call(Locale input) { + return _repository.saveLocale(input); + } +} diff --git a/apps/packages/core_localization/lib/src/l10n/en.i18n.json b/apps/packages/core_localization/lib/src/l10n/en.i18n.json new file mode 100644 index 00000000..63aa509e --- /dev/null +++ b/apps/packages/core_localization/lib/src/l10n/en.i18n.json @@ -0,0 +1,190 @@ +{ + "common": { + "ok": "OK", + "cancel": "Cancel", + "save": "Save", + "delete": "Delete", + "continue_text": "Continue" + }, + "settings": { + "language": "Language", + "change_language": "Change Language" + }, + "staff_authentication": { + "get_started_page": { + "title_part1": "Work, Grow, ", + "title_part2": "Elevate", + "subtitle": "Build your career in hospitality with \nflexibility and freedom.", + "sign_up_button": "Sign Up", + "log_in_button": "Log In" + }, + "phone_verification_page": { + "validation_error": "Please enter a valid 10-digit phone number", + "send_code_button": "Send Code", + "enter_code_title": "Enter verification code", + "code_sent_message": "We sent a 6-digit code to ", + "code_sent_instruction": ". Enter it below to verify your account." + }, + "phone_input": { + "title": "Verify your phone number", + "subtitle": "We'll send you a verification code to get started.", + "label": "Phone Number", + "hint": "Enter your number" + }, + "otp_verification": { + "did_not_get_code": "Didn't get the code ?", + "resend_in": "Resend in $seconds s", + "resend_code": "Resend code" + }, + "profile_setup_page": { + "step_indicator": "Step $current of $total", + "error_occurred": "An error occurred", + "complete_setup_button": "Complete Setup", + "steps": { + "basic": "Basic Info", + "location": "Location", + "experience": "Experience" + }, + "basic_info": { + "title": "Let's get to know you", + "subtitle": "Tell us a bit about yourself", + "full_name_label": "Full Name *", + "full_name_hint": "John Smith", + "bio_label": "Short Bio", + "bio_hint": "Experienced hospitality professional..." + }, + "location": { + "title": "Where do you want to work?", + "subtitle": "Add your preferred work locations", + "add_location_label": "Add Location *", + "add_location_hint": "City or ZIP code", + "add_button": "Add", + "max_distance": "Max Distance: $distance miles", + "min_dist_label": "5 mi", + "max_dist_label": "50 mi" + }, + "experience": { + "title": "What are your skills?", + "subtitle": "Select all that apply", + "skills_label": "Skills *", + "industries_label": "Preferred Industries", + "skills": { + "food_service": "Food Service", + "bartending": "Bartending", + "warehouse": "Warehouse", + "retail": "Retail", + "events": "Events", + "customer_service": "Customer Service", + "cleaning": "Cleaning", + "security": "Security", + "driving": "Driving", + "cooking": "Cooking" + }, + "industries": { + "hospitality": "Hospitality", + "food_service": "Food Service", + "warehouse": "Warehouse", + "events": "Events", + "retail": "Retail", + "healthcare": "Healthcare" + } + } + }, + "common": { + "trouble_question": "Having trouble? ", + "contact_support": "Contact Support" + } + }, + "client_authentication": { + "get_started_page": { + "title": "Take Control of Your\nShifts and Events", + "subtitle": "Streamline your operations with powerful tools to manage schedules, track performance, and keep your team on the same page—all in one place", + "sign_in_button": "Sign In", + "create_account_button": "Create Account" + }, + "sign_in_page": { + "title": "Welcome Back", + "subtitle": "Sign in to manage your shifts and workers", + "email_label": "Email", + "email_hint": "Enter your email", + "password_label": "Password", + "password_hint": "Enter your password", + "forgot_password": "Forgot Password?", + "sign_in_button": "Sign In", + "or_divider": "or", + "social_apple": "Sign In with Apple", + "social_google": "Sign In with Google", + "no_account": "Don't have an account? ", + "sign_up_link": "Sign Up" + }, + "sign_up_page": { + "title": "Create Account", + "subtitle": "Get started with Krow for your business", + "company_label": "Company Name", + "company_hint": "Enter company name", + "email_label": "Email", + "email_hint": "Enter your email", + "password_label": "Password", + "password_hint": "Create a password", + "confirm_password_label": "Confirm Password", + "confirm_password_hint": "Confirm your password", + "create_account_button": "Create Account", + "or_divider": "or", + "social_apple": "Sign Up with Apple", + "social_google": "Sign Up with Google", + "has_account": "Already have an account? ", + "sign_in_link": "Sign In" + } + }, + "client_home": { + "dashboard": { + "welcome_back": "Welcome back", + "edit_mode_active": "Edit Mode Active", + "drag_instruction": "Drag to reorder, toggle visibility", + "reset": "Reset", + "metric_needed": "Needed", + "metric_filled": "Filled", + "metric_open": "Open", + "view_all": "View all", + "insight_lightbulb": "Save $amount/month", + "insight_tip": "Book 48hrs ahead for better rates" + }, + "widgets": { + "actions": "Quick Actions", + "reorder": "Reorder", + "coverage": "Today's Coverage", + "spending": "Spending Insights", + "live_activity": "Live Activity" + }, + "actions": { + "rapid": "RAPID", + "rapid_subtitle": "Urgent same-day", + "create_order": "Create Order", + "create_order_subtitle": "Schedule shifts" + }, + "reorder": { + "title": "REORDER", + "reorder_button": "Reorder", + "per_hr": "$amount/hr" + }, + "form": { + "edit_reorder": "Edit & Reorder", + "post_new": "Post a New Shift", + "review_subtitle": "Review and edit the details before posting", + "date_label": "Date *", + "date_hint": "mm/dd/yyyy", + "location_label": "Location *", + "location_hint": "Business address", + "positions_title": "Positions", + "add_position": "Add Position", + "role_label": "Role *", + "role_hint": "Select role", + "start_time": "Start Time *", + "end_time": "End Time *", + "workers_needed": "Workers Needed *", + "hourly_rate": "Hourly Rate (\\$) *", + "post_shift": "Post Shift" + } + } +} + diff --git a/apps/packages/core_localization/lib/src/l10n/es.i18n.json b/apps/packages/core_localization/lib/src/l10n/es.i18n.json new file mode 100644 index 00000000..dd1dfb8b --- /dev/null +++ b/apps/packages/core_localization/lib/src/l10n/es.i18n.json @@ -0,0 +1,189 @@ +{ + "common": { + "ok": "Aceptar", + "cancel": "Cancelar", + "save": "Guardar", + "delete": "Eliminar", + "continue_text": "Continuar" + }, + "settings": { + "language": "Idioma", + "change_language": "Cambiar Idioma" + }, + "staff_authentication": { + "get_started_page": { + "title_part1": "Trabaja, Crece, ", + "title_part2": "Elévate", + "subtitle": "Construye tu carrera en hostelería con \nflexibilidad y libertad.", + "sign_up_button": "Registrarse", + "log_in_button": "Iniciar sesión" + }, + "phone_verification_page": { + "validation_error": "Por favor, ingresa un número de teléfono válido de 10 dígitos", + "send_code_button": "Enviar código", + "enter_code_title": "Ingresa el código de verificación", + "code_sent_message": "Enviamos un código de 6 dígitos a ", + "code_sent_instruction": ". Ingrésalo a continuación para verificar tu cuenta." + }, + "phone_input": { + "title": "Verifica tu número de teléfono", + "subtitle": "Te enviaremos un código de verificación para comenzar.", + "label": "Número de teléfono", + "hint": "Ingresa tu número" + }, + "otp_verification": { + "did_not_get_code": "¿No recibiste el código?", + "resend_in": "Reenviar en $seconds s", + "resend_code": "Reenviar código" + }, + "profile_setup_page": { + "step_indicator": "Paso $current de $total", + "error_occurred": "Ocurrió un error", + "complete_setup_button": "Completar configuración", + "steps": { + "basic": "Información básica", + "location": "Ubicación", + "experience": "Experiencia" + }, + "basic_info": { + "title": "Conozcámonos", + "subtitle": "Cuéntanos un poco sobre ti", + "full_name_label": "Nombre completo *", + "full_name_hint": "Juan Pérez", + "bio_label": "Biografía corta", + "bio_hint": "Profesional experimentado en hostelería..." + }, + "location": { + "title": "¿Dónde quieres trabajar?", + "subtitle": "Agrega tus ubicaciones de trabajo preferidas", + "add_location_label": "Agregar ubicación *", + "add_location_hint": "Ciudad o código postal", + "add_button": "Agregar", + "max_distance": "Distancia máxima: $distance millas", + "min_dist_label": "5 mi", + "max_dist_label": "50 mi" + }, + "experience": { + "title": "¿Cuáles son tus habilidades?", + "subtitle": "Selecciona todas las que correspondan", + "skills_label": "Habilidades *", + "industries_label": "Industrias preferidas", + "skills": { + "food_service": "Servicio de comida", + "bartending": "Preparación de bebidas", + "warehouse": "Almacén", + "retail": "Venta minorista", + "events": "Eventos", + "customer_service": "Servicio al cliente", + "cleaning": "Limpieza", + "security": "Seguridad", + "driving": "Conducción", + "cooking": "Cocina" + }, + "industries": { + "hospitality": "Hostelería", + "food_service": "Servicio de comida", + "warehouse": "Almacén", + "events": "Eventos", + "retail": "Venta minorista", + "healthcare": "Atención médica" + } + } + }, + "common": { + "trouble_question": "¿Tienes problemas? ", + "contact_support": "Contactar a soporte" + } + }, + "client_authentication": { + "get_started_page": { + "title": "Toma el control de tus\nturnos y eventos", + "subtitle": "Optimiza tus operaciones con potentes herramientas para gestionar horarios, realizar un seguimiento del rendimiento y mantener a tu equipo en la misma página, todo en un solo lugar", + "sign_in_button": "Iniciar sesión", + "create_account_button": "Crear cuenta" + }, + "sign_in_page": { + "title": "Bienvenido de nuevo", + "subtitle": "Inicia sesión para gestionar tus turnos y trabajadores", + "email_label": "Correo electrónico", + "email_hint": "Ingresa tu correo electrónico", + "password_label": "Contraseña", + "password_hint": "Ingresa tu contraseña", + "forgot_password": "¿Olvidaste tu contraseña?", + "sign_in_button": "Iniciar sesión", + "or_divider": "o", + "social_apple": "Iniciar sesión con Apple", + "social_google": "Iniciar sesión con Google", + "no_account": "¿No tienes una cuenta? ", + "sign_up_link": "Regístrate" + }, + "sign_up_page": { + "title": "Crear cuenta", + "subtitle": "Comienza con Krow para tu negocio", + "company_label": "Nombre de la empresa", + "company_hint": "Ingresa el nombre de la empresa", + "email_label": "Correo electrónico", + "email_hint": "Ingresa tu correo electrónico", + "password_label": "Contraseña", + "password_hint": "Crea una contraseña", + "confirm_password_label": "Confirmar contraseña", + "confirm_password_hint": "Confirma tu contraseña", + "create_account_button": "Crear cuenta", + "or_divider": "o", + "social_apple": "Regístrate con Apple", + "social_google": "Regístrate con Google", + "has_account": "¿Ya tienes una cuenta? ", + "sign_in_link": "Iniciar sesión" + } + }, + "client_home": { + "dashboard": { + "welcome_back": "Bienvenido de nuevo", + "edit_mode_active": "Modo Edición Activo", + "drag_instruction": "Arrastra para reordenar, cambia la visibilidad", + "reset": "Restablecer", + "metric_needed": "Necesario", + "metric_filled": "Lleno", + "metric_open": "Abierto", + "view_all": "Ver todo", + "insight_lightbulb": "Ahorra $amount/mes", + "insight_tip": "Reserva con 48h de antelación para mejores tarifas" + }, + "widgets": { + "actions": "Acciones Rápidas", + "reorder": "Reordenar", + "coverage": "Cobertura de Hoy", + "spending": "Información de Gastos", + "live_activity": "Actividad en Vivo" + }, + "actions": { + "rapid": "RÁPIDO", + "rapid_subtitle": "Urgente mismo día", + "create_order": "Crear Orden", + "create_order_subtitle": "Programar turnos" + }, + "reorder": { + "title": "REORDENAR", + "reorder_button": "Reordenar", + "per_hr": "$amount/hr" + }, + "form": { + "edit_reorder": "Editar y Reordenar", + "post_new": "Publicar un Nuevo Turno", + "review_subtitle": "Revisa y edita los detalles antes de publicar", + "date_label": "Fecha *", + "date_hint": "mm/dd/aaaa", + "location_label": "Ubicación *", + "location_hint": "Dirección del negocio", + "positions_title": "Posiciones", + "add_position": "Añadir Posición", + "role_label": "Rol *", + "role_hint": "Seleccionar rol", + "start_time": "Hora de Inicio *", + "end_time": "Hora de Fin *", + "workers_needed": "Trabajadores Necesarios *", + "hourly_rate": "Tarifa por hora (\\$) *", + "post_shift": "Publicar Turno" + } + } +} diff --git a/apps/packages/core_localization/lib/src/l10n/strings.g.dart b/apps/packages/core_localization/lib/src/l10n/strings.g.dart new file mode 100644 index 00000000..1860222f --- /dev/null +++ b/apps/packages/core_localization/lib/src/l10n/strings.g.dart @@ -0,0 +1,183 @@ +/// Generated file. Do not edit. +/// +/// Source: lib/src/l10n +/// To regenerate, run: `dart run slang` +/// +/// Locales: 2 +/// Strings: 276 (138 per locale) +/// +/// Built on 2026-01-21 at 18:21 UTC + +// coverage:ignore-file +// ignore_for_file: type=lint, unused_import +// dart format off + +import 'package:flutter/widgets.dart'; +import 'package:intl/intl.dart'; +import 'package:slang/generated.dart'; +import 'package:slang_flutter/slang_flutter.dart'; +export 'package:slang_flutter/slang_flutter.dart'; + +import 'strings_es.g.dart' deferred as l_es; +part 'strings_en.g.dart'; + +/// Supported locales. +/// +/// Usage: +/// - LocaleSettings.setLocale(AppLocale.en) // set locale +/// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum +/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check +enum AppLocale with BaseAppLocale { + en(languageCode: 'en'), + es(languageCode: 'es'); + + const AppLocale({ + required this.languageCode, + this.scriptCode, // ignore: unused_element, unused_element_parameter + this.countryCode, // ignore: unused_element, unused_element_parameter + }); + + @override final String languageCode; + @override final String? scriptCode; + @override final String? countryCode; + + @override + Future build({ + Map? overrides, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) async { + switch (this) { + case AppLocale.en: + return TranslationsEn( + overrides: overrides, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); + case AppLocale.es: + await l_es.loadLibrary(); + return l_es.TranslationsEs( + overrides: overrides, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); + } + } + + @override + Translations buildSync({ + Map? overrides, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) { + switch (this) { + case AppLocale.en: + return TranslationsEn( + overrides: overrides, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); + case AppLocale.es: + return l_es.TranslationsEs( + overrides: overrides, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); + } + } + + /// Gets current instance managed by [LocaleSettings]. + Translations get translations => LocaleSettings.instance.getTranslations(this); +} + +/// Method A: Simple +/// +/// No rebuild after locale change. +/// Translation happens during initialization of the widget (call of t). +/// Configurable via 'translate_var'. +/// +/// Usage: +/// String a = t.someKey.anotherKey; +/// String b = t['someKey.anotherKey']; // Only for edge cases! +Translations get t => LocaleSettings.instance.currentTranslations; + +/// Method B: Advanced +/// +/// All widgets using this method will trigger a rebuild when locale changes. +/// Use this if you have e.g. a settings page where the user can select the locale during runtime. +/// +/// Step 1: +/// wrap your App with +/// TranslationProvider( +/// child: MyApp() +/// ); +/// +/// Step 2: +/// final t = Translations.of(context); // Get t variable. +/// String a = t.someKey.anotherKey; // Use t variable. +/// String b = t['someKey.anotherKey']; // Only for edge cases! +class TranslationProvider extends BaseTranslationProvider { + TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance); + + static InheritedLocaleData of(BuildContext context) => InheritedLocaleData.of(context); +} + +/// Method B shorthand via [BuildContext] extension method. +/// Configurable via 'translate_var'. +/// +/// Usage (e.g. in a widget's build method): +/// context.t.someKey.anotherKey +extension BuildContextTranslationsExtension on BuildContext { + Translations get t => TranslationProvider.of(this).translations; +} + +/// Manages all translation instances and the current locale +class LocaleSettings extends BaseFlutterLocaleSettings { + LocaleSettings._() : super( + utils: AppLocaleUtils.instance, + lazy: true, + ); + + static final instance = LocaleSettings._(); + + // static aliases (checkout base methods for documentation) + static AppLocale get currentLocale => instance.currentLocale; + static Stream getLocaleStream() => instance.getLocaleStream(); + static Future setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale); + static Future setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale); + static Future useDeviceLocale() => instance.useDeviceLocale(); + static Future setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver( + language: language, + locale: locale, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); + + // synchronous versions + static AppLocale setLocaleSync(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocaleSync(locale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale setLocaleRawSync(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRawSync(rawLocale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale useDeviceLocaleSync() => instance.useDeviceLocaleSync(); + static void setPluralResolverSync({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolverSync( + language: language, + locale: locale, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); +} + +/// Provides utility functions without any side effects. +class AppLocaleUtils extends BaseAppLocaleUtils { + AppLocaleUtils._() : super( + baseLocale: AppLocale.en, + locales: AppLocale.values, + ); + + static final instance = AppLocaleUtils._(); + + // static aliases (checkout base methods for documentation) + static AppLocale parse(String rawLocale) => instance.parse(rawLocale); + static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode); + static AppLocale findDeviceLocale() => instance.findDeviceLocale(); + static List get supportedLocales => instance.supportedLocales; + static List get supportedLocalesRaw => instance.supportedLocalesRaw; +} diff --git a/apps/packages/core_localization/lib/src/l10n/strings_en.g.dart b/apps/packages/core_localization/lib/src/l10n/strings_en.g.dart new file mode 100644 index 00000000..25bf923a --- /dev/null +++ b/apps/packages/core_localization/lib/src/l10n/strings_en.g.dart @@ -0,0 +1,861 @@ +/// +/// Generated file. Do not edit. +/// +// coverage:ignore-file +// ignore_for_file: type=lint, unused_import +// dart format off + +part of 'strings.g.dart'; + +// Path: +typedef TranslationsEn = Translations; // ignore: unused_element +class Translations with BaseTranslations { + /// Returns the current translations of the given [context]. + /// + /// Usage: + /// final t = Translations.of(context); + static Translations of(BuildContext context) => InheritedLocaleData.of(context).translations; + + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + Translations({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver, TranslationMetadata? meta}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = meta ?? TranslationMetadata( + locale: AppLocale.en, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + dynamic operator[](String key) => $meta.getTranslation(key); + + late final Translations _root = this; // ignore: unused_field + + Translations $copyWith({TranslationMetadata? meta}) => Translations(meta: meta ?? this.$meta); + + // Translations + late final TranslationsCommonEn common = TranslationsCommonEn._(_root); + late final TranslationsSettingsEn settings = TranslationsSettingsEn._(_root); + late final TranslationsStaffAuthenticationEn staff_authentication = TranslationsStaffAuthenticationEn._(_root); + late final TranslationsClientAuthenticationEn client_authentication = TranslationsClientAuthenticationEn._(_root); + late final TranslationsClientHomeEn client_home = TranslationsClientHomeEn._(_root); +} + +// Path: common +class TranslationsCommonEn { + TranslationsCommonEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'OK' + String get ok => 'OK'; + + /// en: 'Cancel' + String get cancel => 'Cancel'; + + /// en: 'Save' + String get save => 'Save'; + + /// en: 'Delete' + String get delete => 'Delete'; + + /// en: 'Continue' + String get continue_text => 'Continue'; +} + +// Path: settings +class TranslationsSettingsEn { + TranslationsSettingsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Language' + String get language => 'Language'; + + /// en: 'Change Language' + String get change_language => 'Change Language'; +} + +// Path: staff_authentication +class TranslationsStaffAuthenticationEn { + TranslationsStaffAuthenticationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final TranslationsStaffAuthenticationGetStartedPageEn get_started_page = TranslationsStaffAuthenticationGetStartedPageEn._(_root); + late final TranslationsStaffAuthenticationPhoneVerificationPageEn phone_verification_page = TranslationsStaffAuthenticationPhoneVerificationPageEn._(_root); + late final TranslationsStaffAuthenticationPhoneInputEn phone_input = TranslationsStaffAuthenticationPhoneInputEn._(_root); + late final TranslationsStaffAuthenticationOtpVerificationEn otp_verification = TranslationsStaffAuthenticationOtpVerificationEn._(_root); + late final TranslationsStaffAuthenticationProfileSetupPageEn profile_setup_page = TranslationsStaffAuthenticationProfileSetupPageEn._(_root); + late final TranslationsStaffAuthenticationCommonEn common = TranslationsStaffAuthenticationCommonEn._(_root); +} + +// Path: client_authentication +class TranslationsClientAuthenticationEn { + TranslationsClientAuthenticationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final TranslationsClientAuthenticationGetStartedPageEn get_started_page = TranslationsClientAuthenticationGetStartedPageEn._(_root); + late final TranslationsClientAuthenticationSignInPageEn sign_in_page = TranslationsClientAuthenticationSignInPageEn._(_root); + late final TranslationsClientAuthenticationSignUpPageEn sign_up_page = TranslationsClientAuthenticationSignUpPageEn._(_root); +} + +// Path: client_home +class TranslationsClientHomeEn { + TranslationsClientHomeEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Shift order submitted successfully' + String get shift_created_success => 'Shift order submitted successfully'; + + late final TranslationsClientHomeDashboardEn dashboard = TranslationsClientHomeDashboardEn._(_root); + late final TranslationsClientHomeWidgetsEn widgets = TranslationsClientHomeWidgetsEn._(_root); + late final TranslationsClientHomeActionsEn actions = TranslationsClientHomeActionsEn._(_root); + late final TranslationsClientHomeReorderEn reorder = TranslationsClientHomeReorderEn._(_root); + late final TranslationsClientHomeFormEn form = TranslationsClientHomeFormEn._(_root); +} + +// Path: staff_authentication.get_started_page +class TranslationsStaffAuthenticationGetStartedPageEn { + TranslationsStaffAuthenticationGetStartedPageEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Work, Grow, ' + String get title_part1 => 'Work, Grow, '; + + /// en: 'Elevate' + String get title_part2 => 'Elevate'; + + /// en: 'Build your career in hospitality with flexibility and freedom.' + String get subtitle => 'Build your career in hospitality with \nflexibility and freedom.'; + + /// en: 'Sign Up' + String get sign_up_button => 'Sign Up'; + + /// en: 'Log In' + String get log_in_button => 'Log In'; +} + +// Path: staff_authentication.phone_verification_page +class TranslationsStaffAuthenticationPhoneVerificationPageEn { + TranslationsStaffAuthenticationPhoneVerificationPageEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Please enter a valid 10-digit phone number' + String get validation_error => 'Please enter a valid 10-digit phone number'; + + /// en: 'Send Code' + String get send_code_button => 'Send Code'; + + /// en: 'Enter verification code' + String get enter_code_title => 'Enter verification code'; + + /// en: 'We sent a 6-digit code to ' + String get code_sent_message => 'We sent a 6-digit code to '; + + /// en: '. Enter it below to verify your account.' + String get code_sent_instruction => '. Enter it below to verify your account.'; +} + +// Path: staff_authentication.phone_input +class TranslationsStaffAuthenticationPhoneInputEn { + TranslationsStaffAuthenticationPhoneInputEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Verify your phone number' + String get title => 'Verify your phone number'; + + /// en: 'We'll send you a verification code to get started.' + String get subtitle => 'We\'ll send you a verification code to get started.'; + + /// en: 'Phone Number' + String get label => 'Phone Number'; + + /// en: 'Enter your number' + String get hint => 'Enter your number'; +} + +// Path: staff_authentication.otp_verification +class TranslationsStaffAuthenticationOtpVerificationEn { + TranslationsStaffAuthenticationOtpVerificationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Didn't get the code ?' + String get did_not_get_code => 'Didn\'t get the code ?'; + + /// en: 'Resend in $seconds s' + String resend_in({required Object seconds}) => 'Resend in ${seconds} s'; + + /// en: 'Resend code' + String get resend_code => 'Resend code'; +} + +// Path: staff_authentication.profile_setup_page +class TranslationsStaffAuthenticationProfileSetupPageEn { + TranslationsStaffAuthenticationProfileSetupPageEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Step $current of $total' + String step_indicator({required Object current, required Object total}) => 'Step ${current} of ${total}'; + + /// en: 'An error occurred' + String get error_occurred => 'An error occurred'; + + /// en: 'Complete Setup' + String get complete_setup_button => 'Complete Setup'; + + late final TranslationsStaffAuthenticationProfileSetupPageStepsEn steps = TranslationsStaffAuthenticationProfileSetupPageStepsEn._(_root); + late final TranslationsStaffAuthenticationProfileSetupPageBasicInfoEn basic_info = TranslationsStaffAuthenticationProfileSetupPageBasicInfoEn._(_root); + late final TranslationsStaffAuthenticationProfileSetupPageLocationEn location = TranslationsStaffAuthenticationProfileSetupPageLocationEn._(_root); + late final TranslationsStaffAuthenticationProfileSetupPageExperienceEn experience = TranslationsStaffAuthenticationProfileSetupPageExperienceEn._(_root); +} + +// Path: staff_authentication.common +class TranslationsStaffAuthenticationCommonEn { + TranslationsStaffAuthenticationCommonEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Having trouble? ' + String get trouble_question => 'Having trouble? '; + + /// en: 'Contact Support' + String get contact_support => 'Contact Support'; +} + +// Path: client_authentication.get_started_page +class TranslationsClientAuthenticationGetStartedPageEn { + TranslationsClientAuthenticationGetStartedPageEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Take Control of Your Shifts and Events' + String get title => 'Take Control of Your\nShifts and Events'; + + /// en: 'Streamline your operations with powerful tools to manage schedules, track performance, and keep your team on the same page—all in one place' + String get subtitle => 'Streamline your operations with powerful tools to manage schedules, track performance, and keep your team on the same page—all in one place'; + + /// en: 'Sign In' + String get sign_in_button => 'Sign In'; + + /// en: 'Create Account' + String get create_account_button => 'Create Account'; +} + +// Path: client_authentication.sign_in_page +class TranslationsClientAuthenticationSignInPageEn { + TranslationsClientAuthenticationSignInPageEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Welcome Back' + String get title => 'Welcome Back'; + + /// en: 'Sign in to manage your shifts and workers' + String get subtitle => 'Sign in to manage your shifts and workers'; + + /// en: 'Email' + String get email_label => 'Email'; + + /// en: 'Enter your email' + String get email_hint => 'Enter your email'; + + /// en: 'Password' + String get password_label => 'Password'; + + /// en: 'Enter your password' + String get password_hint => 'Enter your password'; + + /// en: 'Forgot Password?' + String get forgot_password => 'Forgot Password?'; + + /// en: 'Sign In' + String get sign_in_button => 'Sign In'; + + /// en: 'or' + String get or_divider => 'or'; + + /// en: 'Sign In with Apple' + String get social_apple => 'Sign In with Apple'; + + /// en: 'Sign In with Google' + String get social_google => 'Sign In with Google'; + + /// en: 'Don't have an account? ' + String get no_account => 'Don\'t have an account? '; + + /// en: 'Sign Up' + String get sign_up_link => 'Sign Up'; +} + +// Path: client_authentication.sign_up_page +class TranslationsClientAuthenticationSignUpPageEn { + TranslationsClientAuthenticationSignUpPageEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Create Account' + String get title => 'Create Account'; + + /// en: 'Get started with Krow for your business' + String get subtitle => 'Get started with Krow for your business'; + + /// en: 'Company Name' + String get company_label => 'Company Name'; + + /// en: 'Enter company name' + String get company_hint => 'Enter company name'; + + /// en: 'Email' + String get email_label => 'Email'; + + /// en: 'Enter your email' + String get email_hint => 'Enter your email'; + + /// en: 'Password' + String get password_label => 'Password'; + + /// en: 'Create a password' + String get password_hint => 'Create a password'; + + /// en: 'Confirm Password' + String get confirm_password_label => 'Confirm Password'; + + /// en: 'Confirm your password' + String get confirm_password_hint => 'Confirm your password'; + + /// en: 'Create Account' + String get create_account_button => 'Create Account'; + + /// en: 'or' + String get or_divider => 'or'; + + /// en: 'Sign Up with Apple' + String get social_apple => 'Sign Up with Apple'; + + /// en: 'Sign Up with Google' + String get social_google => 'Sign Up with Google'; + + /// en: 'Already have an account? ' + String get has_account => 'Already have an account? '; + + /// en: 'Sign In' + String get sign_in_link => 'Sign In'; +} + +// Path: client_home.dashboard +class TranslationsClientHomeDashboardEn { + TranslationsClientHomeDashboardEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Welcome back' + String get welcome_back => 'Welcome back'; + + /// en: 'Edit Mode Active' + String get edit_mode_active => 'Edit Mode Active'; + + /// en: 'Drag to reorder, toggle visibility' + String get drag_instruction => 'Drag to reorder, toggle visibility'; + + /// en: 'Reset' + String get reset => 'Reset'; + + /// en: 'Needed' + String get metric_needed => 'Needed'; + + /// en: 'Filled' + String get metric_filled => 'Filled'; + + /// en: 'Open' + String get metric_open => 'Open'; + + /// en: 'View all' + String get view_all => 'View all'; + + /// en: 'Save $amount/month' + String insight_lightbulb({required Object amount}) => 'Save ${amount}/month'; + + /// en: 'Book 48hrs ahead for better rates' + String get insight_tip => 'Book 48hrs ahead for better rates'; +} + +// Path: client_home.widgets +class TranslationsClientHomeWidgetsEn { + TranslationsClientHomeWidgetsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Quick Actions' + String get actions => 'Quick Actions'; + + /// en: 'Reorder' + String get reorder => 'Reorder'; + + /// en: 'Today's Coverage' + String get coverage => 'Today\'s Coverage'; + + /// en: 'Spending Insights' + String get spending => 'Spending Insights'; + + /// en: 'Live Activity' + String get live_activity => 'Live Activity'; +} + +// Path: client_home.actions +class TranslationsClientHomeActionsEn { + TranslationsClientHomeActionsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'RAPID' + String get rapid => 'RAPID'; + + /// en: 'Urgent same-day' + String get rapid_subtitle => 'Urgent same-day'; + + /// en: 'Create Order' + String get create_order => 'Create Order'; + + /// en: 'Schedule shifts' + String get create_order_subtitle => 'Schedule shifts'; +} + +// Path: client_home.reorder +class TranslationsClientHomeReorderEn { + TranslationsClientHomeReorderEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'REORDER' + String get title => 'REORDER'; + + /// en: 'Reorder' + String get reorder_button => 'Reorder'; + + /// en: '$amount/hr' + String per_hr({required Object amount}) => '${amount}/hr'; +} + +// Path: client_home.form +class TranslationsClientHomeFormEn { + TranslationsClientHomeFormEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Edit & Reorder' + String get edit_reorder => 'Edit & Reorder'; + + /// en: 'Post a New Shift' + String get post_new => 'Post a New Shift'; + + /// en: 'Review and edit the details before posting' + String get review_subtitle => 'Review and edit the details before posting'; + + /// en: 'Date *' + String get date_label => 'Date *'; + + /// en: 'mm/dd/yyyy' + String get date_hint => 'mm/dd/yyyy'; + + /// en: 'Location *' + String get location_label => 'Location *'; + + /// en: 'Business address' + String get location_hint => 'Business address'; + + /// en: 'Positions' + String get positions_title => 'Positions'; + + /// en: 'Add Position' + String get add_position => 'Add Position'; + + /// en: 'Role *' + String get role_label => 'Role *'; + + /// en: 'Select role' + String get role_hint => 'Select role'; + + /// en: 'Start Time *' + String get start_time => 'Start Time *'; + + /// en: 'End Time *' + String get end_time => 'End Time *'; + + /// en: 'Workers Needed *' + String get workers_needed => 'Workers Needed *'; + + /// en: 'Hourly Rate (\$) *' + String get hourly_rate => 'Hourly Rate (\$) *'; + + /// en: 'Post Shift' + String get post_shift => 'Post Shift'; +} + +// Path: staff_authentication.profile_setup_page.steps +class TranslationsStaffAuthenticationProfileSetupPageStepsEn { + TranslationsStaffAuthenticationProfileSetupPageStepsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Basic Info' + String get basic => 'Basic Info'; + + /// en: 'Location' + String get location => 'Location'; + + /// en: 'Experience' + String get experience => 'Experience'; +} + +// Path: staff_authentication.profile_setup_page.basic_info +class TranslationsStaffAuthenticationProfileSetupPageBasicInfoEn { + TranslationsStaffAuthenticationProfileSetupPageBasicInfoEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Let's get to know you' + String get title => 'Let\'s get to know you'; + + /// en: 'Tell us a bit about yourself' + String get subtitle => 'Tell us a bit about yourself'; + + /// en: 'Full Name *' + String get full_name_label => 'Full Name *'; + + /// en: 'John Smith' + String get full_name_hint => 'John Smith'; + + /// en: 'Short Bio' + String get bio_label => 'Short Bio'; + + /// en: 'Experienced hospitality professional...' + String get bio_hint => 'Experienced hospitality professional...'; +} + +// Path: staff_authentication.profile_setup_page.location +class TranslationsStaffAuthenticationProfileSetupPageLocationEn { + TranslationsStaffAuthenticationProfileSetupPageLocationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Where do you want to work?' + String get title => 'Where do you want to work?'; + + /// en: 'Add your preferred work locations' + String get subtitle => 'Add your preferred work locations'; + + /// en: 'Add Location *' + String get add_location_label => 'Add Location *'; + + /// en: 'City or ZIP code' + String get add_location_hint => 'City or ZIP code'; + + /// en: 'Add' + String get add_button => 'Add'; + + /// en: 'Max Distance: $distance miles' + String max_distance({required Object distance}) => 'Max Distance: ${distance} miles'; + + /// en: '5 mi' + String get min_dist_label => '5 mi'; + + /// en: '50 mi' + String get max_dist_label => '50 mi'; +} + +// Path: staff_authentication.profile_setup_page.experience +class TranslationsStaffAuthenticationProfileSetupPageExperienceEn { + TranslationsStaffAuthenticationProfileSetupPageExperienceEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'What are your skills?' + String get title => 'What are your skills?'; + + /// en: 'Select all that apply' + String get subtitle => 'Select all that apply'; + + /// en: 'Skills *' + String get skills_label => 'Skills *'; + + /// en: 'Preferred Industries' + String get industries_label => 'Preferred Industries'; + + late final TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEn skills = TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEn._(_root); + late final TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEn industries = TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEn._(_root); +} + +// Path: staff_authentication.profile_setup_page.experience.skills +class TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEn { + TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Food Service' + String get food_service => 'Food Service'; + + /// en: 'Bartending' + String get bartending => 'Bartending'; + + /// en: 'Warehouse' + String get warehouse => 'Warehouse'; + + /// en: 'Retail' + String get retail => 'Retail'; + + /// en: 'Events' + String get events => 'Events'; + + /// en: 'Customer Service' + String get customer_service => 'Customer Service'; + + /// en: 'Cleaning' + String get cleaning => 'Cleaning'; + + /// en: 'Security' + String get security => 'Security'; + + /// en: 'Driving' + String get driving => 'Driving'; + + /// en: 'Cooking' + String get cooking => 'Cooking'; +} + +// Path: staff_authentication.profile_setup_page.experience.industries +class TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEn { + TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Hospitality' + String get hospitality => 'Hospitality'; + + /// en: 'Food Service' + String get food_service => 'Food Service'; + + /// en: 'Warehouse' + String get warehouse => 'Warehouse'; + + /// en: 'Events' + String get events => 'Events'; + + /// en: 'Retail' + String get retail => 'Retail'; + + /// en: 'Healthcare' + String get healthcare => 'Healthcare'; +} + +/// The flat map containing all translations for locale . +/// Only for edge cases! For simple maps, use the map function of this library. +/// +/// The Dart AOT compiler has issues with very large switch statements, +/// so the map is split into smaller functions (512 entries each). +extension on Translations { + dynamic _flatMapFunction(String path) { + return switch (path) { + 'common.ok' => 'OK', + 'common.cancel' => 'Cancel', + 'common.save' => 'Save', + 'common.delete' => 'Delete', + 'common.continue_text' => 'Continue', + 'settings.language' => 'Language', + 'settings.change_language' => 'Change Language', + 'staff_authentication.get_started_page.title_part1' => 'Work, Grow, ', + 'staff_authentication.get_started_page.title_part2' => 'Elevate', + 'staff_authentication.get_started_page.subtitle' => 'Build your career in hospitality with \nflexibility and freedom.', + 'staff_authentication.get_started_page.sign_up_button' => 'Sign Up', + 'staff_authentication.get_started_page.log_in_button' => 'Log In', + 'staff_authentication.phone_verification_page.validation_error' => 'Please enter a valid 10-digit phone number', + 'staff_authentication.phone_verification_page.send_code_button' => 'Send Code', + 'staff_authentication.phone_verification_page.enter_code_title' => 'Enter verification code', + 'staff_authentication.phone_verification_page.code_sent_message' => 'We sent a 6-digit code to ', + 'staff_authentication.phone_verification_page.code_sent_instruction' => '. Enter it below to verify your account.', + 'staff_authentication.phone_input.title' => 'Verify your phone number', + 'staff_authentication.phone_input.subtitle' => 'We\'ll send you a verification code to get started.', + 'staff_authentication.phone_input.label' => 'Phone Number', + 'staff_authentication.phone_input.hint' => 'Enter your number', + 'staff_authentication.otp_verification.did_not_get_code' => 'Didn\'t get the code ?', + 'staff_authentication.otp_verification.resend_in' => ({required Object seconds}) => 'Resend in ${seconds} s', + 'staff_authentication.otp_verification.resend_code' => 'Resend code', + 'staff_authentication.profile_setup_page.step_indicator' => ({required Object current, required Object total}) => 'Step ${current} of ${total}', + 'staff_authentication.profile_setup_page.error_occurred' => 'An error occurred', + 'staff_authentication.profile_setup_page.complete_setup_button' => 'Complete Setup', + 'staff_authentication.profile_setup_page.steps.basic' => 'Basic Info', + 'staff_authentication.profile_setup_page.steps.location' => 'Location', + 'staff_authentication.profile_setup_page.steps.experience' => 'Experience', + 'staff_authentication.profile_setup_page.basic_info.title' => 'Let\'s get to know you', + 'staff_authentication.profile_setup_page.basic_info.subtitle' => 'Tell us a bit about yourself', + 'staff_authentication.profile_setup_page.basic_info.full_name_label' => 'Full Name *', + 'staff_authentication.profile_setup_page.basic_info.full_name_hint' => 'John Smith', + 'staff_authentication.profile_setup_page.basic_info.bio_label' => 'Short Bio', + 'staff_authentication.profile_setup_page.basic_info.bio_hint' => 'Experienced hospitality professional...', + 'staff_authentication.profile_setup_page.location.title' => 'Where do you want to work?', + 'staff_authentication.profile_setup_page.location.subtitle' => 'Add your preferred work locations', + 'staff_authentication.profile_setup_page.location.add_location_label' => 'Add Location *', + 'staff_authentication.profile_setup_page.location.add_location_hint' => 'City or ZIP code', + 'staff_authentication.profile_setup_page.location.add_button' => 'Add', + 'staff_authentication.profile_setup_page.location.max_distance' => ({required Object distance}) => 'Max Distance: ${distance} miles', + 'staff_authentication.profile_setup_page.location.min_dist_label' => '5 mi', + 'staff_authentication.profile_setup_page.location.max_dist_label' => '50 mi', + 'staff_authentication.profile_setup_page.experience.title' => 'What are your skills?', + 'staff_authentication.profile_setup_page.experience.subtitle' => 'Select all that apply', + 'staff_authentication.profile_setup_page.experience.skills_label' => 'Skills *', + 'staff_authentication.profile_setup_page.experience.industries_label' => 'Preferred Industries', + 'staff_authentication.profile_setup_page.experience.skills.food_service' => 'Food Service', + 'staff_authentication.profile_setup_page.experience.skills.bartending' => 'Bartending', + 'staff_authentication.profile_setup_page.experience.skills.warehouse' => 'Warehouse', + 'staff_authentication.profile_setup_page.experience.skills.retail' => 'Retail', + 'staff_authentication.profile_setup_page.experience.skills.events' => 'Events', + 'staff_authentication.profile_setup_page.experience.skills.customer_service' => 'Customer Service', + 'staff_authentication.profile_setup_page.experience.skills.cleaning' => 'Cleaning', + 'staff_authentication.profile_setup_page.experience.skills.security' => 'Security', + 'staff_authentication.profile_setup_page.experience.skills.driving' => 'Driving', + 'staff_authentication.profile_setup_page.experience.skills.cooking' => 'Cooking', + 'staff_authentication.profile_setup_page.experience.industries.hospitality' => 'Hospitality', + 'staff_authentication.profile_setup_page.experience.industries.food_service' => 'Food Service', + 'staff_authentication.profile_setup_page.experience.industries.warehouse' => 'Warehouse', + 'staff_authentication.profile_setup_page.experience.industries.events' => 'Events', + 'staff_authentication.profile_setup_page.experience.industries.retail' => 'Retail', + 'staff_authentication.profile_setup_page.experience.industries.healthcare' => 'Healthcare', + 'staff_authentication.common.trouble_question' => 'Having trouble? ', + 'staff_authentication.common.contact_support' => 'Contact Support', + 'client_authentication.get_started_page.title' => 'Take Control of Your\nShifts and Events', + 'client_authentication.get_started_page.subtitle' => 'Streamline your operations with powerful tools to manage schedules, track performance, and keep your team on the same page—all in one place', + 'client_authentication.get_started_page.sign_in_button' => 'Sign In', + 'client_authentication.get_started_page.create_account_button' => 'Create Account', + 'client_authentication.sign_in_page.title' => 'Welcome Back', + 'client_authentication.sign_in_page.subtitle' => 'Sign in to manage your shifts and workers', + 'client_authentication.sign_in_page.email_label' => 'Email', + 'client_authentication.sign_in_page.email_hint' => 'Enter your email', + 'client_authentication.sign_in_page.password_label' => 'Password', + 'client_authentication.sign_in_page.password_hint' => 'Enter your password', + 'client_authentication.sign_in_page.forgot_password' => 'Forgot Password?', + 'client_authentication.sign_in_page.sign_in_button' => 'Sign In', + 'client_authentication.sign_in_page.or_divider' => 'or', + 'client_authentication.sign_in_page.social_apple' => 'Sign In with Apple', + 'client_authentication.sign_in_page.social_google' => 'Sign In with Google', + 'client_authentication.sign_in_page.no_account' => 'Don\'t have an account? ', + 'client_authentication.sign_in_page.sign_up_link' => 'Sign Up', + 'client_authentication.sign_up_page.title' => 'Create Account', + 'client_authentication.sign_up_page.subtitle' => 'Get started with Krow for your business', + 'client_authentication.sign_up_page.company_label' => 'Company Name', + 'client_authentication.sign_up_page.company_hint' => 'Enter company name', + 'client_authentication.sign_up_page.email_label' => 'Email', + 'client_authentication.sign_up_page.email_hint' => 'Enter your email', + 'client_authentication.sign_up_page.password_label' => 'Password', + 'client_authentication.sign_up_page.password_hint' => 'Create a password', + 'client_authentication.sign_up_page.confirm_password_label' => 'Confirm Password', + 'client_authentication.sign_up_page.confirm_password_hint' => 'Confirm your password', + 'client_authentication.sign_up_page.create_account_button' => 'Create Account', + 'client_authentication.sign_up_page.or_divider' => 'or', + 'client_authentication.sign_up_page.social_apple' => 'Sign Up with Apple', + 'client_authentication.sign_up_page.social_google' => 'Sign Up with Google', + 'client_authentication.sign_up_page.has_account' => 'Already have an account? ', + 'client_authentication.sign_up_page.sign_in_link' => 'Sign In', + 'client_home.shift_created_success' => 'Shift order submitted successfully', + 'client_home.dashboard.welcome_back' => 'Welcome back', + 'client_home.dashboard.edit_mode_active' => 'Edit Mode Active', + 'client_home.dashboard.drag_instruction' => 'Drag to reorder, toggle visibility', + 'client_home.dashboard.reset' => 'Reset', + 'client_home.dashboard.metric_needed' => 'Needed', + 'client_home.dashboard.metric_filled' => 'Filled', + 'client_home.dashboard.metric_open' => 'Open', + 'client_home.dashboard.view_all' => 'View all', + 'client_home.dashboard.insight_lightbulb' => ({required Object amount}) => 'Save ${amount}/month', + 'client_home.dashboard.insight_tip' => 'Book 48hrs ahead for better rates', + 'client_home.widgets.actions' => 'Quick Actions', + 'client_home.widgets.reorder' => 'Reorder', + 'client_home.widgets.coverage' => 'Today\'s Coverage', + 'client_home.widgets.spending' => 'Spending Insights', + 'client_home.widgets.live_activity' => 'Live Activity', + 'client_home.actions.rapid' => 'RAPID', + 'client_home.actions.rapid_subtitle' => 'Urgent same-day', + 'client_home.actions.create_order' => 'Create Order', + 'client_home.actions.create_order_subtitle' => 'Schedule shifts', + 'client_home.reorder.title' => 'REORDER', + 'client_home.reorder.reorder_button' => 'Reorder', + 'client_home.reorder.per_hr' => ({required Object amount}) => '${amount}/hr', + 'client_home.form.edit_reorder' => 'Edit & Reorder', + 'client_home.form.post_new' => 'Post a New Shift', + 'client_home.form.review_subtitle' => 'Review and edit the details before posting', + 'client_home.form.date_label' => 'Date *', + 'client_home.form.date_hint' => 'mm/dd/yyyy', + 'client_home.form.location_label' => 'Location *', + 'client_home.form.location_hint' => 'Business address', + 'client_home.form.positions_title' => 'Positions', + 'client_home.form.add_position' => 'Add Position', + 'client_home.form.role_label' => 'Role *', + 'client_home.form.role_hint' => 'Select role', + 'client_home.form.start_time' => 'Start Time *', + 'client_home.form.end_time' => 'End Time *', + 'client_home.form.workers_needed' => 'Workers Needed *', + 'client_home.form.hourly_rate' => 'Hourly Rate (\$) *', + 'client_home.form.post_shift' => 'Post Shift', + _ => null, + }; + } +} diff --git a/apps/packages/core_localization/lib/src/l10n/strings_es.g.dart b/apps/packages/core_localization/lib/src/l10n/strings_es.g.dart new file mode 100644 index 00000000..10fd7a44 --- /dev/null +++ b/apps/packages/core_localization/lib/src/l10n/strings_es.g.dart @@ -0,0 +1,579 @@ +/// +/// Generated file. Do not edit. +/// +// coverage:ignore-file +// ignore_for_file: type=lint, unused_import +// dart format off + +import 'package:flutter/widgets.dart'; +import 'package:intl/intl.dart'; +import 'package:slang/generated.dart'; +import 'strings.g.dart'; + +// Path: +class TranslationsEs with BaseTranslations implements Translations { + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + TranslationsEs({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver, TranslationMetadata? meta}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = meta ?? TranslationMetadata( + locale: AppLocale.es, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + @override dynamic operator[](String key) => $meta.getTranslation(key); + + late final TranslationsEs _root = this; // ignore: unused_field + + @override + TranslationsEs $copyWith({TranslationMetadata? meta}) => TranslationsEs(meta: meta ?? this.$meta); + + // Translations + @override late final _TranslationsCommonEs common = _TranslationsCommonEs._(_root); + @override late final _TranslationsSettingsEs settings = _TranslationsSettingsEs._(_root); + @override late final _TranslationsStaffAuthenticationEs staff_authentication = _TranslationsStaffAuthenticationEs._(_root); + @override late final _TranslationsClientAuthenticationEs client_authentication = _TranslationsClientAuthenticationEs._(_root); + @override late final _TranslationsClientHomeEs client_home = _TranslationsClientHomeEs._(_root); +} + +// Path: common +class _TranslationsCommonEs implements TranslationsCommonEn { + _TranslationsCommonEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get ok => 'Aceptar'; + @override String get cancel => 'Cancelar'; + @override String get save => 'Guardar'; + @override String get delete => 'Eliminar'; + @override String get continue_text => 'Continuar'; +} + +// Path: settings +class _TranslationsSettingsEs implements TranslationsSettingsEn { + _TranslationsSettingsEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get language => 'Idioma'; + @override String get change_language => 'Cambiar Idioma'; +} + +// Path: staff_authentication +class _TranslationsStaffAuthenticationEs implements TranslationsStaffAuthenticationEn { + _TranslationsStaffAuthenticationEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override late final _TranslationsStaffAuthenticationGetStartedPageEs get_started_page = _TranslationsStaffAuthenticationGetStartedPageEs._(_root); + @override late final _TranslationsStaffAuthenticationPhoneVerificationPageEs phone_verification_page = _TranslationsStaffAuthenticationPhoneVerificationPageEs._(_root); + @override late final _TranslationsStaffAuthenticationPhoneInputEs phone_input = _TranslationsStaffAuthenticationPhoneInputEs._(_root); + @override late final _TranslationsStaffAuthenticationOtpVerificationEs otp_verification = _TranslationsStaffAuthenticationOtpVerificationEs._(_root); + @override late final _TranslationsStaffAuthenticationProfileSetupPageEs profile_setup_page = _TranslationsStaffAuthenticationProfileSetupPageEs._(_root); + @override late final _TranslationsStaffAuthenticationCommonEs common = _TranslationsStaffAuthenticationCommonEs._(_root); +} + +// Path: client_authentication +class _TranslationsClientAuthenticationEs implements TranslationsClientAuthenticationEn { + _TranslationsClientAuthenticationEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override late final _TranslationsClientAuthenticationGetStartedPageEs get_started_page = _TranslationsClientAuthenticationGetStartedPageEs._(_root); + @override late final _TranslationsClientAuthenticationSignInPageEs sign_in_page = _TranslationsClientAuthenticationSignInPageEs._(_root); + @override late final _TranslationsClientAuthenticationSignUpPageEs sign_up_page = _TranslationsClientAuthenticationSignUpPageEs._(_root); +} + +// Path: client_home +class _TranslationsClientHomeEs implements TranslationsClientHomeEn { + _TranslationsClientHomeEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get shift_created_success => 'Orden de turno enviada con éxito'; + @override late final _TranslationsClientHomeDashboardEs dashboard = _TranslationsClientHomeDashboardEs._(_root); + @override late final _TranslationsClientHomeWidgetsEs widgets = _TranslationsClientHomeWidgetsEs._(_root); + @override late final _TranslationsClientHomeActionsEs actions = _TranslationsClientHomeActionsEs._(_root); + @override late final _TranslationsClientHomeReorderEs reorder = _TranslationsClientHomeReorderEs._(_root); + @override late final _TranslationsClientHomeFormEs form = _TranslationsClientHomeFormEs._(_root); +} + +// Path: staff_authentication.get_started_page +class _TranslationsStaffAuthenticationGetStartedPageEs implements TranslationsStaffAuthenticationGetStartedPageEn { + _TranslationsStaffAuthenticationGetStartedPageEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title_part1 => 'Trabaja, Crece, '; + @override String get title_part2 => 'Elévate'; + @override String get subtitle => 'Construye tu carrera en hostelería con \nflexibilidad y libertad.'; + @override String get sign_up_button => 'Registrarse'; + @override String get log_in_button => 'Iniciar sesión'; +} + +// Path: staff_authentication.phone_verification_page +class _TranslationsStaffAuthenticationPhoneVerificationPageEs implements TranslationsStaffAuthenticationPhoneVerificationPageEn { + _TranslationsStaffAuthenticationPhoneVerificationPageEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get validation_error => 'Por favor, ingresa un número de teléfono válido de 10 dígitos'; + @override String get send_code_button => 'Enviar código'; + @override String get enter_code_title => 'Ingresa el código de verificación'; + @override String get code_sent_message => 'Enviamos un código de 6 dígitos a '; + @override String get code_sent_instruction => '. Ingrésalo a continuación para verificar tu cuenta.'; +} + +// Path: staff_authentication.phone_input +class _TranslationsStaffAuthenticationPhoneInputEs implements TranslationsStaffAuthenticationPhoneInputEn { + _TranslationsStaffAuthenticationPhoneInputEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'Verifica tu número de teléfono'; + @override String get subtitle => 'Te enviaremos un código de verificación para comenzar.'; + @override String get label => 'Número de teléfono'; + @override String get hint => 'Ingresa tu número'; +} + +// Path: staff_authentication.otp_verification +class _TranslationsStaffAuthenticationOtpVerificationEs implements TranslationsStaffAuthenticationOtpVerificationEn { + _TranslationsStaffAuthenticationOtpVerificationEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get did_not_get_code => '¿No recibiste el código?'; + @override String resend_in({required Object seconds}) => 'Reenviar en ${seconds} s'; + @override String get resend_code => 'Reenviar código'; +} + +// Path: staff_authentication.profile_setup_page +class _TranslationsStaffAuthenticationProfileSetupPageEs implements TranslationsStaffAuthenticationProfileSetupPageEn { + _TranslationsStaffAuthenticationProfileSetupPageEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String step_indicator({required Object current, required Object total}) => 'Paso ${current} de ${total}'; + @override String get error_occurred => 'Ocurrió un error'; + @override String get complete_setup_button => 'Completar configuración'; + @override late final _TranslationsStaffAuthenticationProfileSetupPageStepsEs steps = _TranslationsStaffAuthenticationProfileSetupPageStepsEs._(_root); + @override late final _TranslationsStaffAuthenticationProfileSetupPageBasicInfoEs basic_info = _TranslationsStaffAuthenticationProfileSetupPageBasicInfoEs._(_root); + @override late final _TranslationsStaffAuthenticationProfileSetupPageLocationEs location = _TranslationsStaffAuthenticationProfileSetupPageLocationEs._(_root); + @override late final _TranslationsStaffAuthenticationProfileSetupPageExperienceEs experience = _TranslationsStaffAuthenticationProfileSetupPageExperienceEs._(_root); +} + +// Path: staff_authentication.common +class _TranslationsStaffAuthenticationCommonEs implements TranslationsStaffAuthenticationCommonEn { + _TranslationsStaffAuthenticationCommonEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get trouble_question => '¿Tienes problemas? '; + @override String get contact_support => 'Contactar a soporte'; +} + +// Path: client_authentication.get_started_page +class _TranslationsClientAuthenticationGetStartedPageEs implements TranslationsClientAuthenticationGetStartedPageEn { + _TranslationsClientAuthenticationGetStartedPageEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'Toma el control de tus\nturnos y eventos'; + @override String get subtitle => 'Optimiza tus operaciones con potentes herramientas para gestionar horarios, realizar un seguimiento del rendimiento y mantener a tu equipo en la misma página, todo en un solo lugar'; + @override String get sign_in_button => 'Iniciar sesión'; + @override String get create_account_button => 'Crear cuenta'; +} + +// Path: client_authentication.sign_in_page +class _TranslationsClientAuthenticationSignInPageEs implements TranslationsClientAuthenticationSignInPageEn { + _TranslationsClientAuthenticationSignInPageEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'Bienvenido de nuevo'; + @override String get subtitle => 'Inicia sesión para gestionar tus turnos y trabajadores'; + @override String get email_label => 'Correo electrónico'; + @override String get email_hint => 'Ingresa tu correo electrónico'; + @override String get password_label => 'Contraseña'; + @override String get password_hint => 'Ingresa tu contraseña'; + @override String get forgot_password => '¿Olvidaste tu contraseña?'; + @override String get sign_in_button => 'Iniciar sesión'; + @override String get or_divider => 'o'; + @override String get social_apple => 'Iniciar sesión con Apple'; + @override String get social_google => 'Iniciar sesión con Google'; + @override String get no_account => '¿No tienes una cuenta? '; + @override String get sign_up_link => 'Regístrate'; +} + +// Path: client_authentication.sign_up_page +class _TranslationsClientAuthenticationSignUpPageEs implements TranslationsClientAuthenticationSignUpPageEn { + _TranslationsClientAuthenticationSignUpPageEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'Crear cuenta'; + @override String get subtitle => 'Comienza con Krow para tu negocio'; + @override String get company_label => 'Nombre de la empresa'; + @override String get company_hint => 'Ingresa el nombre de la empresa'; + @override String get email_label => 'Correo electrónico'; + @override String get email_hint => 'Ingresa tu correo electrónico'; + @override String get password_label => 'Contraseña'; + @override String get password_hint => 'Crea una contraseña'; + @override String get confirm_password_label => 'Confirmar contraseña'; + @override String get confirm_password_hint => 'Confirma tu contraseña'; + @override String get create_account_button => 'Crear cuenta'; + @override String get or_divider => 'o'; + @override String get social_apple => 'Regístrate con Apple'; + @override String get social_google => 'Regístrate con Google'; + @override String get has_account => '¿Ya tienes una cuenta? '; + @override String get sign_in_link => 'Iniciar sesión'; +} + +// Path: client_home.dashboard +class _TranslationsClientHomeDashboardEs implements TranslationsClientHomeDashboardEn { + _TranslationsClientHomeDashboardEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get welcome_back => 'Bienvenido de nuevo'; + @override String get edit_mode_active => 'Modo Edición Activo'; + @override String get drag_instruction => 'Arrastra para reordenar, cambia la visibilidad'; + @override String get reset => 'Restablecer'; + @override String get metric_needed => 'Necesario'; + @override String get metric_filled => 'Lleno'; + @override String get metric_open => 'Abierto'; + @override String get view_all => 'Ver todo'; + @override String insight_lightbulb({required Object amount}) => 'Ahorra ${amount}/mes'; + @override String get insight_tip => 'Reserva con 48h de antelación para mejores tarifas'; +} + +// Path: client_home.widgets +class _TranslationsClientHomeWidgetsEs implements TranslationsClientHomeWidgetsEn { + _TranslationsClientHomeWidgetsEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get actions => 'Acciones Rápidas'; + @override String get reorder => 'Reordenar'; + @override String get coverage => 'Cobertura de Hoy'; + @override String get spending => 'Información de Gastos'; + @override String get live_activity => 'Actividad en Vivo'; +} + +// Path: client_home.actions +class _TranslationsClientHomeActionsEs implements TranslationsClientHomeActionsEn { + _TranslationsClientHomeActionsEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get rapid => 'RÁPIDO'; + @override String get rapid_subtitle => 'Urgente mismo día'; + @override String get create_order => 'Crear Orden'; + @override String get create_order_subtitle => 'Programar turnos'; +} + +// Path: client_home.reorder +class _TranslationsClientHomeReorderEs implements TranslationsClientHomeReorderEn { + _TranslationsClientHomeReorderEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'REORDENAR'; + @override String get reorder_button => 'Reordenar'; + @override String per_hr({required Object amount}) => '${amount}/hr'; +} + +// Path: client_home.form +class _TranslationsClientHomeFormEs implements TranslationsClientHomeFormEn { + _TranslationsClientHomeFormEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get edit_reorder => 'Editar y Reordenar'; + @override String get post_new => 'Publicar un Nuevo Turno'; + @override String get review_subtitle => 'Revisa y edita los detalles antes de publicar'; + @override String get date_label => 'Fecha *'; + @override String get date_hint => 'mm/dd/aaaa'; + @override String get location_label => 'Ubicación *'; + @override String get location_hint => 'Dirección del negocio'; + @override String get positions_title => 'Posiciones'; + @override String get add_position => 'Añadir Posición'; + @override String get role_label => 'Rol *'; + @override String get role_hint => 'Seleccionar rol'; + @override String get start_time => 'Hora de Inicio *'; + @override String get end_time => 'Hora de Fin *'; + @override String get workers_needed => 'Trabajadores Necesarios *'; + @override String get hourly_rate => 'Tarifa por hora (\$) *'; + @override String get post_shift => 'Publicar Turno'; +} + +// Path: staff_authentication.profile_setup_page.steps +class _TranslationsStaffAuthenticationProfileSetupPageStepsEs implements TranslationsStaffAuthenticationProfileSetupPageStepsEn { + _TranslationsStaffAuthenticationProfileSetupPageStepsEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get basic => 'Información básica'; + @override String get location => 'Ubicación'; + @override String get experience => 'Experiencia'; +} + +// Path: staff_authentication.profile_setup_page.basic_info +class _TranslationsStaffAuthenticationProfileSetupPageBasicInfoEs implements TranslationsStaffAuthenticationProfileSetupPageBasicInfoEn { + _TranslationsStaffAuthenticationProfileSetupPageBasicInfoEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'Conozcámonos'; + @override String get subtitle => 'Cuéntanos un poco sobre ti'; + @override String get full_name_label => 'Nombre completo *'; + @override String get full_name_hint => 'Juan Pérez'; + @override String get bio_label => 'Biografía corta'; + @override String get bio_hint => 'Profesional experimentado en hostelería...'; +} + +// Path: staff_authentication.profile_setup_page.location +class _TranslationsStaffAuthenticationProfileSetupPageLocationEs implements TranslationsStaffAuthenticationProfileSetupPageLocationEn { + _TranslationsStaffAuthenticationProfileSetupPageLocationEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => '¿Dónde quieres trabajar?'; + @override String get subtitle => 'Agrega tus ubicaciones de trabajo preferidas'; + @override String get add_location_label => 'Agregar ubicación *'; + @override String get add_location_hint => 'Ciudad o código postal'; + @override String get add_button => 'Agregar'; + @override String max_distance({required Object distance}) => 'Distancia máxima: ${distance} millas'; + @override String get min_dist_label => '5 mi'; + @override String get max_dist_label => '50 mi'; +} + +// Path: staff_authentication.profile_setup_page.experience +class _TranslationsStaffAuthenticationProfileSetupPageExperienceEs implements TranslationsStaffAuthenticationProfileSetupPageExperienceEn { + _TranslationsStaffAuthenticationProfileSetupPageExperienceEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => '¿Cuáles son tus habilidades?'; + @override String get subtitle => 'Selecciona todas las que correspondan'; + @override String get skills_label => 'Habilidades *'; + @override String get industries_label => 'Industrias preferidas'; + @override late final _TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEs skills = _TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEs._(_root); + @override late final _TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEs industries = _TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEs._(_root); +} + +// Path: staff_authentication.profile_setup_page.experience.skills +class _TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEs implements TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEn { + _TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get food_service => 'Servicio de comida'; + @override String get bartending => 'Preparación de bebidas'; + @override String get warehouse => 'Almacén'; + @override String get retail => 'Venta minorista'; + @override String get events => 'Eventos'; + @override String get customer_service => 'Servicio al cliente'; + @override String get cleaning => 'Limpieza'; + @override String get security => 'Seguridad'; + @override String get driving => 'Conducción'; + @override String get cooking => 'Cocina'; +} + +// Path: staff_authentication.profile_setup_page.experience.industries +class _TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEs implements TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEn { + _TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get hospitality => 'Hostelería'; + @override String get food_service => 'Servicio de comida'; + @override String get warehouse => 'Almacén'; + @override String get events => 'Eventos'; + @override String get retail => 'Venta minorista'; + @override String get healthcare => 'Atención médica'; +} + +/// The flat map containing all translations for locale . +/// Only for edge cases! For simple maps, use the map function of this library. +/// +/// The Dart AOT compiler has issues with very large switch statements, +/// so the map is split into smaller functions (512 entries each). +extension on TranslationsEs { + dynamic _flatMapFunction(String path) { + return switch (path) { + 'common.ok' => 'Aceptar', + 'common.cancel' => 'Cancelar', + 'common.save' => 'Guardar', + 'common.delete' => 'Eliminar', + 'common.continue_text' => 'Continuar', + 'settings.language' => 'Idioma', + 'settings.change_language' => 'Cambiar Idioma', + 'staff_authentication.get_started_page.title_part1' => 'Trabaja, Crece, ', + 'staff_authentication.get_started_page.title_part2' => 'Elévate', + 'staff_authentication.get_started_page.subtitle' => 'Construye tu carrera en hostelería con \nflexibilidad y libertad.', + 'staff_authentication.get_started_page.sign_up_button' => 'Registrarse', + 'staff_authentication.get_started_page.log_in_button' => 'Iniciar sesión', + 'staff_authentication.phone_verification_page.validation_error' => 'Por favor, ingresa un número de teléfono válido de 10 dígitos', + 'staff_authentication.phone_verification_page.send_code_button' => 'Enviar código', + 'staff_authentication.phone_verification_page.enter_code_title' => 'Ingresa el código de verificación', + 'staff_authentication.phone_verification_page.code_sent_message' => 'Enviamos un código de 6 dígitos a ', + 'staff_authentication.phone_verification_page.code_sent_instruction' => '. Ingrésalo a continuación para verificar tu cuenta.', + 'staff_authentication.phone_input.title' => 'Verifica tu número de teléfono', + 'staff_authentication.phone_input.subtitle' => 'Te enviaremos un código de verificación para comenzar.', + 'staff_authentication.phone_input.label' => 'Número de teléfono', + 'staff_authentication.phone_input.hint' => 'Ingresa tu número', + 'staff_authentication.otp_verification.did_not_get_code' => '¿No recibiste el código?', + 'staff_authentication.otp_verification.resend_in' => ({required Object seconds}) => 'Reenviar en ${seconds} s', + 'staff_authentication.otp_verification.resend_code' => 'Reenviar código', + 'staff_authentication.profile_setup_page.step_indicator' => ({required Object current, required Object total}) => 'Paso ${current} de ${total}', + 'staff_authentication.profile_setup_page.error_occurred' => 'Ocurrió un error', + 'staff_authentication.profile_setup_page.complete_setup_button' => 'Completar configuración', + 'staff_authentication.profile_setup_page.steps.basic' => 'Información básica', + 'staff_authentication.profile_setup_page.steps.location' => 'Ubicación', + 'staff_authentication.profile_setup_page.steps.experience' => 'Experiencia', + 'staff_authentication.profile_setup_page.basic_info.title' => 'Conozcámonos', + 'staff_authentication.profile_setup_page.basic_info.subtitle' => 'Cuéntanos un poco sobre ti', + 'staff_authentication.profile_setup_page.basic_info.full_name_label' => 'Nombre completo *', + 'staff_authentication.profile_setup_page.basic_info.full_name_hint' => 'Juan Pérez', + 'staff_authentication.profile_setup_page.basic_info.bio_label' => 'Biografía corta', + 'staff_authentication.profile_setup_page.basic_info.bio_hint' => 'Profesional experimentado en hostelería...', + 'staff_authentication.profile_setup_page.location.title' => '¿Dónde quieres trabajar?', + 'staff_authentication.profile_setup_page.location.subtitle' => 'Agrega tus ubicaciones de trabajo preferidas', + 'staff_authentication.profile_setup_page.location.add_location_label' => 'Agregar ubicación *', + 'staff_authentication.profile_setup_page.location.add_location_hint' => 'Ciudad o código postal', + 'staff_authentication.profile_setup_page.location.add_button' => 'Agregar', + 'staff_authentication.profile_setup_page.location.max_distance' => ({required Object distance}) => 'Distancia máxima: ${distance} millas', + 'staff_authentication.profile_setup_page.location.min_dist_label' => '5 mi', + 'staff_authentication.profile_setup_page.location.max_dist_label' => '50 mi', + 'staff_authentication.profile_setup_page.experience.title' => '¿Cuáles son tus habilidades?', + 'staff_authentication.profile_setup_page.experience.subtitle' => 'Selecciona todas las que correspondan', + 'staff_authentication.profile_setup_page.experience.skills_label' => 'Habilidades *', + 'staff_authentication.profile_setup_page.experience.industries_label' => 'Industrias preferidas', + 'staff_authentication.profile_setup_page.experience.skills.food_service' => 'Servicio de comida', + 'staff_authentication.profile_setup_page.experience.skills.bartending' => 'Preparación de bebidas', + 'staff_authentication.profile_setup_page.experience.skills.warehouse' => 'Almacén', + 'staff_authentication.profile_setup_page.experience.skills.retail' => 'Venta minorista', + 'staff_authentication.profile_setup_page.experience.skills.events' => 'Eventos', + 'staff_authentication.profile_setup_page.experience.skills.customer_service' => 'Servicio al cliente', + 'staff_authentication.profile_setup_page.experience.skills.cleaning' => 'Limpieza', + 'staff_authentication.profile_setup_page.experience.skills.security' => 'Seguridad', + 'staff_authentication.profile_setup_page.experience.skills.driving' => 'Conducción', + 'staff_authentication.profile_setup_page.experience.skills.cooking' => 'Cocina', + 'staff_authentication.profile_setup_page.experience.industries.hospitality' => 'Hostelería', + 'staff_authentication.profile_setup_page.experience.industries.food_service' => 'Servicio de comida', + 'staff_authentication.profile_setup_page.experience.industries.warehouse' => 'Almacén', + 'staff_authentication.profile_setup_page.experience.industries.events' => 'Eventos', + 'staff_authentication.profile_setup_page.experience.industries.retail' => 'Venta minorista', + 'staff_authentication.profile_setup_page.experience.industries.healthcare' => 'Atención médica', + 'staff_authentication.common.trouble_question' => '¿Tienes problemas? ', + 'staff_authentication.common.contact_support' => 'Contactar a soporte', + 'client_authentication.get_started_page.title' => 'Toma el control de tus\nturnos y eventos', + 'client_authentication.get_started_page.subtitle' => 'Optimiza tus operaciones con potentes herramientas para gestionar horarios, realizar un seguimiento del rendimiento y mantener a tu equipo en la misma página, todo en un solo lugar', + 'client_authentication.get_started_page.sign_in_button' => 'Iniciar sesión', + 'client_authentication.get_started_page.create_account_button' => 'Crear cuenta', + 'client_authentication.sign_in_page.title' => 'Bienvenido de nuevo', + 'client_authentication.sign_in_page.subtitle' => 'Inicia sesión para gestionar tus turnos y trabajadores', + 'client_authentication.sign_in_page.email_label' => 'Correo electrónico', + 'client_authentication.sign_in_page.email_hint' => 'Ingresa tu correo electrónico', + 'client_authentication.sign_in_page.password_label' => 'Contraseña', + 'client_authentication.sign_in_page.password_hint' => 'Ingresa tu contraseña', + 'client_authentication.sign_in_page.forgot_password' => '¿Olvidaste tu contraseña?', + 'client_authentication.sign_in_page.sign_in_button' => 'Iniciar sesión', + 'client_authentication.sign_in_page.or_divider' => 'o', + 'client_authentication.sign_in_page.social_apple' => 'Iniciar sesión con Apple', + 'client_authentication.sign_in_page.social_google' => 'Iniciar sesión con Google', + 'client_authentication.sign_in_page.no_account' => '¿No tienes una cuenta? ', + 'client_authentication.sign_in_page.sign_up_link' => 'Regístrate', + 'client_authentication.sign_up_page.title' => 'Crear cuenta', + 'client_authentication.sign_up_page.subtitle' => 'Comienza con Krow para tu negocio', + 'client_authentication.sign_up_page.company_label' => 'Nombre de la empresa', + 'client_authentication.sign_up_page.company_hint' => 'Ingresa el nombre de la empresa', + 'client_authentication.sign_up_page.email_label' => 'Correo electrónico', + 'client_authentication.sign_up_page.email_hint' => 'Ingresa tu correo electrónico', + 'client_authentication.sign_up_page.password_label' => 'Contraseña', + 'client_authentication.sign_up_page.password_hint' => 'Crea una contraseña', + 'client_authentication.sign_up_page.confirm_password_label' => 'Confirmar contraseña', + 'client_authentication.sign_up_page.confirm_password_hint' => 'Confirma tu contraseña', + 'client_authentication.sign_up_page.create_account_button' => 'Crear cuenta', + 'client_authentication.sign_up_page.or_divider' => 'o', + 'client_authentication.sign_up_page.social_apple' => 'Regístrate con Apple', + 'client_authentication.sign_up_page.social_google' => 'Regístrate con Google', + 'client_authentication.sign_up_page.has_account' => '¿Ya tienes una cuenta? ', + 'client_authentication.sign_up_page.sign_in_link' => 'Iniciar sesión', + 'client_home.shift_created_success' => 'Orden de turno enviada con éxito', + 'client_home.dashboard.welcome_back' => 'Bienvenido de nuevo', + 'client_home.dashboard.edit_mode_active' => 'Modo Edición Activo', + 'client_home.dashboard.drag_instruction' => 'Arrastra para reordenar, cambia la visibilidad', + 'client_home.dashboard.reset' => 'Restablecer', + 'client_home.dashboard.metric_needed' => 'Necesario', + 'client_home.dashboard.metric_filled' => 'Lleno', + 'client_home.dashboard.metric_open' => 'Abierto', + 'client_home.dashboard.view_all' => 'Ver todo', + 'client_home.dashboard.insight_lightbulb' => ({required Object amount}) => 'Ahorra ${amount}/mes', + 'client_home.dashboard.insight_tip' => 'Reserva con 48h de antelación para mejores tarifas', + 'client_home.widgets.actions' => 'Acciones Rápidas', + 'client_home.widgets.reorder' => 'Reordenar', + 'client_home.widgets.coverage' => 'Cobertura de Hoy', + 'client_home.widgets.spending' => 'Información de Gastos', + 'client_home.widgets.live_activity' => 'Actividad en Vivo', + 'client_home.actions.rapid' => 'RÁPIDO', + 'client_home.actions.rapid_subtitle' => 'Urgente mismo día', + 'client_home.actions.create_order' => 'Crear Orden', + 'client_home.actions.create_order_subtitle' => 'Programar turnos', + 'client_home.reorder.title' => 'REORDENAR', + 'client_home.reorder.reorder_button' => 'Reordenar', + 'client_home.reorder.per_hr' => ({required Object amount}) => '${amount}/hr', + 'client_home.form.edit_reorder' => 'Editar y Reordenar', + 'client_home.form.post_new' => 'Publicar un Nuevo Turno', + 'client_home.form.review_subtitle' => 'Revisa y edita los detalles antes de publicar', + 'client_home.form.date_label' => 'Fecha *', + 'client_home.form.date_hint' => 'mm/dd/aaaa', + 'client_home.form.location_label' => 'Ubicación *', + 'client_home.form.location_hint' => 'Dirección del negocio', + 'client_home.form.positions_title' => 'Posiciones', + 'client_home.form.add_position' => 'Añadir Posición', + 'client_home.form.role_label' => 'Rol *', + 'client_home.form.role_hint' => 'Seleccionar rol', + 'client_home.form.start_time' => 'Hora de Inicio *', + 'client_home.form.end_time' => 'Hora de Fin *', + 'client_home.form.workers_needed' => 'Trabajadores Necesarios *', + 'client_home.form.hourly_rate' => 'Tarifa por hora (\$) *', + 'client_home.form.post_shift' => 'Publicar Turno', + _ => null, + }; + } +} diff --git a/apps/packages/core_localization/lib/src/localization_module.dart b/apps/packages/core_localization/lib/src/localization_module.dart new file mode 100644 index 00000000..bbc87c6d --- /dev/null +++ b/apps/packages/core_localization/lib/src/localization_module.dart @@ -0,0 +1,46 @@ +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'data/datasources/locale_local_data_source.dart'; +import 'data/repositories_impl/locale_repository_impl.dart'; +import 'domain/repositories/locale_repository_interface.dart'; +import 'domain/usecases/get_locale_use_case.dart'; +import 'domain/usecases/set_locale_use_case.dart'; +import 'bloc/locale_bloc.dart'; + +/// A [ModularModule] that manages localization dependencies. +/// +/// This module registers all necessary data sources, repositories, use cases, +/// and the BLoC required for application-wide localization management. +class LocalizationModule extends Module { + @override + void binds(Injector i) { + // External Dependencies + i.addInstance(SharedPreferencesAsync()); + + // Data Sources + i.addSingleton( + () => LocaleLocalDataSourceImpl(i.get()), + ); + + // Repositories + i.addSingleton( + () => LocaleRepositoryImpl(i.get()), + ); + + // Use Cases + i.addSingleton( + () => GetLocaleUseCase(i.get()), + ); + i.addSingleton( + () => SetLocaleUseCase(i.get()), + ); + + // BLoCs + i.addSingleton( + () => LocaleBloc( + getLocaleUseCase: i.get(), + setLocaleUseCase: i.get(), + ), + ); + } +} diff --git a/apps/packages/core_localization/pubspec.yaml b/apps/packages/core_localization/pubspec.yaml new file mode 100644 index 00000000..7b12cda7 --- /dev/null +++ b/apps/packages/core_localization/pubspec.yaml @@ -0,0 +1,38 @@ +name: core_localization +description: "Core localization package using Slang." +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + krow_core: + path: ../core + flutter_bloc: ^8.1.0 + flutter_modular: ^6.3.2 + slang: ^4.12.0 + slang_flutter: ^4.12.0 + shared_preferences: ^2.5.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + build_runner: ^2.4.15 + slang_build_runner: ^4.12.0 + +flutter: + uses-material-design: true + +slang: + base_locale: en + fallback_strategy: base_locale + input_directory: lib/src/l10n + input_file_pattern: .i18n.json + output_directory: lib/src/l10n + output_file_name: strings.g.dart diff --git a/apps/packages/core_localization/test/localization_test.dart b/apps/packages/core_localization/test/localization_test.dart new file mode 100644 index 00000000..0d7a0e1e --- /dev/null +++ b/apps/packages/core_localization/test/localization_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:localization/localization.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} diff --git a/apps/packages/data_connect/lib/krow_data_connect.dart b/apps/packages/data_connect/lib/krow_data_connect.dart new file mode 100644 index 00000000..0f96dc17 --- /dev/null +++ b/apps/packages/data_connect/lib/krow_data_connect.dart @@ -0,0 +1,17 @@ +/// The Data Connect layer. +/// +/// This package provides mock implementations of domain repository interfaces +/// for development and testing purposes. +/// +/// TODO: These mocks currently do not implement any specific interfaces. +/// They will implement interfaces defined in feature packages once those are created. + +export 'src/mocks/auth_repository_mock.dart'; +export 'src/mocks/staff_repository_mock.dart'; +export 'src/mocks/business_repository_mock.dart'; +export 'src/mocks/event_repository_mock.dart'; +export 'src/mocks/skill_repository_mock.dart'; +export 'src/mocks/financial_repository_mock.dart'; +export 'src/mocks/rating_repository_mock.dart'; +export 'src/mocks/support_repository_mock.dart'; +export 'src/data_connect_module.dart'; diff --git a/apps/packages/data_connect/lib/src/data_connect_module.dart b/apps/packages/data_connect/lib/src/data_connect_module.dart new file mode 100644 index 00000000..5d201629 --- /dev/null +++ b/apps/packages/data_connect/lib/src/data_connect_module.dart @@ -0,0 +1,11 @@ +import 'package:flutter_modular/flutter_modular.dart'; +import 'mocks/auth_repository_mock.dart'; + +/// A module that provides Data Connect dependencies, including mocks. +class DataConnectModule extends Module { + @override + void exportedBinds(Injector i) { + // Make the AuthRepositoryMock available to any module that imports this one. + i.addLazySingleton(AuthRepositoryMock.new); + } +} diff --git a/apps/packages/data_connect/lib/src/mocks/auth_repository_mock.dart b/apps/packages/data_connect/lib/src/mocks/auth_repository_mock.dart new file mode 100644 index 00000000..1b571cb3 --- /dev/null +++ b/apps/packages/data_connect/lib/src/mocks/auth_repository_mock.dart @@ -0,0 +1,48 @@ +import 'package:krow_domain/krow_domain.dart'; + +// TODO: Implement AuthRepositoryInterface once defined in a feature package. +class AuthRepositoryMock { + Stream get currentUser => Stream.value( + const User(id: 'mock_user_1', email: 'test@krow.com', role: 'staff'), + ); + + Future signInWithPhone(String phoneNumber) async { + await Future.delayed(const Duration(milliseconds: 500)); + return 'mock_verification_id'; + } + + Future verifyOtp(String verificationId, String smsCode) async { + await Future.delayed(const Duration(milliseconds: 500)); + return const User(id: 'mock_user_1', email: 'test@krow.com', role: 'staff'); + } + + Future signOut() async { + await Future.delayed(const Duration(milliseconds: 200)); + } + + /// Signs in a user with email and password (Mock). + Future signInWithEmail(String email, String password) async { + await Future.delayed(const Duration(milliseconds: 500)); + return User(id: 'mock_client_1', email: email, role: 'client_admin'); + } + + /// Registers a new user with email and password (Mock). + Future signUpWithEmail( + String email, + String password, + String companyName, + ) async { + await Future.delayed(const Duration(milliseconds: 500)); + return User(id: 'mock_client_new', email: email, role: 'client_admin'); + } + + /// Authenticates using a social provider (Mock). + Future signInWithSocial(String provider) async { + await Future.delayed(const Duration(milliseconds: 500)); + return const User( + id: 'mock_social_user', + email: 'social@example.com', + role: 'client_admin', + ); + } +} diff --git a/apps/packages/data_connect/lib/src/mocks/business_repository_mock.dart b/apps/packages/data_connect/lib/src/mocks/business_repository_mock.dart new file mode 100644 index 00000000..40d2ca9d --- /dev/null +++ b/apps/packages/data_connect/lib/src/mocks/business_repository_mock.dart @@ -0,0 +1,28 @@ +import 'package:krow_domain/krow_domain.dart'; + +// TODO: Implement BusinessRepositoryInterface once defined in a feature package. +class BusinessRepositoryMock { + Future getBusiness(String id) async { + await Future.delayed(const Duration(milliseconds: 300)); + return const Business( + id: 'biz_1', + name: 'Acme Events Ltd', + registrationNumber: 'REG123456', + status: BusinessStatus.active, + avatar: 'https://via.placeholder.com/150', + ); + } + + Future> getHubs(String businessId) async { + await Future.delayed(const Duration(milliseconds: 300)); + return [ + const Hub( + id: 'hub_1', + businessId: 'biz_1', + name: 'London HQ', + address: '123 Oxford Street, London', + status: HubStatus.active, + ), + ]; + } +} \ No newline at end of file diff --git a/apps/packages/data_connect/lib/src/mocks/event_repository_mock.dart b/apps/packages/data_connect/lib/src/mocks/event_repository_mock.dart new file mode 100644 index 00000000..44159611 --- /dev/null +++ b/apps/packages/data_connect/lib/src/mocks/event_repository_mock.dart @@ -0,0 +1,58 @@ +import 'package:krow_domain/krow_domain.dart'; + +// TODO: Implement EventRepositoryInterface once defined in a feature package. +class EventRepositoryMock { + Future applyForPosition(String positionId, String staffId) async { + await Future.delayed(const Duration(milliseconds: 600)); + return Assignment( + id: 'assign_1', + positionId: positionId, + staffId: staffId, + status: AssignmentStatus.assigned, + ); + } + + Future getEvent(String id) async { + await Future.delayed(const Duration(milliseconds: 300)); + return _mockEvent; + } + + Future> getEventShifts(String eventId) async { + await Future.delayed(const Duration(milliseconds: 300)); + return [ + const EventShift( + id: 'shift_1', + eventId: 'event_1', + name: 'Morning Setup', + address: 'Hyde Park, London', + ), + ]; + } + + Future> getStaffAssignments(String staffId) async { + await Future.delayed(const Duration(milliseconds: 500)); + return [ + const Assignment( + id: 'assign_1', + positionId: 'pos_1', + staffId: 'staff_1', + status: AssignmentStatus.confirmed, + ), + ]; + } + + Future> getUpcomingEvents() async { + await Future.delayed(const Duration(milliseconds: 800)); + return [_mockEvent]; + } + + static final _mockEvent = Event( + id: 'event_1', + businessId: 'biz_1', + hubId: 'hub_1', + name: 'Summer Festival 2026', + date: DateTime.now().add(const Duration(days: 10)), + status: EventStatus.active, + contractType: 'freelance', + ); +} \ No newline at end of file diff --git a/apps/packages/data_connect/lib/src/mocks/financial_repository_mock.dart b/apps/packages/data_connect/lib/src/mocks/financial_repository_mock.dart new file mode 100644 index 00000000..050cf7e5 --- /dev/null +++ b/apps/packages/data_connect/lib/src/mocks/financial_repository_mock.dart @@ -0,0 +1,33 @@ +import 'package:krow_domain/krow_domain.dart'; + +// TODO: Implement FinancialRepositoryInterface once defined in a feature package. +class FinancialRepositoryMock { + Future> getInvoices(String businessId) async { + await Future.delayed(const Duration(milliseconds: 500)); + return [ + const Invoice( + id: 'inv_1', + eventId: 'event_1', + businessId: 'biz_1', + status: InvoiceStatus.paid, + totalAmount: 1500.0, + workAmount: 1400.0, + addonsAmount: 100.0, + ), + ]; + } + + Future> getStaffPayments(String staffId) async { + await Future.delayed(const Duration(milliseconds: 500)); + return [ + StaffPayment( + id: 'pay_1', + staffId: staffId, + assignmentId: 'assign_1', + amount: 120.0, + status: PaymentStatus.paid, + paidAt: DateTime.now().subtract(const Duration(days: 2)), + ), + ]; + } +} \ No newline at end of file diff --git a/apps/packages/data_connect/lib/src/mocks/rating_repository_mock.dart b/apps/packages/data_connect/lib/src/mocks/rating_repository_mock.dart new file mode 100644 index 00000000..eedb0efb --- /dev/null +++ b/apps/packages/data_connect/lib/src/mocks/rating_repository_mock.dart @@ -0,0 +1,22 @@ +import 'package:krow_domain/krow_domain.dart'; + +// TODO: Implement RatingRepositoryInterface once defined in a feature package. +class RatingRepositoryMock { + Future> getStaffRatings(String staffId) async { + await Future.delayed(const Duration(milliseconds: 400)); + return [ + const StaffRating( + id: 'rate_1', + staffId: 'staff_1', + eventId: 'event_1', + businessId: 'biz_1', + rating: 5, + comment: 'Great work!', + ), + ]; + } + + Future submitRating(StaffRating rating) async { + await Future.delayed(const Duration(milliseconds: 500)); + } +} \ No newline at end of file diff --git a/apps/packages/data_connect/lib/src/mocks/skill_repository_mock.dart b/apps/packages/data_connect/lib/src/mocks/skill_repository_mock.dart new file mode 100644 index 00000000..a808733c --- /dev/null +++ b/apps/packages/data_connect/lib/src/mocks/skill_repository_mock.dart @@ -0,0 +1,40 @@ +import 'package:krow_domain/krow_domain.dart'; + +// TODO: Implement SkillRepositoryInterface once defined in a feature package. +class SkillRepositoryMock { + Future addStaffSkill(StaffSkill skill) async { + await Future.delayed(const Duration(milliseconds: 500)); + } + + Future> getAllSkills() async { + await Future.delayed(const Duration(milliseconds: 300)); + return [ + const Skill( + id: 'skill_1', + categoryId: 'cat_1', + name: 'Bartender', + basePrice: 15.0, + ), + const Skill( + id: 'skill_2', + categoryId: 'cat_2', + name: 'Security Guard', + basePrice: 18.0, + ), + ]; + } + + Future> getStaffSkills(String staffId) async { + await Future.delayed(const Duration(milliseconds: 400)); + return [ + const StaffSkill( + id: 'staff_skill_1', + staffId: 'staff_1', + skillId: 'skill_1', + level: SkillLevel.skilled, + experienceYears: 3, + status: StaffSkillStatus.verified, + ), + ]; + } +} \ No newline at end of file diff --git a/apps/packages/data_connect/lib/src/mocks/staff_repository_mock.dart b/apps/packages/data_connect/lib/src/mocks/staff_repository_mock.dart new file mode 100644 index 00000000..b40479ee --- /dev/null +++ b/apps/packages/data_connect/lib/src/mocks/staff_repository_mock.dart @@ -0,0 +1,39 @@ +import 'package:krow_domain/krow_domain.dart'; + +// TODO: Implement StaffRepositoryInterface once defined in a feature package. +class StaffRepositoryMock { + Future createStaffProfile(Staff staff) async { + await Future.delayed(const Duration(milliseconds: 500)); + return staff; + } + + Future> getMemberships(String userId) async { + await Future.delayed(const Duration(milliseconds: 300)); + return [ + Membership( + id: 'mem_1', + userId: userId, + memberableId: 'biz_1', + memberableType: 'business', + role: 'staff', + ), + ]; + } + + Future getStaffProfile(String userId) async { + await Future.delayed(const Duration(milliseconds: 400)); + return Staff( + id: 'staff_1', + authProviderId: userId, + name: 'John Doe', + email: 'john@krow.com', + status: StaffStatus.active, + avatar: 'https://i.pravatar.cc/300', + ); + } + + Future updateStaffProfile(Staff staff) async { + await Future.delayed(const Duration(milliseconds: 500)); + return staff; + } +} \ No newline at end of file diff --git a/apps/packages/data_connect/lib/src/mocks/support_repository_mock.dart b/apps/packages/data_connect/lib/src/mocks/support_repository_mock.dart new file mode 100644 index 00000000..346eb8d1 --- /dev/null +++ b/apps/packages/data_connect/lib/src/mocks/support_repository_mock.dart @@ -0,0 +1,25 @@ +import 'package:krow_domain/krow_domain.dart'; + +// TODO: Implement SupportRepositoryInterface once defined in a feature package. +class SupportRepositoryMock { + Future> getTags() async { + await Future.delayed(const Duration(milliseconds: 200)); + return [ + const Tag(id: 'tag_1', label: 'Urgent'), + const Tag(id: 'tag_2', label: 'VIP Event'), + ]; + } + + Future> getWorkingAreas() async { + await Future.delayed(const Duration(milliseconds: 200)); + return [ + const WorkingArea( + id: 'area_1', + name: 'Central London', + centerLat: 51.5074, + centerLng: -0.1278, + radiusKm: 10.0, + ), + ]; + } +} \ No newline at end of file diff --git a/apps/packages/data_connect/pubspec.yaml b/apps/packages/data_connect/pubspec.yaml new file mode 100644 index 00000000..45610427 --- /dev/null +++ b/apps/packages/data_connect/pubspec.yaml @@ -0,0 +1,16 @@ +name: krow_data_connect +description: Firebase Data Connect access layer. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + krow_domain: + path: ../domain + flutter_modular: ^6.3.0 diff --git a/apps/packages/design_system/.gitignore b/apps/packages/design_system/.gitignore new file mode 100644 index 00000000..dd5eb989 --- /dev/null +++ b/apps/packages/design_system/.gitignore @@ -0,0 +1,31 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins-dependencies +/build/ +/coverage/ diff --git a/apps/packages/design_system/.metadata b/apps/packages/design_system/.metadata new file mode 100644 index 00000000..685c30f1 --- /dev/null +++ b/apps/packages/design_system/.metadata @@ -0,0 +1,10 @@ +# 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: "3b62efc2a3da49882f43c372e0bc53daef7295a6" + channel: "stable" + +project_type: package diff --git a/apps/packages/design_system/CHANGELOG.md b/apps/packages/design_system/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/apps/packages/design_system/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/apps/packages/design_system/LICENSE b/apps/packages/design_system/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/apps/packages/design_system/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/apps/packages/design_system/README.md b/apps/packages/design_system/README.md new file mode 100644 index 00000000..4a260d8d --- /dev/null +++ b/apps/packages/design_system/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/apps/packages/design_system/analysis_options.yaml b/apps/packages/design_system/analysis_options.yaml new file mode 100644 index 00000000..ec9d7265 --- /dev/null +++ b/apps/packages/design_system/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analytics_options.yaml diff --git a/apps/packages/design_system/assets/logo-blue.png b/apps/packages/design_system/assets/logo-blue.png new file mode 100644 index 00000000..5d757231 Binary files /dev/null and b/apps/packages/design_system/assets/logo-blue.png differ diff --git a/apps/packages/design_system/assets/logo-yellow.png b/apps/packages/design_system/assets/logo-yellow.png new file mode 100644 index 00000000..ef04350b Binary files /dev/null and b/apps/packages/design_system/assets/logo-yellow.png differ diff --git a/apps/packages/design_system/lib/design_system.dart b/apps/packages/design_system/lib/design_system.dart new file mode 100644 index 00000000..5e2638b2 --- /dev/null +++ b/apps/packages/design_system/lib/design_system.dart @@ -0,0 +1,12 @@ +export 'src/ui_colors.dart'; +export 'src/ui_typography.dart'; +export 'src/ui_constants.dart'; +export 'src/ui_theme.dart'; +export 'src/ui_icons.dart'; +export 'src/ui_images_assets.dart'; +export 'src/widgets/ui_app_bar.dart'; +export 'src/widgets/ui_text_field.dart'; +export 'src/widgets/ui_step_indicator.dart'; +export 'src/widgets/ui_icon_button.dart'; +export 'src/widgets/ui_button.dart'; +export 'src/widgets/ui_chip.dart'; diff --git a/apps/packages/design_system/lib/src/ui_colors.dart b/apps/packages/design_system/lib/src/ui_colors.dart new file mode 100644 index 00000000..35c455cc --- /dev/null +++ b/apps/packages/design_system/lib/src/ui_colors.dart @@ -0,0 +1,322 @@ +import 'package:flutter/material.dart'; + +/// Static definitions of color palettes and semantic colors for the Staff Design System. +/// Values are defined in design_tokens_react.md. +class UiColors { + UiColors._(); + + // --------------------------------------------------------------------------- + // 1. Base Tokens + // --------------------------------------------------------------------------- + + /// Background color (#FAFBFC) + static const Color background = Color(0xFFFAFBFC); + + /// Foreground color (#121826) + static const Color foreground = Color(0xFF121826); + + /// Primary brand color blue (#0A39DF) + static const Color primary = Color(0xFF0A39DF); + + /// Foreground color on primary background (#F7FAFC) + static const Color primaryForeground = Color(0xFFF7FAFC); + + /// Inverse primary color (#9FABF1) + static const Color primaryInverse = Color(0xFF9FABF1); + + /// Secondary background color (#F1F3F5) + static const Color secondary = Color(0xFFF1F3F5); + + /// Foreground color on secondary background (#121826) + static const Color secondaryForeground = Color(0xFF121826); + + /// Muted background color (#F1F3F5) + static const Color muted = Color(0xFFF1F3F5); + + /// Muted foreground color (#6A7382) + static const Color mutedForeground = Color(0xFF6A7382); + + /// Accent yellow color (#F9E547) + static const Color accent = Color(0xFFF9E547); + + /// Foreground color on accent background (#4C460D) + static const Color accentForeground = Color(0xFF4C460D); + + /// Destructive red color (#F04444) + static const Color destructive = Color(0xFFF04444); + + /// Foreground color on destructive background (#FAFAFA) + static const Color destructiveForeground = Color(0xFFFAFAFA); + + /// Default border color (#D1D5DB) + static const Color border = Color(0xFFD1D5DB); + + /// Default input border color (#E7EAEE) + static const Color input = Color(0xFFF5F6F8); + + /// Focus ring color (#0A39DF) + static const Color ring = Color(0xFF0A39DF); + + // --------------------------------------------------------------------------- + // 2. Semantic Mappings + // --------------------------------------------------------------------------- + + // --- Background Colors --- + + /// Primary background (#FAFBFC) + static const Color bgPrimary = background; + + /// Secondary background (#F1F3F5) + static const Color bgSecondary = secondary; + + /// Tertiary background (#EDF0F2) + static const Color bgThird = Color(0xFFEDF0F2); + + /// Popup background (#FFFFFF) + static const Color bgPopup = Color(0xFFFFFFFF); + + /// Highlighted background (#FEF9C3) + static const Color bgHighlight = Color(0xFFFEF9C3); + + /// Menu background (#F8FAFC) + static const Color bgMenu = Color(0xFFF8FAFC); + + /// Banner background (#FFFFFF) + static const Color bgBanner = Color(0xFFFFFFFF); + + /// Overlay background (#000000 with 50% opacity) + static const Color bgOverlay = Color(0x80000000); + + /// Toast background (#121826) + static const Color toastBg = Color(0xFF121826); + + /// Input field background (#E3E6E9) + static const Color bgInputField = input; + + /// Footer banner background (#F1F5F9) + static const Color bgFooterBanner = Color(0xFFF1F5F9); + + // --- Text Colors --- + + /// Primary text (#121826) + static const Color textPrimary = foreground; + + /// Secondary text (#6A7382) + static const Color textSecondary = mutedForeground; + + /// Inactive text (#9CA3AF) + static const Color textInactive = Color(0xFF9CA3AF); + + /// Placeholder text (#9CA3AF) + static const Color textPlaceholder = Color(0xFF9CA3AF); + + /// Description text (#6A7382) + static const Color textDescription = mutedForeground; + + /// Success text (#10B981) + static const Color textSuccess = Color(0xFF10B981); + + /// Error text (#F04444) + static const Color textError = destructive; + + /// Deep error text for containers (#450A0A) + static const Color textErrorContainer = Color(0xFF450A0A); + + /// Warning text (#D97706) + static const Color textWarning = Color(0xFFD97706); + + /// Link text (#0A39DF) + static const Color textLink = primary; + + /// Filter text (#4B5563) + static const Color textFilter = Color(0xFF4B5563); + + // --- Icon Colors --- + + /// Primary icon (#121826) + static const Color iconPrimary = foreground; + + /// Secondary icon (#6A7382) + static const Color iconSecondary = mutedForeground; + + /// Tertiary icon (#9CA3AF) + static const Color iconThird = Color(0xFF9CA3AF); + + /// Inactive icon (#D1D5DB) + static const Color iconInactive = Color(0xFFD1D5DB); + + /// Active icon (#0A39DF) + static const Color iconActive = primary; + + /// Success icon (#10B981) + static const Color iconSuccess = Color(0xFF10B981); + + /// Error icon (#F04444) + static const Color iconError = destructive; + + // --- Loader Colors --- + + /// Active loader (#0A39DF) + static const Color loaderActive = primary; + + /// Inactive loader (#E2E8F0) + static const Color loaderInactive = Color(0xFFE2E8F0); + + // --- Pin Input Colors --- + + /// Unfilled pin (#E2E8F0) + static const Color pinUnfilled = Color(0xFFE2E8F0); + + /// Active pin (#0A39DF) + static const Color pinActive = primary; + + /// Inactive pin (#94A3B8) + static const Color pinInactive = Color(0xFF94A3B8); + + // --- Separator Colors --- + + /// Primary separator (#E3E6E9) + static const Color separatorPrimary = border; + + /// Secondary separator (#F1F5F9) + static const Color separatorSecondary = Color(0xFFF1F5F9); + + /// Special separator (#F9E547) + static const Color separatorSpecial = accent; + + // --- Tag Colors --- + + /// Default tag background (#F1F5F9) + static const Color tagValue = Color(0xFFF1F5F9); + + /// Pending state tag background (#FEF3C7) + static const Color tagPending = Color(0xFFFEF3C7); + + /// In-progress state tag background (#DBEAFE) + static const Color tagInProgress = Color(0xFFDBEAFE); + + /// Error state tag background (#FEE2E2) + static const Color tagError = Color(0xFFFEE2E2); + + /// Active state tag background (#DCFCE7) + static const Color tagActive = Color(0xFFDCFCE7); + + /// Frozen state tag background (#F3F4F6) + static const Color tagFreeze = Color(0xFFF3F4F6); + + /// Success state tag background (#DCFCE7) + static const Color tagSuccess = Color(0xFFDCFCE7); + + /// Refunded state tag background (#E0E7FF) + static const Color tagRefunded = Color(0xFFE0E7FF); + + // --- Border Colors --- + + /// Static border (#D1D5DB) + static const Color borderStill = border; + + /// Primary border (#D1D5DB) + static const Color borderPrimary = border; + + /// Error border (#F04444) + static const Color borderError = destructive; + + /// Focus border (#0A39DF) + static const Color borderFocus = ring; + + /// Inactive border (#F1F5F9) + static const Color borderInactive = Color(0xFFF1F5F9); + + // --- Button Colors --- + + /// Primary button default (#0A39DF) + static const Color buttonPrimaryStill = primary; + + /// Primary button hover (#082EB2) + static const Color buttonPrimaryHover = Color(0xFF082EB2); + + /// Primary button inactive (#F1F3F5) + static const Color buttonPrimaryInactive = secondary; + + /// Secondary button default (#F1F3F5) + static const Color buttonSecondaryStill = secondary; + + /// Secondary button hover (#E2E8F0) + static const Color buttonSecondaryHover = Color(0xFFE2E8F0); + + /// Secondary button inactive (#F3F4F6) + static const Color buttonSecondaryInactive = Color(0xFFF3F4F6); + + /// Button inactive state (#94A3B8) + static const Color buttonInactive = Color(0xFF94A3B8); + + /// Pin button background (#F8FAFC) + static const Color pinButtonBackground = Color(0xFFF8FAFC); + + // --- Switch Colors --- + + /// Switch active state (#10B981) + static const Color switchActive = Color(0xFF10B981); + + /// Switch inactive state (#CBD5E1) + static const Color switchInactive = Color(0xFFCBD5E1); + + /// Switch dot inactive state (#FFFFFF) + static const Color dotInactive = Color(0xFFFFFFFF); + + // --- Basic Colors --- + + /// Standard white (#FFFFFF) + static const Color white = Color(0xFFFFFFFF); + + /// Standard black (#000000) + static const Color black = Color(0xFF000000); + + /// Transparent color (0x00000000) + static const Color transparent = Color(0x00000000); + + /// Card background (#FFFFFF) + static const Color cardViewBackground = Color(0xFFFFFFFF); + + // --- Shadows --- + + /// Primary popup shadow (#000000 with 10% opacity) + static const Color popupShadow = Color(0x1A000000); + + // --------------------------------------------------------------------------- + // 3. ColorScheme + // --------------------------------------------------------------------------- + + /// Generates a ColorScheme based on the tokens. + static ColorScheme get colorScheme => const ColorScheme( + brightness: Brightness.light, + primary: primary, + onPrimary: primaryForeground, + primaryContainer: tagRefunded, + onPrimaryContainer: primary, + secondary: secondary, + onSecondary: secondaryForeground, + secondaryContainer: muted, + onSecondaryContainer: secondaryForeground, + tertiary: accent, + onTertiary: accentForeground, + tertiaryContainer: bgHighlight, + onTertiaryContainer: accentForeground, + error: destructive, + onError: destructiveForeground, + errorContainer: tagError, + onErrorContainer: textErrorContainer, + surface: background, + onSurface: foreground, + surfaceContainerHighest: muted, + onSurfaceVariant: mutedForeground, + outline: border, + outlineVariant: separatorSecondary, + shadow: black, + scrim: black, + inverseSurface: foreground, + onInverseSurface: background, + inversePrimary: primaryInverse, + surfaceTint: primary, + ); +} diff --git a/apps/packages/design_system/lib/src/ui_constants.dart b/apps/packages/design_system/lib/src/ui_constants.dart new file mode 100644 index 00000000..4bce22fa --- /dev/null +++ b/apps/packages/design_system/lib/src/ui_constants.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +/// Design system constants for spacing, radii, and other layout properties. +class UiConstants { + UiConstants._(); + + // --- Border Radii --- + + /// Base radius: 12px + static const double radiusBase = 12.0; + static final BorderRadius radiusLg = BorderRadius.circular(radiusBase); + + /// Medium radius: 6px + static const double radiusMdValue = 6.0; + static final BorderRadius radiusMd = BorderRadius.circular(radiusMdValue); + + /// Small radius: 4px + static final BorderRadius radiusSm = BorderRadius.circular(4.0); + + /// Extra small radius: 2px + static final BorderRadius radiusXs = BorderRadius.circular(2.0); + + /// Large/Full radius + static final BorderRadius radiusFull = BorderRadius.circular(999.0); + + // --- Spacing --- + + static const double space0 = 0.0; + static const double space1 = 4.0; + static const double space2 = 8.0; + static const double space3 = 12.0; + static const double space4 = 16.0; + static const double space5 = 20.0; + static const double space6 = 24.0; + static const double space8 = 32.0; + static const double space10 = 40.0; + static const double space12 = 48.0; +} diff --git a/apps/packages/design_system/lib/src/ui_icons.dart b/apps/packages/design_system/lib/src/ui_icons.dart new file mode 100644 index 00000000..9b3252cf --- /dev/null +++ b/apps/packages/design_system/lib/src/ui_icons.dart @@ -0,0 +1,174 @@ +import 'package:flutter/widgets.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:lucide_icons/lucide_icons.dart'; + +/// The primary icon library used by the design system. +/// This allows for easier swapping of icon libraries in the future. +typedef _IconLib = LucideIcons; + +/// The secondary icon library used by the design system. +/// This allows for easier swapping of icon libraries in the future. +typedef _IconLib2 = FontAwesomeIcons; + +/// Static definitions of icons for the UI design system. +/// This class wraps the primary icon library to provide a consistent interface. +/// +/// example: +/// ```dart +/// Icon(UiIcons.home) +/// ``` +class UiIcons { + UiIcons._(); + + // --- Navigation --- + + /// Home icon + static const IconData home = _IconLib.home; + + /// Calendar icon for shifts or schedules + static const IconData calendar = _IconLib.calendar; + + /// Briefcase icon for jobs + static const IconData briefcase = _IconLib.briefcase; + + /// User icon for profile + static const IconData user = _IconLib.user; + + /// Settings icon + static const IconData settings = _IconLib.settings; + + // --- Actions --- + + /// Search icon + static const IconData search = _IconLib.search; + + /// Filter icon + static const IconData filter = _IconLib.filter; + + /// Plus/Add icon + static const IconData add = _IconLib.plus; + + /// Edit icon + static const IconData edit = _IconLib.edit2; + + /// Delete/Trash icon + static const IconData delete = _IconLib.trash2; + + /// Checkmark icon + static const IconData check = _IconLib.check; + + /// X/Cancel icon + static const IconData close = _IconLib.x; + + /// Arrow right icon + static const IconData arrowRight = _IconLib.arrowRight; + + /// Arrow left icon + static const IconData arrowLeft = _IconLib.arrowLeft; + + /// Swap/Transfer icon + static const IconData swap = _IconLib.arrowLeftRight; + + /// Chevron right icon + static const IconData chevronRight = _IconLib.chevronRight; + + /// Chevron left icon + static const IconData chevronLeft = _IconLib.chevronLeft; + + // --- Status & Feedback --- + + /// Info icon + static const IconData info = _IconLib.info; + + /// Help/Circle icon + static const IconData help = _IconLib.helpCircle; + + /// Alert/Triangle icon for warnings + static const IconData warning = _IconLib.alertTriangle; + + /// Alert/Circle icon for errors + static const IconData error = _IconLib.alertCircle; + + /// Success/Check circle icon + static const IconData success = _IconLib.checkCircle2; + + // --- Miscellaneous --- + + /// Clock icon + static const IconData clock = _IconLib.clock; + + /// Log in icon + static const IconData logIn = _IconLib.logIn; + + /// Break icon (Coffee) + static const IconData breakIcon = _IconLib.coffee; + + /// Map pin icon for locations + static const IconData mapPin = _IconLib.mapPin; + + /// Dollar sign icon for payments/earnings + static const IconData dollar = _IconLib.dollarSign; + + /// Wallet icon + static const IconData wallet = _IconLib.wallet; + + /// Credit card icon + static const IconData creditCard = _IconLib.creditCard; + + /// Bell icon for notifications + static const IconData bell = _IconLib.bell; + + /// Log out icon + static const IconData logOut = _IconLib.logOut; + + /// File/Document icon + static const IconData file = _IconLib.fileText; + + /// Lock icon + static const IconData lock = _IconLib.lock; + + /// Shield check icon for compliance/security + static const IconData shield = _IconLib.shieldCheck; + + /// Sparkles icon for features or AI + static const IconData sparkles = _IconLib.sparkles; + + /// Star icon for ratings + static const IconData star = _IconLib.star; + + /// Camera icon for photo upload + static const IconData camera = _IconLib.camera; + + /// Mail icon + static const IconData mail = _IconLib.mail; + + /// Eye icon for visibility + static const IconData eye = _IconLib.eye; + + /// Eye off icon for hidden visibility + static const IconData eyeOff = _IconLib.eyeOff; + + /// Building icon for companies + static const IconData building = _IconLib.building2; + + /// Zap icon for rapid actions + static const IconData zap = _IconLib.zap; + + /// Grip vertical icon for reordering + static const IconData gripVertical = _IconLib.gripVertical; + + /// Trending down icon for insights + static const IconData trendingDown = _IconLib.trendingDown; + + /// Target icon for metrics + static const IconData target = _IconLib.target; + + /// Rotate CCW icon for reordering + static const IconData rotateCcw = _IconLib.rotateCcw; + + /// Apple icon + static const IconData apple = _IconLib2.apple; + + /// Google icon + static const IconData google = _IconLib2.google; +} diff --git a/apps/packages/design_system/lib/src/ui_images_assets.dart b/apps/packages/design_system/lib/src/ui_images_assets.dart new file mode 100644 index 00000000..14b32f0d --- /dev/null +++ b/apps/packages/design_system/lib/src/ui_images_assets.dart @@ -0,0 +1,14 @@ +/// Static definitions of image asset paths for the Design System. +/// +/// This class provides a centralized way to access image assets +/// stored within the `design_system` package. +class UiImageAssets { + UiImageAssets._(); + + /// The path to the yellow version of the logo image. + static const String logoYellow = + 'packages/design_system/assets/logo-yellow.png'; + + /// The path to the blue version of the logo image. + static const String logoBlue = 'packages/design_system/assets/logo-blue.png'; +} diff --git a/apps/packages/design_system/lib/src/ui_theme.dart b/apps/packages/design_system/lib/src/ui_theme.dart new file mode 100644 index 00000000..47252d81 --- /dev/null +++ b/apps/packages/design_system/lib/src/ui_theme.dart @@ -0,0 +1,359 @@ +import 'package:flutter/material.dart'; +import 'ui_colors.dart'; +import 'ui_typography.dart'; +import 'ui_constants.dart'; + +/// The main entry point for the Staff Design System theme. +/// Assembles colors, typography, and constants into a comprehensive Material 3 theme. +/// +/// Adheres to the tokens defined in design_tokens_react.md. +class UiTheme { + UiTheme._(); + + /// Returns the light theme for the Staff application. + static ThemeData get light { + final colorScheme = UiColors.colorScheme; + final textTheme = UiTypography.textTheme; + + return ThemeData( + useMaterial3: true, + colorScheme: colorScheme, + scaffoldBackgroundColor: UiColors.background, + primaryColor: UiColors.primary, + canvasColor: UiColors.background, + + // Typography + textTheme: textTheme, + + // Icon Theme + iconTheme: const IconThemeData(color: UiColors.iconPrimary, size: 24), + + // Text Selection Theme + textSelectionTheme: const TextSelectionThemeData( + cursorColor: UiColors.primary, + selectionColor: UiColors.primaryInverse, + selectionHandleColor: UiColors.primary, + ), + + // Divider Theme + dividerTheme: const DividerThemeData( + color: UiColors.separatorPrimary, + space: 1, + thickness: 1, + ), + + // Card Theme + cardTheme: CardThemeData( + color: UiColors.white, + elevation: 2, + shadowColor: UiColors.popupShadow, + shape: RoundedRectangleBorder( + borderRadius: UiConstants.radiusLg, + side: const BorderSide(color: UiColors.borderStill), + ), + margin: EdgeInsets.zero, + ), + + // Elevated Button Theme (Primary) + elevatedButtonTheme: ElevatedButtonThemeData( + style: + ElevatedButton.styleFrom( + elevation: 0, + backgroundColor: UiColors.buttonPrimaryStill, + foregroundColor: UiColors.primaryForeground, + disabledBackgroundColor: UiColors.buttonPrimaryInactive, + textStyle: UiTypography.buttonXL, + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space6, + vertical: UiConstants.space3, + ), + minimumSize: const Size(double.infinity, 54), + maximumSize: const Size(double.infinity, 54), + ).copyWith( + side: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return const BorderSide( + color: UiColors.borderPrimary, + width: 0.5, + ); + } + return null; + }), + overlayColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.hovered)) + return UiColors.buttonPrimaryHover; + return null; + }), + ), + ), + + // Text Button Theme + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: UiColors.textPrimary, + disabledForegroundColor: UiColors.textInactive, + textStyle: UiTypography.buttonXL, + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space2, + ), + minimumSize: const Size(double.infinity, 52), + maximumSize: const Size(double.infinity, 52), + ), + ), + + // Outlined Button Theme (Secondary) + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + elevation: 0, + backgroundColor: UiColors.buttonSecondaryStill, + foregroundColor: UiColors.primary, + side: const BorderSide(color: UiColors.borderFocus, width: 0.5), + textStyle: UiTypography.buttonXL, + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space3, + ), + minimumSize: const Size(double.infinity, 52), + maximumSize: const Size(double.infinity, 52), + ), + ), + + // Icon Button Theme + iconButtonTheme: IconButtonThemeData( + style: IconButton.styleFrom( + foregroundColor: UiColors.iconPrimary, + disabledForegroundColor: UiColors.iconInactive, + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusFull), + ), + ), + + // Floating Action Button Theme + floatingActionButtonTheme: const FloatingActionButtonThemeData( + backgroundColor: UiColors.primary, + foregroundColor: UiColors.primaryForeground, + elevation: 4, + shape: CircleBorder(), + ), + + // Tab Bar Theme + tabBarTheme: TabBarThemeData( + labelColor: UiColors.primary, + unselectedLabelColor: UiColors.textSecondary, + labelStyle: UiTypography.buttonM, + unselectedLabelStyle: UiTypography.buttonM, + indicatorSize: TabBarIndicatorSize.label, + indicator: const UnderlineTabIndicator( + borderSide: BorderSide(color: UiColors.primary, width: 2), + ), + ), + + // Input Theme + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: UiColors.bgInputField, + hintStyle: UiTypography.body2r.textPlaceholder, + labelStyle: UiTypography.body4r.textPrimary, + errorStyle: UiTypography.footnote1r.textError, + contentPadding: const EdgeInsets.symmetric( + horizontal: UiConstants.space3, + vertical: UiConstants.space3, + ), + border: OutlineInputBorder( + borderRadius: UiConstants.radiusMd, + borderSide: const BorderSide(color: UiColors.borderStill), + ), + enabledBorder: OutlineInputBorder( + borderRadius: UiConstants.radiusMd, + borderSide: const BorderSide(color: UiColors.borderStill), + ), + focusedBorder: OutlineInputBorder( + borderRadius: UiConstants.radiusMd, + borderSide: const BorderSide( + color: UiColors.borderFocus, + width: 0.75, + ), + ), + errorBorder: OutlineInputBorder( + borderRadius: UiConstants.radiusMd, + borderSide: const BorderSide(color: UiColors.textError), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: UiConstants.radiusMd, + borderSide: const BorderSide(color: UiColors.textError, width: 1), + ), + ), + + // List Tile Theme + listTileTheme: ListTileThemeData( + textColor: UiColors.textPrimary, + iconColor: UiColors.iconPrimary, + titleTextStyle: UiTypography.body1m, + subtitleTextStyle: UiTypography.body2r.textSecondary, + contentPadding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + ), + tileColor: UiColors.transparent, + ), + + // Badge Theme + badgeTheme: BadgeThemeData( + backgroundColor: UiColors.primary, + textColor: UiColors.primaryForeground, + textStyle: UiTypography.footnote2m, + padding: const EdgeInsets.symmetric(horizontal: 4), + ), + + // App Bar Theme + appBarTheme: AppBarTheme( + backgroundColor: UiColors.background, + elevation: 0, + titleTextStyle: UiTypography.headline5m.textPrimary, + iconTheme: const IconThemeData(color: UiColors.iconThird, size: 20), + surfaceTintColor: UiColors.transparent, + ), + + // Dialog Theme + dialogTheme: DialogThemeData( + backgroundColor: UiColors.bgPopup, + elevation: 8, + shadowColor: UiColors.popupShadow, + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg), + titleTextStyle: UiTypography.headline2r.textPrimary, + contentTextStyle: UiTypography.body2r.textDescription, + ), + + // Bottom Navigation Bar Theme + bottomNavigationBarTheme: BottomNavigationBarThemeData( + backgroundColor: UiColors.white, + selectedItemColor: UiColors.primary, + unselectedItemColor: UiColors.textInactive, + selectedLabelStyle: UiTypography.footnote2m, + unselectedLabelStyle: UiTypography.footnote2r, + type: BottomNavigationBarType.fixed, + elevation: 8, + ), + + // Navigation Bar Theme (Modern M3 Bottom Nav) + navigationBarTheme: NavigationBarThemeData( + backgroundColor: UiColors.white, + indicatorColor: UiColors.primaryInverse.withAlpha(51), // 20% of 255 + labelTextStyle: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return UiTypography.footnote2m.textPrimary; + } + return UiTypography.footnote2r.textInactive; + }), + ), + + // Switch Theme + switchTheme: SwitchThemeData( + trackColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return UiColors.switchActive; + } + return UiColors.switchInactive; + }), + thumbColor: const WidgetStatePropertyAll(UiColors.white), + ), + + // Checkbox Theme + checkboxTheme: CheckboxThemeData( + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) return UiColors.primary; + return null; + }), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + + // Radio Theme + radioTheme: RadioThemeData( + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) return UiColors.primary; + return null; + }), + ), + + // Slider Theme + sliderTheme: const SliderThemeData( + activeTrackColor: UiColors.primary, + inactiveTrackColor: UiColors.loaderInactive, + thumbColor: UiColors.primary, + overlayColor: UiColors.primaryInverse, + ), + + // Chip Theme + chipTheme: ChipThemeData( + backgroundColor: UiColors.bgSecondary, + labelStyle: UiTypography.footnote1m, + secondaryLabelStyle: UiTypography.footnote1m.white, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusMd), + side: const BorderSide(color: UiColors.borderStill, width: 0.5), + ), + + // SnackBar Theme + snackBarTheme: SnackBarThemeData( + backgroundColor: UiColors.toastBg, + contentTextStyle: UiTypography.body2r.white, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusMd), + elevation: 4, + ), + + // Bottom Sheet Theme + bottomSheetTheme: const BottomSheetThemeData( + backgroundColor: UiColors.bgSecondary, + modalBackgroundColor: UiColors.bgSecondary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(32)), + ), + ), + + // Expansion Tile Theme + expansionTileTheme: ExpansionTileThemeData( + iconColor: UiColors.iconSecondary, + collapsedIconColor: UiColors.iconPrimary, + backgroundColor: UiColors.bgPopup, + collapsedBackgroundColor: UiColors.transparent, + textColor: UiColors.textPrimary, + collapsedTextColor: UiColors.textPrimary, + tilePadding: const EdgeInsets.symmetric(horizontal: UiConstants.space4), + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusMd), + collapsedShape: RoundedRectangleBorder( + borderRadius: UiConstants.radiusMd, + ), + ), + + // Menu Theme + menuTheme: MenuThemeData( + style: MenuStyle( + backgroundColor: WidgetStateProperty.all(UiColors.bgPopup), + elevation: WidgetStateProperty.all(4), + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: UiConstants.radiusMd), + ), + ), + ), + + // Tooltip Theme + tooltipTheme: TooltipThemeData( + decoration: BoxDecoration( + color: UiColors.toastBg.withAlpha(230), // ~90% of 255 + borderRadius: UiConstants.radiusMd, + ), + textStyle: UiTypography.footnote2r.white, + ), + + // Progress Indicator Theme + progressIndicatorTheme: const ProgressIndicatorThemeData( + color: UiColors.primary, + linearTrackColor: UiColors.loaderInactive, + linearMinHeight: 4, + ), + ); + } +} diff --git a/apps/packages/design_system/lib/src/ui_typography.dart b/apps/packages/design_system/lib/src/ui_typography.dart new file mode 100644 index 00000000..9f3d5b99 --- /dev/null +++ b/apps/packages/design_system/lib/src/ui_typography.dart @@ -0,0 +1,564 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:design_system/design_system.dart'; +import 'ui_colors.dart'; + +/// Static definitions of typography styles for the Staff Design System. +class UiTypography { + UiTypography._(); + + // --------------------------------------------------------------------------- + // 0. Base Font Styles + // --------------------------------------------------------------------------- + + /// The primary font family used throughout the design system. + static final TextStyle _primaryBase = GoogleFonts.instrumentSans(); + + /// The secondary font family used for display or specialized elements. + static final TextStyle _secondaryBase = GoogleFonts.spaceGrotesk(); + + // --------------------------------------------------------------------------- + // 1. Primary Typography (Instrument Sans) + // --------------------------------------------------------------------------- + + // --- 1.1 Display --- + + /// Display Large - Font: Instrument Sans, Size: 36, Height: 1.1 (#121826) + static final TextStyle displayL = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 36, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display medium - Font: Instrument Sans, Size: 32, Height: 1.1 (#121826) + static final TextStyle displayM = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 32, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display small - Font: Instrument Sans, Size: 32, Height: 1.1 (#121826) + static final TextStyle displayMb = _primaryBase.copyWith( + fontWeight: FontWeight.w600, + fontSize: 32, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display 1 Medium - Font: Instrument Sans, Size: 26, Height: 1.1 (#121826) + static final TextStyle display1m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 26, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display 1 Regular - Font: Instrument Sans, Size: 38, Height: 1.3 (#121826) + static final TextStyle display1r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 26, + height: 1.3, + letterSpacing: -1, + color: UiColors.textPrimary, + ); + + /// Display 1 Bold - Font: Instrument Sans, Size: 38, Height: 1.3 (#121826) + static final TextStyle display1b = _primaryBase.copyWith( + fontWeight: FontWeight.w600, + fontSize: 26, + height: 1.3, + letterSpacing: -1, + color: UiColors.textPrimary, + ); + + /// Display 2 Medium - Font: Instrument Sans, Size: 16, Height: 1.1 (#121826) + static final TextStyle display2m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display 2 Regular - Font: Instrument Sans, Size: 28, Height: 1.5 (#121826) + static final TextStyle display2r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Display 3 Medium - Font: Instrument Sans, Size: 32, Height: 1.1 (#121826) + static final TextStyle display3m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display 3 Regular - Font: Instrument Sans, Size: 32, Height: 1.3 (#121826) + static final TextStyle display3r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + height: 1.3, + color: UiColors.textPrimary, + ); + + /// Display 3 Bold - Font: Instrument Sans, Size: 32, Height: 1.1 (#121826) + static final TextStyle display3b = _primaryBase.copyWith( + fontWeight: FontWeight.w600, + fontSize: 14, + height: 1.1, + color: UiColors.textPrimary, + ); + + // --- 1.2 Title --- + + /// Title 1 Medium - Font: Instrument Sans, Size: 18, Height: 1.5 (#121826) + static final TextStyle title1m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 18, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Title 1 Regular - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826) + static final TextStyle title1r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 18, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Title 2 Bold - Font: Instrument Sans, Size: 20, Height: 1.1 (#121826) + static final TextStyle title2b = _primaryBase.copyWith( + fontWeight: FontWeight.w600, + fontSize: 16, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Title 2 Medium - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826) + static final TextStyle title2m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Title 2 Regular - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826) + static final TextStyle title2r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + height: 1.5, + color: UiColors.textPrimary, + ); + + // --- 1.3 Headline --- + + /// Headline 1 Medium - Font: Instrument Sans, Size: 26, Height: 1.5 (#121826) + static final TextStyle headline1m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 26, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Headline 1 Regular - Font: Instrument Sans, Size: 26, Height: 1.5 (#121826) + static final TextStyle headline1r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 26, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Headline 2 Medium - Font: Instrument Sans, Size: 20, Height: 1.5 (#121826) + static final TextStyle headline2m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 22, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Headline 2 Regular - Font: Instrument Sans, Size: 20, Height: 1.5 (#121826) + static final TextStyle headline2r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 22, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Headline 3 Medium - Font: Instrument Sans, Size: 22, Height: 1.5 (#121826) + static final TextStyle headline3m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 20, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Headline 4 Medium - Font: Instrument Sans, Size: 22, Height: 1.5 (#121826) + static final TextStyle headline4m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 18, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Headline 4 Regular - Font: Instrument Sans, Size: 20, Height: 1.5 (#121826) + static final TextStyle headline4r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 18, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Headline 5 Regular - Font: Instrument Sans, Size: 18, Height: 1.5 (#121826) + static final TextStyle headline5r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Headline 5 Medium - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826) + static final TextStyle headline5m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16, + height: 1.5, + color: UiColors.textPrimary, + ); + + // --- 1.4 Title Uppercase --- + + /// Title Uppercase 2 Medium - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.7 (#121826) + static final TextStyle titleUppercase2m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.5, + letterSpacing: 0.7, + color: UiColors.textPrimary, + ); + + /// Title Uppercase 3 Medium - Font: Instrument Sans, Size: 12, Height: 1.5, Spacing: 1.5 (#121826) + static final TextStyle titleUppercase3m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 12, + height: 1.5, + letterSpacing: 1.5, + color: UiColors.textPrimary, + ); + + /// Title Uppercase 4 Medium - Font: Instrument Sans, Size: 11, Height: 1.5, Spacing: 2.2 (#121826) + static final TextStyle titleUppercase4m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 11, + height: 1.5, + letterSpacing: 2.2, + color: UiColors.textPrimary, + ); + + // --- 1.5 Body --- + + /// Body 1 Bold - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826) + static final TextStyle body1b = _primaryBase.copyWith( + fontWeight: FontWeight.w700, + fontSize: 16, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Body 1 Medium - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826) + static final TextStyle body1m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16, + height: 1.5, + letterSpacing: -0.025, + color: UiColors.textPrimary, + ); + + /// Body 1 Regular - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826) + static final TextStyle body1r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + height: 1.5, + letterSpacing: -0.05, + color: UiColors.textPrimary, + ); + + /// Body 2 Bold - Font: Instrument Sans, Size: 14, Height: 1.5 (#121826) + static final TextStyle body2b = _primaryBase.copyWith( + fontWeight: FontWeight.w700, + fontSize: 14, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Body 2 Medium - Font: Instrument Sans, Size: 14, Height: 1.5 (#121826) + static final TextStyle body2m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Body 2 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.1 (#121826) + static final TextStyle body2r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + height: 1.5, + letterSpacing: 0.1, + color: UiColors.textPrimary, + ); + + /// Body 3 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: -0.1 (#121826) + static final TextStyle body3r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + height: 1.5, + letterSpacing: -0.1, + color: UiColors.textPrimary, + ); + + /// Body 4 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.05 (#121826) + static final TextStyle body4r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + height: 1.5, + letterSpacing: 0.05, + color: UiColors.textPrimary, + ); + + /// Body 4 Medium - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.05 (#121826) + static final TextStyle body4m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 12, + height: 1.5, + letterSpacing: 0.05, + color: UiColors.textPrimary, + ); + + // --- 1.6 Footnote --- + + /// Footnote 1 Medium - Font: Instrument Sans, Size: 12, Height: 1.5 (#121826) + static final TextStyle footnote1m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 12, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Footnote 1 Regular - Font: Instrument Sans, Size: 12, Height: 1.5, Spacing: 0.05 (#121826) + static final TextStyle footnote1r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + height: 1.5, + letterSpacing: 0.05, + color: UiColors.textPrimary, + ); + + /// Footnote 1 Bold - Font: Instrument Sans, Size: 12, Height: 1.5, Spacing: 0.05 (#121826) + static final TextStyle footnote1b = _primaryBase.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + height: 1.5, + letterSpacing: 0.05, + color: UiColors.textPrimary, + ); + + /// Footnote 2 Medium - Font: Instrument Sans, Size: 10, Height: 1.5 (#121826) + static final TextStyle footnote2m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 10, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Footnote 2 Bold - Font: Instrument Sans, Size: 10, Height: 1.5 (#121826) + static final TextStyle footnote2b = _primaryBase.copyWith( + fontWeight: FontWeight.w700, + fontSize: 10, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Footnote 2 Regular - Font: Instrument Sans, Size: 10, Height: 1.5 (#121826) + static final TextStyle footnote2r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 10, + height: 1.5, + color: UiColors.textPrimary, + ); + + // --- 1.7 Button --- + + /// Button S - Font: Instrument Sans, Size: 10, Height: 1.5 (#121826) + static final TextStyle buttonS = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 10, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Button Medium - Font: Instrument Sans, Size: 12, Height: 1.5 (#121826) + static final TextStyle buttonM = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 12, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Button Large - Font: Instrument Sans, Size: 14, Height: 1.5 (#121826) + static final TextStyle buttonL = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.5, + color: UiColors.textPrimary, + ); + + /// Button XL - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826) + static final TextStyle buttonXL = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16, + height: 1.5, + color: UiColors.textPrimary, + ); + + // --- 1.8 Link --- + + /// Link 1 Regular - Font: Instrument Sans, Size: 16, Height: 1.5, Underlined (#0A39DF) + static final TextStyle link1r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 16, + height: 1.5, + color: UiColors.textLink, + decoration: TextDecoration.underline, + decorationColor: UiColors.textLink, + ); + + /// Link 2 Medium - Font: Instrument Sans, Size: 14, Height: 1.5, Underlined (#0A39DF) + static final TextStyle link2m = _primaryBase.copyWith( + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.5, + color: UiColors.textLink, + decoration: TextDecoration.underline, + decorationColor: UiColors.textLink, + ); + + /// Link 2 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Underlined (#0A39DF) + static final TextStyle link2r = _primaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + height: 1.5, + color: UiColors.textLink, + decoration: TextDecoration.underline, + decorationColor: UiColors.textLink, + ); + + // --------------------------------------------------------------------------- + // 2. Secondary Typography (Space Grotesk) + // --------------------------------------------------------------------------- + + // --- 2.1 Display --- + + /// Display 1 Bold (Secondary) - Font: Space Grotesk, Size: 50, Height: 1.1 (#121826) + static final TextStyle secondaryDisplay1b = _secondaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 50, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display 1 Regular (Secondary) - Font: Space Grotesk, Size: 50, Height: 1.1 (#121826) + static final TextStyle secondaryDisplay1r = _secondaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 50, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display 2 Bold (Secondary) - Font: Space Grotesk, Size: 40, Height: 1.1 (#121826) + static final TextStyle secondaryDisplay2b = _secondaryBase.copyWith( + fontWeight: FontWeight.w700, + fontSize: 40, + height: 1.1, + color: UiColors.textPrimary, + ); + + /// Display 2 Regular (Secondary) - Font: Space Grotesk, Size: 40, Height: 1.1 (#121826) + static final TextStyle secondaryDisplay2r = _secondaryBase.copyWith( + fontWeight: FontWeight.w400, + fontSize: 40, + height: 1.1, + color: UiColors.textPrimary, + ); + + // --------------------------------------------------------------------------- + // 3. TextTheme Mapping + // --------------------------------------------------------------------------- + + /// Primary TextTheme + static TextTheme get textTheme => TextTheme( + displayLarge: display1r, + displayMedium: displayL, + displaySmall: display3m, + headlineLarge: headline1m, + headlineMedium: headline3m, + headlineSmall: headline2m, + titleLarge: title1m, + titleMedium: title2m, + titleSmall: body2m, + bodyLarge: body1r, + bodyMedium: body2r, + bodySmall: footnote1r, + labelLarge: buttonL, + labelMedium: buttonM, + labelSmall: footnote2r, + ); +} + +/// Extension to easily color text styles using the Staff Design System color palette. +extension TypographyColors on TextStyle { + /// Primary text color (#121826) + TextStyle get textPrimary => copyWith(color: UiColors.textPrimary); + + /// Secondary text color (#6A7382) + TextStyle get textSecondary => copyWith(color: UiColors.textSecondary); + + /// Inactive text color (#9CA3AF) + TextStyle get textInactive => copyWith(color: UiColors.textInactive); + + /// Tertiary text color (#9CA3AF) + TextStyle get textTertiary => copyWith(color: UiColors.textInactive); + + /// Placeholder text color (#9CA3AF) + TextStyle get textPlaceholder => copyWith(color: UiColors.textPlaceholder); + + /// Description text color (#6A7382) + TextStyle get textDescription => copyWith(color: UiColors.textDescription); + + /// Success text color (#10B981) + TextStyle get textSuccess => copyWith(color: UiColors.textSuccess); + + /// Error text color (#F04444) + TextStyle get textError => copyWith(color: UiColors.textError); + + /// Warning text color (#D97706) + TextStyle get textWarning => copyWith(color: UiColors.textWarning); + + /// Link text color (#0A39DF) + TextStyle get textLink => copyWith(color: UiColors.textLink); + + /// White text color (#FFFFFF) + TextStyle get white => copyWith(color: UiColors.white); + + /// Black text color (#000000) + TextStyle get black => copyWith(color: UiColors.black); + + /// Underline decoration + TextStyle get underline => copyWith(decoration: TextDecoration.underline); + + /// Active content color + TextStyle get activeContentColor => copyWith(color: UiColors.textPrimary); +} diff --git a/apps/packages/design_system/lib/src/widgets/ui_app_bar.dart b/apps/packages/design_system/lib/src/widgets/ui_app_bar.dart new file mode 100644 index 00000000..2af61b8b --- /dev/null +++ b/apps/packages/design_system/lib/src/widgets/ui_app_bar.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; + +import '../ui_icons.dart'; + +/// A custom AppBar for the Krow UI design system. +/// +/// This widget provides a consistent look and feel for top app bars across the application. +class UiAppBar extends StatelessWidget implements PreferredSizeWidget { + /// The title text to display in the app bar. + final String? title; + + /// A widget to display instead of the title text. + final Widget? titleWidget; + + /// The widget to display before the title. + /// Usually an [IconButton] for navigation. + final Widget? leading; + + /// A list of Widgets to display in a row after the [title] widget. + final List? actions; + + /// The height of the app bar. Defaults to [kToolbarHeight]. + final double height; + + /// Whether the title should be centered. + final bool centerTitle; + + /// Signature for the callback that is called when the leading button is pressed. + /// If [leading] is null, this callback will be used for a default back button. + final VoidCallback? onLeadingPressed; + + /// Whether to show a default back button if [leading] is null. + final bool showBackButton; + + /// This widget appears across the bottom of the app bar. + /// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can be used at the bottom of an app bar. + final PreferredSizeWidget? bottom; + + const UiAppBar({ + super.key, + this.title, + this.titleWidget, + this.leading, + this.actions, + this.height = kToolbarHeight, + this.centerTitle = true, + this.onLeadingPressed, + this.showBackButton = true, + this.bottom, + }); + + @override + Widget build(BuildContext context) { + return AppBar( + title: titleWidget ?? + (title != null + ? Text( + title!, + ) + : null), + leading: leading ?? + (showBackButton + ? IconButton( + icon: const Icon(UiIcons.chevronLeft, size: 20), + onPressed: onLeadingPressed ?? () => Navigator.of(context).pop(), + ) + : null), + actions: actions, + centerTitle: centerTitle, + bottom: bottom, + ); + } + + @override + Size get preferredSize => Size.fromHeight(height + (bottom?.preferredSize.height ?? 0.0)); +} diff --git a/apps/packages/design_system/lib/src/widgets/ui_button.dart b/apps/packages/design_system/lib/src/widgets/ui_button.dart new file mode 100644 index 00000000..4f0535c6 --- /dev/null +++ b/apps/packages/design_system/lib/src/widgets/ui_button.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import '../ui_constants.dart'; + +/// A custom button widget with different variants and icon support. +class UiButton extends StatelessWidget { + /// The text to display on the button. + final String? text; + + /// Optional custom child widget. If provided, overrides text and icons. + final Widget? child; + + /// Callback when the button is tapped. + final VoidCallback? onPressed; + + /// Optional leading icon. + final IconData? leadingIcon; + + /// Optional trailing icon. + final IconData? trailingIcon; + + /// Optional Style + final ButtonStyle? style; + + /// The size of the icons. Defaults to 20. + final double iconSize; + + /// The size of the button. + final UiButtonSize size; + + /// The button widget to use (ElevatedButton, OutlinedButton, or TextButton). + final Widget Function( + BuildContext context, + VoidCallback? onPressed, + ButtonStyle? style, + Widget child, + ) + buttonBuilder; + + /// Creates a [UiButton] with a custom button builder. + const UiButton({ + super.key, + this.text, + this.child, + required this.buttonBuilder, + this.onPressed, + this.leadingIcon, + this.trailingIcon, + this.style, + this.iconSize = 20, + this.size = UiButtonSize.medium, + }) : assert( + text != null || child != null, + 'Either text or child must be provided', + ); + + /// Creates a primary button using [ElevatedButton]. + UiButton.primary({ + super.key, + this.text, + this.child, + this.onPressed, + this.leadingIcon, + this.trailingIcon, + this.style, + this.iconSize = 20, + this.size = UiButtonSize.medium, + }) : buttonBuilder = _elevatedButtonBuilder, + assert( + text != null || child != null, + 'Either text or child must be provided', + ); + + /// Creates a secondary button using [OutlinedButton]. + UiButton.secondary({ + super.key, + this.text, + this.child, + this.onPressed, + this.leadingIcon, + this.trailingIcon, + this.style, + this.iconSize = 20, + this.size = UiButtonSize.medium, + }) : buttonBuilder = _outlinedButtonBuilder, + assert( + text != null || child != null, + 'Either text or child must be provided', + ); + + /// Creates a text button using [TextButton]. + UiButton.text({ + super.key, + this.text, + this.child, + this.onPressed, + this.leadingIcon, + this.trailingIcon, + this.style, + this.iconSize = 20, + this.size = UiButtonSize.medium, + }) : buttonBuilder = _textButtonBuilder, + assert( + text != null || child != null, + 'Either text or child must be provided', + ); + + @override + /// Builds the button UI. + Widget build(BuildContext context) { + return buttonBuilder(context, onPressed, style, _buildButtonContent()); + } + + /// Builds the button content with optional leading and trailing icons. + Widget _buildButtonContent() { + if (child != null) { + return child!; + } + + // Single icon or text case + if (leadingIcon == null && trailingIcon == null) { + return Text(text!); + } + + if (leadingIcon != null && text == null && trailingIcon == null) { + return Icon(leadingIcon, size: iconSize); + } + + // Multiple elements case + final List children = []; + + if (leadingIcon != null) { + children.add(Icon(leadingIcon, size: iconSize)); + children.add(const SizedBox(width: UiConstants.space2)); + } + + children.add(Text(text!)); + + if (trailingIcon != null) { + children.add(const SizedBox(width: UiConstants.space2)); + children.add(Icon(trailingIcon, size: iconSize)); + } + + return Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: children, + ); + } + + /// Builder for ElevatedButton. + static Widget _elevatedButtonBuilder( + BuildContext context, + VoidCallback? onPressed, + ButtonStyle? style, + Widget child, + ) { + return ElevatedButton(onPressed: onPressed, style: style, child: child); + } + + /// Builder for OutlinedButton. + static Widget _outlinedButtonBuilder( + BuildContext context, + VoidCallback? onPressed, + ButtonStyle? style, + Widget child, + ) { + return OutlinedButton(onPressed: onPressed, style: style, child: child); + } + + /// Builder for TextButton. + static Widget _textButtonBuilder( + BuildContext context, + VoidCallback? onPressed, + ButtonStyle? style, + Widget child, + ) { + return TextButton(onPressed: onPressed, style: style, child: child); + } +} + +/// Defines the size of a [UiButton]. +enum UiButtonSize { + /// Small button (compact) + small, + + /// Medium button (standard) + medium, + + /// Large button (prominent) + large, +} diff --git a/apps/packages/design_system/lib/src/widgets/ui_chip.dart b/apps/packages/design_system/lib/src/widgets/ui_chip.dart new file mode 100644 index 00000000..55ec46d0 --- /dev/null +++ b/apps/packages/design_system/lib/src/widgets/ui_chip.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import '../ui_colors.dart'; +import '../ui_constants.dart'; +import '../ui_typography.dart'; + +/// Sizes for the [UiChip] widget. +enum UiChipSize { + /// Small size (e.g. for tags in tight spaces). + small, + + /// Medium size (default). + medium, + + /// Large size (e.g. for standalone filters). + large, +} + +/// Themes for the [UiChip] widget. +enum UiChipVariant { + /// Primary style with solid background. + primary, + + /// Secondary style with light background. + secondary, + + /// Accent style with highlight background. + accent, +} + +/// A custom chip widget with supports for different sizes, themes, and icons. +class UiChip extends StatelessWidget { + /// The text label to display. + final String label; + + /// The size of the chip. Defaults to [UiChipSize.medium]. + final UiChipSize size; + + /// The theme variant of the chip. Defaults to [UiChipVariant.secondary]. + final UiChipVariant variant; + + /// Optional leading icon. + final IconData? leadingIcon; + + /// Optional trailing icon. + final IconData? trailingIcon; + + /// Callback when the chip is tapped. + final VoidCallback? onTap; + + /// Callback when the trailing icon is tapped (e.g. for removal). + final VoidCallback? onTrailingIconTap; + + /// Whether the chip is currently selected/active. + final bool isSelected; + + /// Creates a [UiChip]. + const UiChip({ + super.key, + required this.label, + this.size = UiChipSize.medium, + this.variant = UiChipVariant.secondary, + this.leadingIcon, + this.trailingIcon, + this.onTap, + this.onTrailingIconTap, + this.isSelected = false, + }); + + @override + Widget build(BuildContext context) { + final backgroundColor = _getBackgroundColor(); + final contentColor = _getContentColor(); + final textStyle = _getTextStyle().copyWith(color: contentColor); + final padding = _getPadding(); + final iconSize = _getIconSize(); + + final content = Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (leadingIcon != null) ...[ + Icon(leadingIcon, size: iconSize, color: contentColor), + SizedBox(width: _getGap()), + ], + Text(label, style: textStyle), + if (trailingIcon != null) ...[ + SizedBox(width: _getGap()), + GestureDetector( + onTap: onTrailingIconTap, + child: Icon(trailingIcon, size: iconSize, color: contentColor), + ), + ], + ], + ); + + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: padding, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: UiConstants.radiusFull, + border: _getBorder(), + ), + child: content, + ), + ); + } + + Color _getBackgroundColor() { + if (!isSelected && variant == UiChipVariant.primary) { + return UiColors.white; + } + + switch (variant) { + case UiChipVariant.primary: + return UiColors.primary; + case UiChipVariant.secondary: + return UiColors.tagInProgress; + case UiChipVariant.accent: + return UiColors.accent; + } + } + + Color _getContentColor() { + if (!isSelected && variant == UiChipVariant.primary) { + return UiColors.textSecondary; + } + + switch (variant) { + case UiChipVariant.primary: + return UiColors.white; + case UiChipVariant.secondary: + return UiColors.primary; + case UiChipVariant.accent: + return UiColors.accentForeground; + } + } + + TextStyle _getTextStyle() { + switch (size) { + case UiChipSize.small: + return UiTypography.body3r; + case UiChipSize.medium: + return UiTypography.body2m; + case UiChipSize.large: + return UiTypography.body1m; + } + } + + EdgeInsets _getPadding() { + switch (size) { + case UiChipSize.small: + return const EdgeInsets.symmetric(horizontal: 10, vertical: 6); + case UiChipSize.medium: + return const EdgeInsets.symmetric(horizontal: 12, vertical: 8); + case UiChipSize.large: + return const EdgeInsets.symmetric(horizontal: 16, vertical: 10); + } + } + + double _getIconSize() { + switch (size) { + case UiChipSize.small: + return 12; + case UiChipSize.medium: + return 16; + case UiChipSize.large: + return 20; + } + } + + double _getGap() { + switch (size) { + case UiChipSize.small: + return UiConstants.space1; + case UiChipSize.medium: + return UiConstants.space1 + 2; + case UiChipSize.large: + return UiConstants.space2; + } + } + + BoxBorder? _getBorder() { + if (!isSelected && variant == UiChipVariant.primary) { + return Border.all(color: UiColors.border); + } + return null; + } +} diff --git a/apps/packages/design_system/lib/src/widgets/ui_icon_button.dart b/apps/packages/design_system/lib/src/widgets/ui_icon_button.dart new file mode 100644 index 00000000..d49ac67d --- /dev/null +++ b/apps/packages/design_system/lib/src/widgets/ui_icon_button.dart @@ -0,0 +1,89 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import '../ui_colors.dart'; +import '../ui_constants.dart'; + +/// A custom icon button with blur effect and different variants. +class UiIconButton extends StatelessWidget { + /// The icon to display. + final IconData icon; + + /// The size of the icon button. + final double size; + + /// The size of the icon. + final double iconSize; + + /// The background color of the button. + final Color backgroundColor; + + /// The color of the icon. + final Color iconColor; + + /// Whether to apply blur effect. + final bool useBlur; + + /// Callback when the button is tapped. + final VoidCallback? onTap; + + /// Creates a [UiIconButton] with custom properties. + const UiIconButton({ + super.key, + required this.icon, + this.size = 40, + this.iconSize = 20, + required this.backgroundColor, + required this.iconColor, + this.useBlur = false, + this.onTap, + }); + + /// Creates a primary variant icon button with solid background. + const UiIconButton.primary({ + super.key, + required this.icon, + this.size = 40, + this.iconSize = 20, + this.onTap, + }) : backgroundColor = UiColors.primary, + iconColor = UiColors.white, + useBlur = false; + + /// Creates a secondary variant icon button with blur effect. + UiIconButton.secondary({ + super.key, + required this.icon, + this.size = 40, + this.iconSize = 20, + this.onTap, + }) : backgroundColor = UiColors.primary.withAlpha(96), + iconColor = UiColors.primary, + useBlur = true; + + @override + /// Builds the icon button UI. + Widget build(BuildContext context) { + final Widget button = Container( + width: size, + height: size, + decoration: BoxDecoration(color: backgroundColor, shape: BoxShape.circle), + child: Icon(icon, color: iconColor, size: iconSize), + ); + + final Widget content = useBlur + ? ClipRRect( + borderRadius: UiConstants.radiusFull, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: button, + ), + ) + : button; + + if (onTap != null) { + return GestureDetector(onTap: onTap, child: content); + } + + return content; + } +} diff --git a/apps/packages/design_system/lib/src/widgets/ui_step_indicator.dart b/apps/packages/design_system/lib/src/widgets/ui_step_indicator.dart new file mode 100644 index 00000000..e26275a8 --- /dev/null +++ b/apps/packages/design_system/lib/src/widgets/ui_step_indicator.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import '../ui_colors.dart'; +import '../ui_constants.dart'; +import '../ui_icons.dart'; + +/// A widget that displays a horizontal step indicator with icons. +/// +/// This widget shows a series of circular step indicators connected by lines, +/// with different visual states for completed, active, and inactive steps. +class UiStepIndicator extends StatelessWidget { + /// The list of icons to display for each step. + final List stepIcons; + + /// The index of the currently active step (0-based). + final int currentStep; + + /// Creates a [UiStepIndicator]. + const UiStepIndicator({ + super.key, + required this.stepIcons, + required this.currentStep, + }); + + @override + /// Builds the step indicator UI. + Widget build(BuildContext context) { + // active step color + const Color activeColor = UiColors.primary; + // completed step color + const Color completedColor = UiColors.textSuccess; + // inactive step color + const Color inactiveColor = UiColors.iconSecondary; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: UiConstants.space2), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(stepIcons.length, (index) { + final bool isActive = index == currentStep; + final bool isCompleted = index < currentStep; + + Color bgColor; + Color iconColor; + if (isCompleted) { + bgColor = completedColor.withAlpha(24); + iconColor = completedColor; + } else if (isActive) { + bgColor = activeColor.withAlpha(24); + iconColor = activeColor; + } else { + bgColor = inactiveColor.withAlpha(24); + iconColor = inactiveColor.withAlpha(128); + } + + return Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: bgColor, + shape: BoxShape.circle, + ), + child: Icon( + isCompleted ? UiIcons.check : stepIcons[index], + size: 20, + color: iconColor, + ), + ), + if (index < stepIcons.length - 1) + Container( + width: 30, + height: 2, + margin: const EdgeInsets.symmetric( + horizontal: UiConstants.space1, + ), + color: isCompleted + ? completedColor.withAlpha(96) + : inactiveColor.withAlpha(96), + ), + ], + ); + }), + ), + ); + } +} diff --git a/apps/packages/design_system/lib/src/widgets/ui_text_field.dart b/apps/packages/design_system/lib/src/widgets/ui_text_field.dart new file mode 100644 index 00000000..0ea7cb09 --- /dev/null +++ b/apps/packages/design_system/lib/src/widgets/ui_text_field.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../ui_typography.dart'; +import '../ui_constants.dart'; +import '../ui_colors.dart'; + +/// A custom TextField for the Krow UI design system. +/// +/// This widget combines a label and a [TextField] with consistent styling. +class UiTextField extends StatelessWidget { + /// The label text to display above the text field. + final String? label; + + /// The hint text to display inside the text field when empty. + final String? hintText; + + /// Signature for the callback that is called when the text in the field changes. + final ValueChanged? onChanged; + + /// The controller for the text field. + final TextEditingController? controller; + + /// The type of keyboard to use for editing the text. + final TextInputType? keyboardType; + + /// The maximum number of lines for the text field. Defaults to 1. + final int? maxLines; + + /// Whether to hide the text being edited (e.g., for passwords). Defaults to false. + final bool obscureText; + + /// The type of action button to use for the keyboard. + final TextInputAction? textInputAction; + + /// Signature for the callback that is called when the user submits the text field. + final ValueChanged? onSubmitted; + + /// Whether the text field should be focused automatically. Defaults to false. + final bool autofocus; + + /// Optional input formatters to validate or format the text as it is typed. + final List? inputFormatters; + + /// Optional prefix icon to display at the start of the text field. + final IconData? prefixIcon; + + /// Optional suffix icon to display at the end of the text field. + final IconData? suffixIcon; + + /// Optional custom suffix widget to display at the end (e.g., password toggle). + final Widget? suffix; + + /// Whether the text field should be read-only. + final bool readOnly; + + /// Callback when the text field is tapped. + final VoidCallback? onTap; + + const UiTextField({ + super.key, + this.label, + this.hintText, + this.onChanged, + this.controller, + this.keyboardType, + this.maxLines = 1, + this.obscureText = false, + this.textInputAction, + this.onSubmitted, + this.autofocus = false, + this.inputFormatters, + this.prefixIcon, + this.suffixIcon, + this.suffix, + this.readOnly = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (label != null) ...[ + Text(label!, style: UiTypography.body4m.textSecondary), + const SizedBox(height: UiConstants.space1), + ], + TextField( + controller: controller, + onChanged: onChanged, + keyboardType: keyboardType, + maxLines: maxLines, + obscureText: obscureText, + textInputAction: textInputAction, + onSubmitted: onSubmitted, + autofocus: autofocus, + inputFormatters: inputFormatters, + readOnly: readOnly, + onTap: onTap, + style: UiTypography.body1r.textPrimary, + decoration: InputDecoration( + hintText: hintText, + prefixIcon: prefixIcon != null + ? Icon(prefixIcon, size: 20, color: UiColors.iconSecondary) + : null, + suffixIcon: suffixIcon != null + ? Icon(suffixIcon, size: 20, color: UiColors.iconSecondary) + : suffix, + ), + ), + ], + ); + } +} diff --git a/apps/packages/design_system/pubspec.yaml b/apps/packages/design_system/pubspec.yaml new file mode 100644 index 00000000..fb4e10c9 --- /dev/null +++ b/apps/packages/design_system/pubspec.yaml @@ -0,0 +1,31 @@ +name: design_system +description: "A new Flutter package project." +version: 0.0.1 +homepage: +resolution: workspace + +environment: + sdk: ^3.10.7 + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + google_fonts: ^7.0.2 + lucide_icons: ^0.257.0 + font_awesome_flutter: ^10.7.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.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: + + # To add assets to your package, add an assets section, like this: + assets: + - assets/ diff --git a/apps/packages/design_system/test/design_system_test.dart b/apps/packages/design_system/test/design_system_test.dart new file mode 100644 index 00000000..c2beb8de --- /dev/null +++ b/apps/packages/design_system/test/design_system_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:design_system/design_system.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} diff --git a/apps/packages/domain/lib/krow_domain.dart b/apps/packages/domain/lib/krow_domain.dart new file mode 100644 index 00000000..0940d703 --- /dev/null +++ b/apps/packages/domain/lib/krow_domain.dart @@ -0,0 +1,57 @@ +/// The Shared Domain Layer. +/// +/// This package contains the core business entities and rules. +/// It is pure Dart and has no dependencies on Flutter or Firebase. +/// +/// Note: Repository Interfaces are now located in their respective Feature packages. + +// Users & Membership +export 'src/entities/users/user.dart'; +export 'src/entities/users/staff.dart'; +export 'src/entities/users/membership.dart'; +export 'src/entities/users/biz_member.dart'; +export 'src/entities/users/hub_member.dart'; + +// Business & Organization +export 'src/entities/business/business.dart'; +export 'src/entities/business/business_setting.dart'; +export 'src/entities/business/hub.dart'; +export 'src/entities/business/hub_department.dart'; +export 'src/entities/business/biz_contract.dart'; + +// Events & Shifts +export 'src/entities/events/event.dart'; +export 'src/entities/events/event_shift.dart'; +export 'src/entities/events/event_shift_position.dart'; +export 'src/entities/events/assignment.dart'; +export 'src/entities/events/work_session.dart'; + +// Skills & Certs +export 'src/entities/skills/skill.dart'; +export 'src/entities/skills/skill_category.dart'; +export 'src/entities/skills/staff_skill.dart'; +export 'src/entities/skills/certificate.dart'; +export 'src/entities/skills/skill_kit.dart'; + +// Financial & Payroll +export 'src/entities/financial/invoice.dart'; +export 'src/entities/financial/invoice_item.dart'; +export 'src/entities/financial/invoice_decline.dart'; +export 'src/entities/financial/staff_payment.dart'; + +// Ratings & Penalties +export 'src/entities/ratings/staff_rating.dart'; +export 'src/entities/ratings/penalty_log.dart'; +export 'src/entities/ratings/business_staff_preference.dart'; + +// Staff Profile +export 'src/entities/profile/emergency_contact.dart'; +export 'src/entities/profile/bank_account.dart'; +export 'src/entities/profile/accessibility.dart'; +export 'src/entities/profile/schedule.dart'; + +// Support & Config +export 'src/entities/support/addon.dart'; +export 'src/entities/support/tag.dart'; +export 'src/entities/support/media.dart'; +export 'src/entities/support/working_area.dart'; diff --git a/apps/packages/domain/lib/src/entities/business/biz_contract.dart b/apps/packages/domain/lib/src/entities/business/biz_contract.dart new file mode 100644 index 00000000..81ebf648 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/business/biz_contract.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a legal or service contract. +/// +/// Can be between a business and the platform, or a business and staff. +class BizContract extends Equatable { + /// Unique identifier. + final String id; + + /// The [Business] party to the contract. + final String businessId; + + /// Descriptive name of the contract. + final String name; + + /// Valid from date. + final DateTime startDate; + + /// Valid until date (null if indefinite). + final DateTime? endDate; + + /// URL to the document content (PDF/HTML). + final String contentUrl; + + const BizContract({ + required this.id, + required this.businessId, + required this.name, + required this.startDate, + this.endDate, + required this.contentUrl, + }); + + @override + List get props => [id, businessId, name, startDate, endDate, contentUrl]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/business/business.dart b/apps/packages/domain/lib/src/entities/business/business.dart new file mode 100644 index 00000000..a719d748 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/business/business.dart @@ -0,0 +1,47 @@ +import 'package:equatable/equatable.dart'; + +/// The operating status of a [Business]. +enum BusinessStatus { + /// Business created but not yet approved. + pending, + + /// Fully active and operational. + active, + + /// Temporarily suspended (e.g. for non-payment). + suspended, + + /// Permanently inactive. + inactive, +} + +/// Represents a Client Company / Business. +/// +/// This is the top-level organizational entity in the system. +class Business extends Equatable { + /// Unique identifier for the business. + final String id; + + /// Display name of the business. + final String name; + + /// Legal registration or tax number. + final String registrationNumber; + + /// Current operating status. + final BusinessStatus status; + + /// URL to the business logo. + final String? avatar; + + const Business({ + required this.id, + required this.name, + required this.registrationNumber, + required this.status, + this.avatar, + }); + + @override + List get props => [id, name, registrationNumber, status, avatar]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/business/business_setting.dart b/apps/packages/domain/lib/src/entities/business/business_setting.dart new file mode 100644 index 00000000..b9f62bd0 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/business/business_setting.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +/// Represents payroll and operational configuration for a [Business]. +class BusinessSetting extends Equatable { + /// Unique identifier for the settings record. + final String id; + + /// The [Business] these settings apply to. + final String businessId; + + /// Prefix for generated invoices (e.g., "INV-"). + final String prefix; + + /// Whether overtime calculations are applied. + final bool overtimeEnabled; + + /// Requirement method for clocking in (e.g. "qr_code", "geo_fence"). + final String? clockInRequirement; + + /// Requirement method for clocking out. + final String? clockOutRequirement; + + const BusinessSetting({ + required this.id, + required this.businessId, + required this.prefix, + required this.overtimeEnabled, + this.clockInRequirement, + this.clockOutRequirement, + }); + + @override + List get props => [ + id, + businessId, + prefix, + overtimeEnabled, + clockInRequirement, + clockOutRequirement, + ]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/business/hub.dart b/apps/packages/domain/lib/src/entities/business/hub.dart new file mode 100644 index 00000000..ac5a46c7 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/business/hub.dart @@ -0,0 +1,42 @@ +import 'package:equatable/equatable.dart'; + +/// The status of a [Hub]. +enum HubStatus { + /// Fully operational. + active, + + /// Closed or inactive. + inactive, + + /// Not yet ready for operations. + underConstruction, +} + +/// Represents a branch location or operational unit within a [Business]. +class Hub extends Equatable { + /// Unique identifier. + final String id; + + /// The parent [Business]. + final String businessId; + + /// Display name of the hub (e.g. "Downtown Branch"). + final String name; + + /// Physical address of this hub. + final String address; + + /// Operational status. + final HubStatus status; + + const Hub({ + required this.id, + required this.businessId, + required this.name, + required this.address, + required this.status, + }); + + @override + List get props => [id, businessId, name, address, status]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/business/hub_department.dart b/apps/packages/domain/lib/src/entities/business/hub_department.dart new file mode 100644 index 00000000..0e8f523e --- /dev/null +++ b/apps/packages/domain/lib/src/entities/business/hub_department.dart @@ -0,0 +1,24 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a department within a [Hub]. +/// +/// Used for more granular organization of staff and events (e.g. "Kitchen", "Service"). +class HubDepartment extends Equatable { + /// Unique identifier. + final String id; + + /// The [Hub] this department belongs to. + final String hubId; + + /// Name of the department. + final String name; + + const HubDepartment({ + required this.id, + required this.hubId, + required this.name, + }); + + @override + List get props => [id, hubId, name]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/events/assignment.dart b/apps/packages/domain/lib/src/entities/events/assignment.dart new file mode 100644 index 00000000..26795977 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/events/assignment.dart @@ -0,0 +1,58 @@ +import 'package:equatable/equatable.dart'; + +/// The status of a staff [Assignment]. +enum AssignmentStatus { + /// Staff member has been assigned but hasn't confirmed. + assigned, + + /// Staff member has accepted the assignment. + confirmed, + + /// Work is currently in progress (Clocked In). + ongoing, + + /// Work completed successfully (Clocked Out). + completed, + + /// Staff rejected the assignment offer. + declinedByStaff, + + /// Staff canceled after accepting. + canceledByStaff, + + /// Staff did not show up. + noShowed, +} + +/// Represents the link between a [Staff] member and an [EventShiftPosition]. +class Assignment extends Equatable { + /// Unique identifier. + final String id; + + /// The job position being filled. + final String positionId; + + /// The staff member filling the position. + final String staffId; + + /// Current status of the assignment. + final AssignmentStatus status; + + /// Actual timestamp when staff clocked in. + final DateTime? clockIn; + + /// Actual timestamp when staff clocked out. + final DateTime? clockOut; + + const Assignment({ + required this.id, + required this.positionId, + required this.staffId, + required this.status, + this.clockIn, + this.clockOut, + }); + + @override + List get props => [id, positionId, staffId, status, clockIn, clockOut]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/events/event.dart b/apps/packages/domain/lib/src/entities/events/event.dart new file mode 100644 index 00000000..717fb24a --- /dev/null +++ b/apps/packages/domain/lib/src/entities/events/event.dart @@ -0,0 +1,70 @@ +import 'package:equatable/equatable.dart'; + +/// The workflow status of an [Event]. +enum EventStatus { + /// Created but incomplete. + draft, + + /// Waiting for approval or publication. + pending, + + /// Published and staff have been assigned. + assigned, + + /// Fully confirmed and ready to start. + confirmed, + + /// Currently in progress. + active, + + /// Work has finished. + finished, + + /// All post-event processes (invoicing) complete. + completed, + + /// Archived. + closed, + + /// Flagged for administrative review. + underReview, +} + +/// Represents a Job Posting or Event. +/// +/// This is the central entity for scheduling work. An Event contains [EventShift]s. +class Event extends Equatable { + /// Unique identifier. + final String id; + + /// The [Business] hosting the event. + final String businessId; + + /// The [Hub] location. + final String hubId; + + /// Title of the event. + final String name; + + /// Date of the event. + final DateTime date; + + /// Current workflow status. + final EventStatus status; + + /// Type of employment contract (e.g., 'freelance', 'permanent'). + final String contractType; + + const Event({ + required this.id, + required this.businessId, + required this.hubId, + required this.name, + required this.date, + required this.status, + required this.contractType, + }); + + @override + List get props => [id, businessId, hubId, name, date, status, contractType]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/events/event_shift.dart b/apps/packages/domain/lib/src/entities/events/event_shift.dart new file mode 100644 index 00000000..d5218e19 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/events/event_shift.dart @@ -0,0 +1,28 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a specific time block or "shift" within an [Event]. +/// +/// An Event can have multiple shifts (e.g. "Morning Shift", "Evening Shift"). +class EventShift extends Equatable { + /// Unique identifier. + final String id; + + /// The [Event] this shift belongs to. + final String eventId; + + /// Descriptive name (e.g. "Setup Crew"). + final String name; + + /// Specific address for this shift (if different from Hub). + final String address; + + const EventShift({ + required this.id, + required this.eventId, + required this.name, + required this.address, + }); + + @override + List get props => [id, eventId, name, address]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/events/event_shift_position.dart b/apps/packages/domain/lib/src/entities/events/event_shift_position.dart new file mode 100644 index 00000000..abceb7b9 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/events/event_shift_position.dart @@ -0,0 +1,53 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a specific job opening within a [EventShift]. +/// +/// Defines the requirement for a specific [Skill], the quantity needed, and the pay. +class EventShiftPosition extends Equatable { + /// Unique identifier. + final String id; + + /// The [EventShift] this position is part of. + final String shiftId; + + /// The [Skill] required for this position. + final String skillId; + + /// Number of staff needed. + final int count; + + /// Hourly pay rate. + final double rate; + + /// Start time of this specific position. + final DateTime startTime; + + /// End time of this specific position. + final DateTime endTime; + + /// Deducted break duration in minutes. + final int breakDurationMinutes; + + const EventShiftPosition({ + required this.id, + required this.shiftId, + required this.skillId, + required this.count, + required this.rate, + required this.startTime, + required this.endTime, + required this.breakDurationMinutes, + }); + + @override + List get props => [ + id, + shiftId, + skillId, + count, + rate, + startTime, + endTime, + breakDurationMinutes, + ]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/events/work_session.dart b/apps/packages/domain/lib/src/entities/events/work_session.dart new file mode 100644 index 00000000..319606bd --- /dev/null +++ b/apps/packages/domain/lib/src/entities/events/work_session.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a verified record of time worked. +/// +/// Derived from [Assignment] clock-in/out times, used for payroll. +class WorkSession extends Equatable { + /// Unique identifier. + final String id; + + /// The [Assignment] this session belongs to. + final String assignmentId; + + /// Verified start time. + final DateTime startTime; + + /// Verified end time. + final DateTime? endTime; + + /// Verified break duration. + final int breakDurationMinutes; + + const WorkSession({ + required this.id, + required this.assignmentId, + required this.startTime, + this.endTime, + required this.breakDurationMinutes, + }); + + @override + List get props => [id, assignmentId, startTime, endTime, breakDurationMinutes]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/financial/invoice.dart b/apps/packages/domain/lib/src/entities/financial/invoice.dart new file mode 100644 index 00000000..7775d775 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/financial/invoice.dart @@ -0,0 +1,70 @@ +import 'package:equatable/equatable.dart'; + +/// The workflow status of an [Invoice]. +enum InvoiceStatus { + /// Generated but not yet sent/finalized. + open, + + /// Client has disputed a line item. + disputed, + + /// Dispute has been handled. + resolved, + + /// Invoice accepted by client. + verified, + + /// Payment received. + paid, + + /// Payment reconciled in accounting. + reconciled, + + /// Payment not received by due date. + overdue, +} + +/// Represents a bill sent to a [Business] for services rendered. +class Invoice extends Equatable { + /// Unique identifier. + final String id; + + /// The [Event] this invoice covers. + final String eventId; + + /// The [Business] being billed. + final String businessId; + + /// Current payment/approval status. + final InvoiceStatus status; + + /// Grand total amount. + final double totalAmount; + + /// Total amount for labor costs. + final double workAmount; + + /// Total amount for addons/extras. + final double addonsAmount; + + const Invoice({ + required this.id, + required this.eventId, + required this.businessId, + required this.status, + required this.totalAmount, + required this.workAmount, + required this.addonsAmount, + }); + + @override + List get props => [ + id, + eventId, + businessId, + status, + totalAmount, + workAmount, + addonsAmount, + ]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/financial/invoice_decline.dart b/apps/packages/domain/lib/src/entities/financial/invoice_decline.dart new file mode 100644 index 00000000..17d7afc4 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/financial/invoice_decline.dart @@ -0,0 +1,26 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a reason or log for a declined [Invoice]. +class InvoiceDecline extends Equatable { + /// Unique identifier. + final String id; + + /// The [Invoice] that was declined. + final String invoiceId; + + /// Reason provided by the client. + final String reason; + + /// When the decline happened. + final DateTime declinedAt; + + const InvoiceDecline({ + required this.id, + required this.invoiceId, + required this.reason, + required this.declinedAt, + }); + + @override + List get props => [id, invoiceId, reason, declinedAt]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/financial/invoice_item.dart b/apps/packages/domain/lib/src/entities/financial/invoice_item.dart new file mode 100644 index 00000000..b290d7b1 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/financial/invoice_item.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a line item in an [Invoice]. +/// +/// Corresponds to the work done by one [Staff] member. +class InvoiceItem extends Equatable { + /// Unique identifier. + final String id; + + /// The [Invoice] this item belongs to. + final String invoiceId; + + /// The [Staff] member whose work is being billed. + final String staffId; + + /// Total billed hours. + final double workHours; + + /// Hourly rate applied. + final double rate; + + /// Total line item amount (workHours * rate). + final double amount; + + const InvoiceItem({ + required this.id, + required this.invoiceId, + required this.staffId, + required this.workHours, + required this.rate, + required this.amount, + }); + + @override + List get props => [id, invoiceId, staffId, workHours, rate, amount]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/financial/staff_payment.dart b/apps/packages/domain/lib/src/entities/financial/staff_payment.dart new file mode 100644 index 00000000..ed8cd75c --- /dev/null +++ b/apps/packages/domain/lib/src/entities/financial/staff_payment.dart @@ -0,0 +1,49 @@ +import 'package:equatable/equatable.dart'; + +/// Status of a staff payout. +enum PaymentStatus { + /// Payout calculated but not processed. + pending, + + /// Submitted to banking provider. + processing, + + /// Successfully transferred to staff. + paid, + + /// Transfer failed. + failed, +} + +/// Represents a payout to a [Staff] member for a completed [Assignment]. +class StaffPayment extends Equatable { + /// Unique identifier. + final String id; + + /// The recipient [Staff]. + final String staffId; + + /// The [Assignment] being paid for. + final String assignmentId; + + /// Amount to be paid. + final double amount; + + /// Processing status. + final PaymentStatus status; + + /// When the payment was successfully processed. + final DateTime? paidAt; + + const StaffPayment({ + required this.id, + required this.staffId, + required this.assignmentId, + required this.amount, + required this.status, + this.paidAt, + }); + + @override + List get props => [id, staffId, assignmentId, amount, status, paidAt]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/profile/accessibility.dart b/apps/packages/domain/lib/src/entities/profile/accessibility.dart new file mode 100644 index 00000000..22169d82 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/profile/accessibility.dart @@ -0,0 +1,20 @@ +import 'package:equatable/equatable.dart'; + +/// Represents accessibility requirements or features. +/// +/// Can apply to Staff (needs) or Events (provision). +class Accessibility extends Equatable { + /// Unique identifier. + final String id; + + /// Description (e.g. "Wheelchair Access"). + final String name; + + const Accessibility({ + required this.id, + required this.name, + }); + + @override + List get props => [id, name]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/profile/bank_account.dart b/apps/packages/domain/lib/src/entities/profile/bank_account.dart new file mode 100644 index 00000000..04b74224 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/profile/bank_account.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; + +/// Represents bank account details for payroll. +class BankAccount extends Equatable { + /// Unique identifier. + final String id; + + /// The [User] owning the account. + final String userId; + + /// Name of the bank. + final String bankName; + + /// Account number. + final String accountNumber; + + /// Name on the account. + final String accountName; + + /// Sort code (if applicable). + final String? sortCode; + + const BankAccount({ + required this.id, + required this.userId, + required this.bankName, + required this.accountNumber, + required this.accountName, + this.sortCode, + }); + + @override + List get props => [id, userId, bankName, accountNumber, accountName, sortCode]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/profile/emergency_contact.dart b/apps/packages/domain/lib/src/entities/profile/emergency_contact.dart new file mode 100644 index 00000000..99ffe704 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/profile/emergency_contact.dart @@ -0,0 +1,24 @@ +import 'package:equatable/equatable.dart'; + +/// Represents an emergency contact for a user. +/// +/// Critical for staff safety during shifts. +class EmergencyContact extends Equatable { + /// Full name of the contact. + final String name; + + /// Relationship to the user (e.g. "Spouse", "Parent"). + final String relationship; + + /// Phone number. + final String phone; + + const EmergencyContact({ + required this.name, + required this.relationship, + required this.phone, + }); + + @override + List get props => [name, relationship, phone]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/profile/schedule.dart b/apps/packages/domain/lib/src/entities/profile/schedule.dart new file mode 100644 index 00000000..40276a20 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/profile/schedule.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +/// Represents general availability schedule for a [Staff] member. +/// +/// Defines recurring availability (e.g., "Mondays 9-5"). +class Schedule extends Equatable { + /// Unique identifier. + final String id; + + /// The [Staff] member. + final String staffId; + + /// Day of the week (1 = Monday, 7 = Sunday). + final int dayOfWeek; + + /// Start time of availability. + final DateTime startTime; + + /// End time of availability. + final DateTime endTime; + + const Schedule({ + required this.id, + required this.staffId, + required this.dayOfWeek, + required this.startTime, + required this.endTime, + }); + + @override + List get props => [id, staffId, dayOfWeek, startTime, endTime]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/ratings/business_staff_preference.dart b/apps/packages/domain/lib/src/entities/ratings/business_staff_preference.dart new file mode 100644 index 00000000..1c4ea3af --- /dev/null +++ b/apps/packages/domain/lib/src/entities/ratings/business_staff_preference.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; + +/// The type of preference a business has for a staff member. +enum PreferenceType { + /// Business wants to prioritize this staff member. + favorite, + + /// Business does not want to work with this staff member. + blocked, +} + +/// Represents a business's specific preference for a staff member. +class BusinessStaffPreference extends Equatable { + /// Unique identifier. + final String id; + + /// The [Business] holding the preference. + final String businessId; + + /// The [Staff] member. + final String staffId; + + /// Whether they are a favorite or blocked. + final PreferenceType type; + + const BusinessStaffPreference({ + required this.id, + required this.businessId, + required this.staffId, + required this.type, + }); + + @override + List get props => [id, businessId, staffId, type]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/ratings/penalty_log.dart b/apps/packages/domain/lib/src/entities/ratings/penalty_log.dart new file mode 100644 index 00000000..317b6dd6 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/ratings/penalty_log.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a penalty issued to a staff member. +/// +/// Penalties are issued for no-shows, cancellations, or poor conduct. +class PenaltyLog extends Equatable { + /// Unique identifier. + final String id; + + /// The [Staff] member penalized. + final String staffId; + + /// The [Assignment] context (if applicable). + final String assignmentId; + + /// Reason for the penalty. + final String reason; + + /// Score points deducted from staff profile. + final int points; + + /// When the penalty was issued. + final DateTime issuedAt; + + const PenaltyLog({ + required this.id, + required this.staffId, + required this.assignmentId, + required this.reason, + required this.points, + required this.issuedAt, + }); + + @override + List get props => [id, staffId, assignmentId, reason, points, issuedAt]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/ratings/staff_rating.dart b/apps/packages/domain/lib/src/entities/ratings/staff_rating.dart new file mode 100644 index 00000000..635dcc0b --- /dev/null +++ b/apps/packages/domain/lib/src/entities/ratings/staff_rating.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a rating given to a staff member by a client. +class StaffRating extends Equatable { + /// Unique identifier. + final String id; + + /// The [Staff] being rated. + final String staffId; + + /// The [Event] context. + final String eventId; + + /// The [Business] leaving the rating. + final String businessId; + + /// Star rating (1-5). + final int rating; + + /// Optional feedback text. + final String? comment; + + const StaffRating({ + required this.id, + required this.staffId, + required this.eventId, + required this.businessId, + required this.rating, + this.comment, + }); + + @override + List get props => [id, staffId, eventId, businessId, rating, comment]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/skills/certificate.dart b/apps/packages/domain/lib/src/entities/skills/certificate.dart new file mode 100644 index 00000000..362832c0 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/skills/certificate.dart @@ -0,0 +1,24 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a required certificate definition. +/// +/// Examples: "Food Hygiene Level 2", "SIA Badge". +class Certificate extends Equatable { + /// Unique identifier. + final String id; + + /// Display name of the certificate. + final String name; + + /// Whether this certificate is mandatory for platform access or specific roles. + final bool isRequired; + + const Certificate({ + required this.id, + required this.name, + required this.isRequired, + }); + + @override + List get props => [id, name, isRequired]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/skills/skill.dart b/apps/packages/domain/lib/src/entities/skills/skill.dart new file mode 100644 index 00000000..a5d11320 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/skills/skill.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a job category / skill type. +/// +/// Examples: "Waiter", "Security Guard", "Bartender". +/// Linked to a [SkillCategory]. +class Skill extends Equatable { + /// Unique identifier. + final String id; + + /// The broader category (e.g. "Hospitality"). + final String categoryId; + + /// Display name of the skill. + final String name; + + /// Default hourly rate suggested for this skill. + final double basePrice; + + const Skill({ + required this.id, + required this.categoryId, + required this.name, + required this.basePrice, + }); + + @override + List get props => [id, categoryId, name, basePrice]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/skills/skill_category.dart b/apps/packages/domain/lib/src/entities/skills/skill_category.dart new file mode 100644 index 00000000..063dedb8 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/skills/skill_category.dart @@ -0,0 +1,18 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a broad category of skills (e.g. "Hospitality", "Logistics"). +class SkillCategory extends Equatable { + /// Unique identifier. + final String id; + + /// Display name. + final String name; + + const SkillCategory({ + required this.id, + required this.name, + }); + + @override + List get props => [id, name]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/skills/skill_kit.dart b/apps/packages/domain/lib/src/entities/skills/skill_kit.dart new file mode 100644 index 00000000..a92b8bd2 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/skills/skill_kit.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +/// Represents required equipment or uniform for a specific [Skill]. +/// +/// Examples: "Black Shirt" (Uniform), "Safety Boots" (Equipment). +class SkillKit extends Equatable { + /// Unique identifier. + final String id; + + /// The [Skill] this kit applies to. + final String skillId; + + /// Description of the item. + final String name; + + /// Whether the staff member MUST possess this item. + final bool isRequired; + + /// Type of kit ('uniform' or 'equipment'). + final String type; + + const SkillKit({ + required this.id, + required this.skillId, + required this.name, + required this.isRequired, + required this.type, + }); + + @override + List get props => [id, skillId, name, isRequired, type]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/skills/staff_skill.dart b/apps/packages/domain/lib/src/entities/skills/staff_skill.dart new file mode 100644 index 00000000..da54471f --- /dev/null +++ b/apps/packages/domain/lib/src/entities/skills/staff_skill.dart @@ -0,0 +1,58 @@ +import 'package:equatable/equatable.dart'; + +/// The expertise level of a staff member in a specific skill. +enum SkillLevel { + /// Entry level. + beginner, + + /// Experienced. + skilled, + + /// Expert / Managerial level. + professional, +} + +/// The verification status of a claimed skill. +enum StaffSkillStatus { + /// Claimed by staff, waiting for admin approval. + pending, + + /// Verified by admin (documents checked). + verified, + + /// Rejected by admin. + rejected, +} + +/// Represents a staff member's qualification in a specific [Skill]. +class StaffSkill extends Equatable { + /// Unique identifier. + final String id; + + /// The [Staff] member. + final String staffId; + + /// The [Skill] they possess. + final String skillId; + + /// Their expertise level. + final SkillLevel level; + + /// Years of experience. + final int experienceYears; + + /// Verification status. + final StaffSkillStatus status; + + const StaffSkill({ + required this.id, + required this.staffId, + required this.skillId, + required this.level, + required this.experienceYears, + required this.status, + }); + + @override + List get props => [id, staffId, skillId, level, experienceYears, status]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/support/addon.dart b/apps/packages/domain/lib/src/entities/support/addon.dart new file mode 100644 index 00000000..9a78353f --- /dev/null +++ b/apps/packages/domain/lib/src/entities/support/addon.dart @@ -0,0 +1,26 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a financial addon/bonus/deduction applied to an Invoice or Payment. +class Addon extends Equatable { + /// Unique identifier. + final String id; + + /// Description (e.g. "Travel Expense"). + final String name; + + /// Monetary value. + final double amount; + + /// Type ('credit' or 'debit'). + final String type; + + const Addon({ + required this.id, + required this.name, + required this.amount, + required this.type, + }); + + @override + List get props => [id, name, amount, type]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/support/media.dart b/apps/packages/domain/lib/src/entities/support/media.dart new file mode 100644 index 00000000..8b298b61 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/support/media.dart @@ -0,0 +1,24 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a media file reference. +/// +/// Used for avatars, certificates, or event photos. +class Media extends Equatable { + /// Unique identifier. + final String id; + + /// External URL to the file. + final String url; + + /// MIME type or general type (image, pdf). + final String type; + + const Media({ + required this.id, + required this.url, + required this.type, + }); + + @override + List get props => [id, url, type]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/support/tag.dart b/apps/packages/domain/lib/src/entities/support/tag.dart new file mode 100644 index 00000000..62deacaa --- /dev/null +++ b/apps/packages/domain/lib/src/entities/support/tag.dart @@ -0,0 +1,18 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a descriptive tag used for categorizing events or staff. +class Tag extends Equatable { + /// Unique identifier. + final String id; + + /// Text label. + final String label; + + const Tag({ + required this.id, + required this.label, + }); + + @override + List get props => [id, label]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/support/working_area.dart b/apps/packages/domain/lib/src/entities/support/working_area.dart new file mode 100644 index 00000000..cc044b4c --- /dev/null +++ b/apps/packages/domain/lib/src/entities/support/working_area.dart @@ -0,0 +1,30 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a geographical area where a [Staff] member is willing to work. +class WorkingArea extends Equatable { + /// Unique identifier. + final String id; + + /// Name of the area (e.g. "London Zone 1"). + final String name; + + /// Latitude of the center point. + final double centerLat; + + /// Longitude of the center point. + final double centerLng; + + /// Radius in Kilometers. + final double radiusKm; + + const WorkingArea({ + required this.id, + required this.name, + required this.centerLat, + required this.centerLng, + required this.radiusKm, + }); + + @override + List get props => [id, name, centerLat, centerLng, radiusKm]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/users/biz_member.dart b/apps/packages/domain/lib/src/entities/users/biz_member.dart new file mode 100644 index 00000000..fc7b8099 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/users/biz_member.dart @@ -0,0 +1,28 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a member of a Business. +/// +/// Grants a user access to business-level operations. +class BizMember extends Equatable { + /// Unique identifier for this membership. + final String id; + + /// The [Business] the user belongs to. + final String businessId; + + /// The [User] who is a member. + final String userId; + + /// The role within the business. + final String role; + + const BizMember({ + required this.id, + required this.businessId, + required this.userId, + required this.role, + }); + + @override + List get props => [id, businessId, userId, role]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/users/hub_member.dart b/apps/packages/domain/lib/src/entities/users/hub_member.dart new file mode 100644 index 00000000..0ef66a18 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/users/hub_member.dart @@ -0,0 +1,28 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a member of a Hub. +/// +/// Grants a user access to specific [Hub] operations, distinct from [BizMember]. +class HubMember extends Equatable { + /// Unique identifier for this membership. + final String id; + + /// The [Hub] the user belongs to. + final String hubId; + + /// The [User] who is a member. + final String userId; + + /// The role within the hub. + final String role; + + const HubMember({ + required this.id, + required this.hubId, + required this.userId, + required this.role, + }); + + @override + List get props => [id, hubId, userId, role]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/users/membership.dart b/apps/packages/domain/lib/src/entities/users/membership.dart new file mode 100644 index 00000000..be5a0587 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/users/membership.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a polymorphic membership to an organization unit. +/// +/// Allows a [User] to be a member of either a [Business] or a [Hub]. +class Membership extends Equatable { + /// Unique identifier for the membership record. + final String id; + + /// The [User] holding this membership. + final String userId; + + /// The ID of the organization unit (Business or Hub). + final String memberableId; + + /// The type of the organization unit ('business' or 'hub'). + final String memberableType; + + /// The role within that organization (e.g., 'manager', 'viewer'). + final String role; + + const Membership({ + required this.id, + required this.userId, + required this.memberableId, + required this.memberableType, + required this.role, + }); + + @override + List get props => [id, userId, memberableId, memberableType, role]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/users/staff.dart b/apps/packages/domain/lib/src/entities/users/staff.dart new file mode 100644 index 00000000..29f417cb --- /dev/null +++ b/apps/packages/domain/lib/src/entities/users/staff.dart @@ -0,0 +1,83 @@ +import 'package:equatable/equatable.dart'; + +/// The lifecycle status of a [Staff] account. +enum StaffStatus { + /// Account created but profile not started. + registered, + + /// Profile submitted and awaiting verification. + pending, + + /// Profile information filled but not submitted for verification. + completedProfile, + + /// Profile verified by admin. + verified, + + /// Staff is currently active and eligible for work. + active, + + /// Account is temporarily suspended. + blocked, + + /// Account is permanently inactive. + inactive, +} + +/// Represents a worker profile. +/// +/// Contains all personal and professional details of a staff member. +/// Linked to a [User] via [authProviderId]. +class Staff extends Equatable { + /// Unique identifier for the staff profile. + final String id; + + /// Link to the [User] authentication record. + final String authProviderId; + + /// Full display name. + final String name; + + /// Contact email. + final String email; + + /// Contact phone number. + final String? phone; + + /// Current workflow status of the staff member. + final StaffStatus status; + + /// Physical address string. + final String? address; + + /// URL to the avatar image. + final String? avatar; + + /// URL to a verified live photo for identity verification. + final String? livePhoto; + + const Staff({ + required this.id, + required this.authProviderId, + required this.name, + required this.email, + this.phone, + required this.status, + this.address, + this.avatar, + this.livePhoto, + }); + + @override + List get props => [ + id, + authProviderId, + name, + email, + phone, + status, + address, + avatar, + livePhoto, + ]; +} \ No newline at end of file diff --git a/apps/packages/domain/lib/src/entities/users/user.dart b/apps/packages/domain/lib/src/entities/users/user.dart new file mode 100644 index 00000000..bc1b3e11 --- /dev/null +++ b/apps/packages/domain/lib/src/entities/users/user.dart @@ -0,0 +1,30 @@ +import 'package:equatable/equatable.dart'; + +/// Represents a base authenticated user in the KROW platform. +/// +/// This entity corresponds to the Firebase Auth user record and acts as the +/// linkage between the authentication system and the specific [Staff] or Client profiles. +class User extends Equatable { + /// The unique identifier from the authentication provider (e.g., Firebase UID). + final String id; + + /// The user's email address. + final String email; + + /// The user's phone number, if available. + final String? phone; + + /// The primary role of the user (e.g., 'staff', 'client_admin'). + /// This determines the initial routing and permissions. + final String role; + + const User({ + required this.id, + required this.email, + this.phone, + required this.role, + }); + + @override + List get props => [id, email, phone, role]; +} \ No newline at end of file diff --git a/apps/packages/domain/pubspec.yaml b/apps/packages/domain/pubspec.yaml new file mode 100644 index 00000000..8d6247e0 --- /dev/null +++ b/apps/packages/domain/pubspec.yaml @@ -0,0 +1,11 @@ +name: krow_domain +description: Domain entities and business logic. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + +dependencies: + equatable: ^2.0.8 diff --git a/apps/packages/features/.gitkeep b/apps/packages/features/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/packages/features/client/authentication/lib/client_authentication.dart b/apps/packages/features/client/authentication/lib/client_authentication.dart new file mode 100644 index 00000000..268a1dc7 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/client_authentication.dart @@ -0,0 +1,63 @@ +library client_authentication; + +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_data_connect/krow_data_connect.dart'; +import 'src/data/repositories_impl/auth_repository_impl.dart'; +import 'src/domain/repositories/auth_repository_interface.dart'; +import 'src/domain/usecases/sign_in_with_email_use_case.dart'; +import 'src/domain/usecases/sign_in_with_social_use_case.dart'; +import 'src/domain/usecases/sign_out_use_case.dart'; +import 'src/domain/usecases/sign_up_with_email_use_case.dart'; +import 'src/presentation/blocs/client_auth_bloc.dart'; +import 'src/presentation/pages/client_get_started_page.dart'; +import 'src/presentation/pages/client_sign_in_page.dart'; +import 'src/presentation/pages/client_sign_up_page.dart'; + +export 'src/presentation/pages/client_get_started_page.dart'; +export 'src/presentation/pages/client_sign_in_page.dart'; +export 'src/presentation/pages/client_sign_up_page.dart'; +export 'src/presentation/navigation/client_auth_navigator.dart'; +export 'package:core_localization/core_localization.dart'; + +/// A [Module] for the client authentication feature. +class ClientAuthenticationModule extends Module { + @override + List get imports => [DataConnectModule()]; + + @override + void binds(Injector i) { + // Repositories + i.addLazySingleton( + () => AuthRepositoryImpl(dataSource: i.get()), + ); + + // UseCases + i.addLazySingleton( + () => SignInWithEmailUseCase(i.get()), + ); + i.addLazySingleton( + () => SignUpWithEmailUseCase(i.get()), + ); + i.addLazySingleton( + () => SignInWithSocialUseCase(i.get()), + ); + i.addLazySingleton(() => SignOutUseCase(i.get())); + + // BLoCs + i.addLazySingleton( + () => ClientAuthBloc( + signInWithEmail: i.get(), + signUpWithEmail: i.get(), + signInWithSocial: i.get(), + signOut: i.get(), + ), + ); + } + + @override + void routes(r) { + r.child('/', child: (_) => const ClientGetStartedPage()); + r.child('/client-sign-in', child: (_) => const ClientSignInPage()); + r.child('/client-sign-up', child: (_) => const ClientSignUpPage()); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart new file mode 100644 index 00000000..176f6a64 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -0,0 +1,43 @@ +import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:krow_domain/krow_domain.dart'; + +import '../../domain/repositories/auth_repository_interface.dart'; + +/// Production-ready implementation of the [AuthRepositoryInterface]. +/// +/// This implementation integrates with the [krow_data_connect] package to provide +/// authentication services. It delegates actual data operations to the +/// [AuthRepositoryMock] (or eventually the real implementation) injected from the app layer. +class AuthRepositoryImpl implements AuthRepositoryInterface { + /// The data source used for authentication operations. + final AuthRepositoryMock _dataSource; + + /// Creates an [AuthRepositoryImpl] with the injected [dataSource] dependency. + AuthRepositoryImpl({required AuthRepositoryMock dataSource}) + : _dataSource = dataSource; + + @override + Future signInWithEmail({ + required String email, + required String password, + }) { + return _dataSource.signInWithEmail(email, password); + } + + @override + Future signUpWithEmail({ + required String companyName, + required String email, + required String password, + }) { + return _dataSource.signUpWithEmail(email, password, companyName); + } + + @override + Future signInWithSocial({required String provider}) { + return _dataSource.signInWithSocial(provider); + } + + @override + Future signOut() => _dataSource.signOut(); +} diff --git a/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_in_with_email_arguments.dart b/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_in_with_email_arguments.dart new file mode 100644 index 00000000..4c3f15af --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_in_with_email_arguments.dart @@ -0,0 +1,15 @@ +import 'package:krow_core/core.dart'; + +/// Arguments for the [SignInWithEmailUseCase]. +class SignInWithEmailArguments extends UseCaseArgument { + /// The user's email address. + final String email; + + /// The user's password. + final String password; + + const SignInWithEmailArguments({required this.email, required this.password}); + + @override + List get props => [email, password]; +} diff --git a/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_in_with_social_arguments.dart b/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_in_with_social_arguments.dart new file mode 100644 index 00000000..f658a5ab --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_in_with_social_arguments.dart @@ -0,0 +1,12 @@ +import 'package:krow_core/core.dart'; + +/// Arguments for the [SignInWithSocialUseCase]. +class SignInWithSocialArguments extends UseCaseArgument { + /// The social provider name (e.g. 'google' or 'apple'). + final String provider; + + const SignInWithSocialArguments({required this.provider}); + + @override + List get props => [provider]; +} diff --git a/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_up_with_email_arguments.dart b/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_up_with_email_arguments.dart new file mode 100644 index 00000000..f282d657 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/domain/arguments/sign_up_with_email_arguments.dart @@ -0,0 +1,22 @@ +import 'package:krow_core/core.dart'; + +/// Arguments for the [SignUpWithEmailUseCase]. +class SignUpWithEmailArguments extends UseCaseArgument { + /// The name of the company. + final String companyName; + + /// The user's email address. + final String email; + + /// The user's password. + final String password; + + const SignUpWithEmailArguments({ + required this.companyName, + required this.email, + required this.password, + }); + + @override + List get props => [companyName, email, password]; +} diff --git a/apps/packages/features/client/authentication/lib/src/domain/repositories/auth_repository_interface.dart b/apps/packages/features/client/authentication/lib/src/domain/repositories/auth_repository_interface.dart new file mode 100644 index 00000000..21a1830c --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/domain/repositories/auth_repository_interface.dart @@ -0,0 +1,37 @@ +import 'package:krow_domain/krow_domain.dart'; + +/// Interface for the Client Authentication Repository. +/// +/// This abstraction defines the core authentication operations required for +/// the client application, allowing the presentation layer to work with +/// different data sources (e.g., Supabase, Firebase, or Mock) without +/// depending on specific implementations. +abstract class AuthRepositoryInterface { + /// Signs in an existing client user using their email and password. + /// + /// Returns a [User] object upon successful authentication. + /// Throws an exception if authentication fails. + Future signInWithEmail({ + required String email, + required String password, + }); + + /// Registers a new client user with their business details. + /// + /// Takes [companyName], [email], and [password] to create a new account. + /// Returns the newly created [User]. + Future signUpWithEmail({ + required String companyName, + required String email, + required String password, + }); + + /// Authenticates using an OAuth provider. + /// + /// [provider] can be 'google' or 'apple'. + /// Returns a [User] upon successful social login. + Future signInWithSocial({required String provider}); + + /// Terminates the current user session and clears authentication tokens. + Future signOut(); +} diff --git a/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_in_with_email_use_case.dart b/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_in_with_email_use_case.dart new file mode 100644 index 00000000..a10358f7 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_in_with_email_use_case.dart @@ -0,0 +1,24 @@ +import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../arguments/sign_in_with_email_arguments.dart'; +import '../repositories/auth_repository_interface.dart'; + +/// Use case for signing in a client using email and password. +/// +/// This use case encapsulates the logic for authenticating an existing user +/// via email/password credentials. +class SignInWithEmailUseCase + implements UseCase { + final AuthRepositoryInterface _repository; + + const SignInWithEmailUseCase(this._repository); + + /// Executes the sign-in operation. + @override + Future call(SignInWithEmailArguments params) { + return _repository.signInWithEmail( + email: params.email, + password: params.password, + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_in_with_social_use_case.dart b/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_in_with_social_use_case.dart new file mode 100644 index 00000000..dcbeab4a --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_in_with_social_use_case.dart @@ -0,0 +1,18 @@ +import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../arguments/sign_in_with_social_arguments.dart'; +import '../repositories/auth_repository_interface.dart'; + +/// Use case for signing in a client via social providers (Google/Apple). +class SignInWithSocialUseCase + implements UseCase { + final AuthRepositoryInterface _repository; + + const SignInWithSocialUseCase(this._repository); + + /// Executes the social sign-in operation. + @override + Future call(SignInWithSocialArguments params) { + return _repository.signInWithSocial(provider: params.provider); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_out_use_case.dart b/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_out_use_case.dart new file mode 100644 index 00000000..707cf75c --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_out_use_case.dart @@ -0,0 +1,18 @@ +import 'package:krow_core/core.dart'; +import '../repositories/auth_repository_interface.dart'; + +/// Use case for signing out the current client user. +/// +/// This use case handles the termination of the user's session and +/// clearing of any local authentication tokens. +class SignOutUseCase implements NoInputUseCase { + final AuthRepositoryInterface _repository; + + const SignOutUseCase(this._repository); + + /// Executes the sign-out operation. + @override + Future call() { + return _repository.signOut(); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_up_with_email_use_case.dart b/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_up_with_email_use_case.dart new file mode 100644 index 00000000..60c53fde --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/domain/usecases/sign_up_with_email_use_case.dart @@ -0,0 +1,25 @@ +import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../arguments/sign_up_with_email_arguments.dart'; +import '../repositories/auth_repository_interface.dart'; + +/// Use case for registering a new client user. +/// +/// This use case handles the creation of a new client account using +/// email, password, and company details. +class SignUpWithEmailUseCase + implements UseCase { + final AuthRepositoryInterface _repository; + + const SignUpWithEmailUseCase(this._repository); + + /// Executes the sign-up operation. + @override + Future call(SignUpWithEmailArguments params) { + return _repository.signUpWithEmail( + companyName: params.companyName, + email: params.email, + password: params.password, + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_bloc.dart b/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_bloc.dart new file mode 100644 index 00000000..0e241ea2 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_bloc.dart @@ -0,0 +1,131 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../domain/arguments/sign_in_with_email_arguments.dart'; +import '../../domain/arguments/sign_in_with_social_arguments.dart'; +import '../../domain/arguments/sign_up_with_email_arguments.dart'; +import '../../domain/usecases/sign_in_with_email_use_case.dart'; +import '../../domain/usecases/sign_up_with_email_use_case.dart'; +import '../../domain/usecases/sign_in_with_social_use_case.dart'; +import '../../domain/usecases/sign_out_use_case.dart'; +import 'client_auth_event.dart'; +import 'client_auth_state.dart'; + +/// Business Logic Component for Client Authentication. +/// +/// This BLoC manages the state transitions for the authentication flow in +/// the client application. It handles user inputs (events), interacts with +/// domain use cases, and emits corresponding [ClientAuthState]s. +/// +/// Use this BLoC to handle: +/// * Email/Password Sign In +/// * Business Account Registration +/// * Social Authentication +/// * Session Termination +class ClientAuthBloc extends Bloc { + final SignInWithEmailUseCase _signInWithEmail; + final SignUpWithEmailUseCase _signUpWithEmail; + final SignInWithSocialUseCase _signInWithSocial; + final SignOutUseCase _signOut; + + /// Initializes the BLoC with the required use cases and initial state. + ClientAuthBloc({ + required SignInWithEmailUseCase signInWithEmail, + required SignUpWithEmailUseCase signUpWithEmail, + required SignInWithSocialUseCase signInWithSocial, + required SignOutUseCase signOut, + }) : _signInWithEmail = signInWithEmail, + _signUpWithEmail = signUpWithEmail, + _signInWithSocial = signInWithSocial, + _signOut = signOut, + super(const ClientAuthState()) { + on(_onSignInRequested); + on(_onSignUpRequested); + on(_onSocialSignInRequested); + on(_onSignOutRequested); + } + + /// Handles the [ClientSignInRequested] event. + Future _onSignInRequested( + ClientSignInRequested event, + Emitter emit, + ) async { + emit(state.copyWith(status: ClientAuthStatus.loading)); + try { + final user = await _signInWithEmail( + SignInWithEmailArguments(email: event.email, password: event.password), + ); + emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user)); + } catch (e) { + emit( + state.copyWith( + status: ClientAuthStatus.error, + errorMessage: e.toString(), + ), + ); + } + } + + /// Handles the [ClientSignUpRequested] event. + Future _onSignUpRequested( + ClientSignUpRequested event, + Emitter emit, + ) async { + emit(state.copyWith(status: ClientAuthStatus.loading)); + try { + final user = await _signUpWithEmail( + SignUpWithEmailArguments( + companyName: event.companyName, + email: event.email, + password: event.password, + ), + ); + emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user)); + } catch (e) { + emit( + state.copyWith( + status: ClientAuthStatus.error, + errorMessage: e.toString(), + ), + ); + } + } + + /// Handles the [ClientSocialSignInRequested] event. + Future _onSocialSignInRequested( + ClientSocialSignInRequested event, + Emitter emit, + ) async { + emit(state.copyWith(status: ClientAuthStatus.loading)); + try { + final user = await _signInWithSocial( + SignInWithSocialArguments(provider: event.provider), + ); + emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user)); + } catch (e) { + emit( + state.copyWith( + status: ClientAuthStatus.error, + errorMessage: e.toString(), + ), + ); + } + } + + /// Handles the [ClientSignOutRequested] event. + Future _onSignOutRequested( + ClientSignOutRequested event, + Emitter emit, + ) async { + emit(state.copyWith(status: ClientAuthStatus.loading)); + try { + await _signOut(); + emit(state.copyWith(status: ClientAuthStatus.signedOut, user: null)); + } catch (e) { + emit( + state.copyWith( + status: ClientAuthStatus.error, + errorMessage: e.toString(), + ), + ); + } + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_event.dart b/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_event.dart new file mode 100644 index 00000000..1e8a6c92 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_event.dart @@ -0,0 +1,51 @@ +import 'package:equatable/equatable.dart'; + +/// Base class for all authentication events in the client feature. +abstract class ClientAuthEvent extends Equatable { + const ClientAuthEvent(); + + @override + List get props => []; +} + +/// Event dispatched when a user attempts to sign in with email and password. +class ClientSignInRequested extends ClientAuthEvent { + final String email; + final String password; + + const ClientSignInRequested({required this.email, required this.password}); + + @override + List get props => [email, password]; +} + +/// Event dispatched when a user attempts to create a new business account. +class ClientSignUpRequested extends ClientAuthEvent { + final String companyName; + final String email; + final String password; + + const ClientSignUpRequested({ + required this.companyName, + required this.email, + required this.password, + }); + + @override + List get props => [companyName, email, password]; +} + +/// Event dispatched for third-party authentication (Google/Apple). +class ClientSocialSignInRequested extends ClientAuthEvent { + final String provider; + + const ClientSocialSignInRequested({required this.provider}); + + @override + List get props => [provider]; +} + +/// Event dispatched when the user requests to terminate their session. +class ClientSignOutRequested extends ClientAuthEvent { + const ClientSignOutRequested(); +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_state.dart b/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_state.dart new file mode 100644 index 00000000..0c42096b --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/blocs/client_auth_state.dart @@ -0,0 +1,54 @@ +import 'package:equatable/equatable.dart'; +import 'package:krow_domain/krow_domain.dart'; + +/// Enum representing the various states of the authentication process. +enum ClientAuthStatus { + /// Initial state before any action is taken. + initial, + + /// An authentication operation is in progress. + loading, + + /// The user has successfully authenticated. + authenticated, + + /// The user has successfully signed out. + signedOut, + + /// An error occurred during authentication. + error, +} + +/// Represents the state of the client authentication flow. +class ClientAuthState extends Equatable { + /// Current status of the authentication process. + final ClientAuthStatus status; + + /// The authenticated user (if status is [ClientAuthStatus.authenticated]). + final User? user; + + /// Optional error message when status is [ClientAuthStatus.error]. + final String? errorMessage; + + const ClientAuthState({ + this.status = ClientAuthStatus.initial, + this.user, + this.errorMessage, + }); + + /// Creates a copy of this state with the given fields replaced by the new values. + ClientAuthState copyWith({ + ClientAuthStatus? status, + User? user, + String? errorMessage, + }) { + return ClientAuthState( + status: status ?? this.status, + user: user ?? this.user, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [status, user, errorMessage]; +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/navigation/client_auth_navigator.dart b/apps/packages/features/client/authentication/lib/src/presentation/navigation/client_auth_navigator.dart new file mode 100644 index 00000000..a1cb7365 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/navigation/client_auth_navigator.dart @@ -0,0 +1,25 @@ +import 'package:flutter_modular/flutter_modular.dart'; + +/// Typed navigation for the Client Authentication feature. +/// +/// This extension on [IModularNavigator] provides named methods for +/// navigating between authentication pages, reducing magic strings and +/// improving maintainability. +extension ClientAuthNavigator on IModularNavigator { + /// Navigates to the sign in page using a push named route. + void pushClientSignIn() { + pushNamed('/client-sign-in'); + } + + /// Navigates to the sign up page using a push named route. + void pushClientSignUp() { + pushNamed('/client-sign-up'); + } + + /// Navigates to the main client home dashboard. + /// + /// Uses absolute path navigation to reset the navigation stack if necessary. + void navigateClientHome() { + navigate('/client-home'); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/pages/client_get_started_page.dart b/apps/packages/features/client/authentication/lib/src/presentation/pages/client_get_started_page.dart new file mode 100644 index 00000000..f673e78d --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/pages/client_get_started_page.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import '../navigation/client_auth_navigator.dart'; + +class ClientGetStartedPage extends StatelessWidget { + const ClientGetStartedPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + // Background Illustration/Visuals from prototype + Positioned( + top: -100, + right: -100, + child: Container( + width: 400, + height: 400, + decoration: BoxDecoration( + color: UiColors.secondary.withAlpha(50), + shape: BoxShape.circle, + ), + ), + ), + + SafeArea( + child: Column( + children: [ + const SizedBox(height: UiConstants.space10), + // Logo + Center( + child: Image.asset( + UiImageAssets.logoBlue, + height: 40, + fit: BoxFit.contain, + ), + ), + + const Spacer(), + + // Content Cards Area (Keeping prototype layout) + Container( + height: 300, + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space6, + ), + child: Stack( + children: [ + // Representative cards from prototype + Positioned( + top: 20, + left: 0, + right: 20, + child: _ShiftOrderCard(), + ), + Positioned( + bottom: 40, + right: 0, + left: 40, + child: _WorkerProfileCard(), + ), + Positioned(top: 60, right: 10, child: _CalendarCard()), + ], + ), + ), + + const Spacer(), + + // Bottom Content + Padding( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space6, + vertical: UiConstants.space10, + ), + child: Column( + children: [ + Text( + t.client_authentication.get_started_page.title, + textAlign: TextAlign.center, + style: UiTypography.displayM, + ), + const SizedBox(height: UiConstants.space3), + Text( + t.client_authentication.get_started_page.subtitle, + textAlign: TextAlign.center, + style: UiTypography.body2r.textSecondary, + ), + const SizedBox(height: UiConstants.space8), + + // Sign In Button + UiButton.primary( + text: t + .client_authentication + .get_started_page + .sign_in_button, + onPressed: () => Modular.to.pushClientSignIn(), + ), + + const SizedBox(height: UiConstants.space3), + + // Create Account Button + UiButton.secondary( + text: t + .client_authentication + .get_started_page + .create_account_button, + onPressed: () => Modular.to.pushClientSignUp(), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +// Internal Prototype Widgets Updated with Design System Primitives +class _ShiftOrderCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(UiConstants.space3), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: UiConstants.radiusLg, + boxShadow: [ + BoxShadow( + color: UiColors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(UiConstants.space1), + decoration: BoxDecoration( + color: UiColors.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: const Icon( + UiIcons.briefcase, + size: 14, + color: UiColors.primary, + ), + ), + const SizedBox(width: UiConstants.space2), + Text('Shift Order #824', style: UiTypography.footnote1b), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: UiColors.tagPending, + borderRadius: UiConstants.radiusFull, + ), + child: Text( + 'Pending', + style: UiTypography.footnote2m.copyWith( + color: UiColors.textWarning, + ), + ), + ), + ], + ), + const SizedBox(height: UiConstants.space2), + Text( + 'Event Staffing - Hilton Hotel', + style: UiTypography.footnote2r.textSecondary, + ), + ], + ), + ); + } +} + +class _WorkerProfileCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(UiConstants.space3), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: UiConstants.radiusLg, + boxShadow: [ + BoxShadow( + color: UiColors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + CircleAvatar( + radius: 16, + backgroundColor: UiColors.primary.withOpacity(0.1), + child: const Icon(UiIcons.user, size: 16, color: UiColors.primary), + ), + const SizedBox(width: UiConstants.space2), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text('Alex Thompson', style: UiTypography.footnote1b), + Text( + 'Professional Waiter • 4.9★', + style: UiTypography.footnote2r.textSecondary, + ), + ], + ), + ], + ), + ); + } +} + +class _CalendarCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(UiConstants.space2), + decoration: BoxDecoration( + color: UiColors.accent, + borderRadius: UiConstants.radiusMd, + boxShadow: [ + BoxShadow( + color: UiColors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(4, 4), + ), + ], + ), + child: const Icon( + UiIcons.calendar, + size: 20, + color: UiColors.accentForeground, + ), + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/pages/client_sign_in_page.dart b/apps/packages/features/client/authentication/lib/src/presentation/pages/client_sign_in_page.dart new file mode 100644 index 00000000..42dc2e46 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/pages/client_sign_in_page.dart @@ -0,0 +1,147 @@ +import 'package:client_authentication/src/presentation/widgets/common/section_titles.dart'; +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../blocs/client_auth_bloc.dart'; +import '../blocs/client_auth_event.dart'; +import '../blocs/client_auth_state.dart'; +import '../navigation/client_auth_navigator.dart'; +import '../widgets/client_sign_in_page/client_sign_in_form.dart'; +import '../widgets/common/auth_divider.dart'; +import '../widgets/common/auth_social_button.dart'; + +/// Page for client users to sign in to their account. +/// +/// This page provides email/password authentication as well as social sign-in +/// options via Apple and Google. It matches the design system standards +/// for client-facing authentication flows. +class ClientSignInPage extends StatelessWidget { + /// Creates a [ClientSignInPage]. + const ClientSignInPage({super.key}); + + /// Dispatches the sign in event to the BLoC. + void _handleSignIn( + BuildContext context, { + required String email, + required String password, + }) { + BlocProvider.of( + context, + ).add(ClientSignInRequested(email: email, password: password)); + } + + /// Dispatches the social sign in event to the BLoC. + void _handleSocialSignIn(BuildContext context, {required String provider}) { + BlocProvider.of( + context, + ).add(ClientSocialSignInRequested(provider: provider)); + } + + @override + Widget build(BuildContext context) { + final i18n = t.client_authentication.sign_in_page; + final authBloc = Modular.get(); + + return BlocProvider.value( + value: authBloc, + child: BlocConsumer( + listener: (context, state) { + if (state.status == ClientAuthStatus.authenticated) { + Modular.to.navigateClientHome(); + } else if (state.status == ClientAuthStatus.error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage ?? 'Authentication Error'), + ), + ); + } + }, + builder: (context, state) { + final isLoading = state.status == ClientAuthStatus.loading; + + return Scaffold( + appBar: const UiAppBar(showBackButton: true), + body: SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.fromLTRB( + UiConstants.space6, + UiConstants.space8, + UiConstants.space6, + 0, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SectionTitle(title: i18n.title, subtitle: i18n.subtitle), + const SizedBox(height: UiConstants.space8), + + // Sign In Form + ClientSignInForm( + isLoading: isLoading, + onSignIn: ({required email, required password}) => + _handleSignIn( + context, + email: email, + password: password, + ), + ), + + const SizedBox(height: UiConstants.space6), + + // Divider + AuthDivider(text: i18n.or_divider), + + const SizedBox(height: UiConstants.space6), + + // Social Buttons + AuthSocialButton( + text: i18n.social_apple, + icon: UiIcons.apple, + onPressed: () => + _handleSocialSignIn(context, provider: 'apple'), + ), + const SizedBox(height: UiConstants.space3), + AuthSocialButton( + text: i18n.social_google, + icon: UiIcons.google, + onPressed: () => + _handleSocialSignIn(context, provider: 'google'), + ), + + const SizedBox(height: UiConstants.space8), + + // Sign Up Link + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + i18n.no_account, + style: UiTypography.body2r.textSecondary, + ), + const SizedBox(width: UiConstants.space1), + GestureDetector( + onTap: () => Modular.to.pushClientSignUp(), + child: Text( + i18n.sign_up_link, + style: UiTypography.body2m.textLink, + ), + ), + ], + ), + const SizedBox(height: UiConstants.space10), + ], + ), + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/pages/client_sign_up_page.dart b/apps/packages/features/client/authentication/lib/src/presentation/pages/client_sign_up_page.dart new file mode 100644 index 00000000..538b3106 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/pages/client_sign_up_page.dart @@ -0,0 +1,158 @@ +import 'package:client_authentication/src/presentation/widgets/common/section_titles.dart'; +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../blocs/client_auth_bloc.dart'; +import '../blocs/client_auth_event.dart'; +import '../blocs/client_auth_state.dart'; +import '../navigation/client_auth_navigator.dart'; +import '../widgets/client_sign_up_page/client_sign_up_form.dart'; +import '../widgets/common/auth_divider.dart'; +import '../widgets/common/auth_social_button.dart'; + +/// Page for client users to sign up for a new account. +/// +/// This page collects company details, email, and password, and offers +/// social sign-up options. It adheres to the design system standards. +class ClientSignUpPage extends StatelessWidget { + /// Creates a [ClientSignUpPage]. + const ClientSignUpPage({super.key}); + + /// Validates inputs and dispatches the sign up event. + void _handleSignUp( + BuildContext context, { + required String companyName, + required String email, + required String password, + }) { + BlocProvider.of(context).add( + ClientSignUpRequested( + companyName: companyName, + email: email, + password: password, + ), + ); + } + + /// Dispatches the social sign up event. + void _handleSocialSignUp(BuildContext context, {required String provider}) { + BlocProvider.of( + context, + ).add(ClientSocialSignInRequested(provider: provider)); + } + + @override + Widget build(BuildContext context) { + final i18n = t.client_authentication.sign_up_page; + final authBloc = Modular.get(); + + return BlocProvider.value( + value: authBloc, + child: BlocConsumer( + listener: (context, state) { + if (state.status == ClientAuthStatus.authenticated) { + Modular.to.navigateClientHome(); + } else if (state.status == ClientAuthStatus.error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage ?? 'Authentication Error'), + ), + ); + } + }, + builder: (context, state) { + final isLoading = state.status == ClientAuthStatus.loading; + + return Scaffold( + appBar: const UiAppBar(showBackButton: true), + body: SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.fromLTRB( + UiConstants.space6, + UiConstants.space8, + UiConstants.space6, + 0, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SectionTitle(title: i18n.title, subtitle: i18n.subtitle), + const SizedBox(height: UiConstants.space8), + + // Sign Up Form + ClientSignUpForm( + isLoading: isLoading, + onSignUp: + ({ + required companyName, + required email, + required password, + }) => _handleSignUp( + context, + companyName: companyName, + email: email, + password: password, + ), + ), + + const SizedBox(height: UiConstants.space6), + + // Divider + // Divider + AuthDivider(text: i18n.or_divider), + + const SizedBox(height: UiConstants.space6), + + // Social Buttons + // Social Buttons + AuthSocialButton( + text: i18n.social_apple, + icon: UiIcons.apple, + onPressed: () => + _handleSocialSignUp(context, provider: 'apple'), + ), + const SizedBox(height: UiConstants.space3), + AuthSocialButton( + text: i18n.social_google, + icon: UiIcons.google, + onPressed: () => + _handleSocialSignUp(context, provider: 'google'), + ), + + const SizedBox(height: UiConstants.space8), + + // Sign In Link + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + i18n.has_account, + style: UiTypography.body2r.textSecondary, + ), + const SizedBox(width: UiConstants.space1), + GestureDetector( + onTap: () => Modular.to.pushClientSignIn(), + child: Text( + i18n.sign_in_link, + style: UiTypography.body2m.textLink, + ), + ), + ], + ), + const SizedBox(height: UiConstants.space10), + ], + ), + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/widgets/client_sign_in_page/client_sign_in_form.dart b/apps/packages/features/client/authentication/lib/src/presentation/widgets/client_sign_in_page/client_sign_in_form.dart new file mode 100644 index 00000000..c1489ae6 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/widgets/client_sign_in_page/client_sign_in_form.dart @@ -0,0 +1,113 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A form widget for client sign-in. +/// +/// This widget handles user input for email and password and delegates +/// authentication events to the parent via callbacks. +class ClientSignInForm extends StatefulWidget { + /// Callback when the sign-in button is pressed. + final void Function({required String email, required String password}) + onSignIn; + + /// Whether the authentication is currently loading. + final bool isLoading; + + /// Creates a [ClientSignInForm]. + const ClientSignInForm({ + super.key, + required this.onSignIn, + this.isLoading = false, + }); + + @override + State createState() => _ClientSignInFormState(); +} + +class _ClientSignInFormState extends State { + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + bool _obscurePassword = true; + + @override + void dispose() { + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + void _handleSubmit() { + widget.onSignIn( + email: _emailController.text, + password: _passwordController.text, + ); + } + + @override + Widget build(BuildContext context) { + final i18n = t.client_authentication.sign_in_page; + + return Column( + children: [ + // Email Field + UiTextField( + label: i18n.email_label, + hintText: i18n.email_hint, + controller: _emailController, + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: UiConstants.space5), + + // Password Field + UiTextField( + label: i18n.password_label, + hintText: i18n.password_hint, + controller: _passwordController, + obscureText: _obscurePassword, + suffix: IconButton( + icon: Icon( + _obscurePassword ? UiIcons.eyeOff : UiIcons.eye, + color: UiColors.iconSecondary, + size: 20, + ), + onPressed: () => + setState(() => _obscurePassword = !_obscurePassword), + ), + ), + + const SizedBox(height: UiConstants.space2), + + // Forgot Password + Align( + alignment: Alignment.centerLeft, + child: GestureDetector( + onTap: () {}, + child: Text( + i18n.forgot_password, + style: UiTypography.body2r.textLink, + ), + ), + ), + + const SizedBox(height: UiConstants.space8), + + // Sign In Button + UiButton.primary( + text: widget.isLoading ? null : i18n.sign_in_button, + onPressed: widget.isLoading ? null : _handleSubmit, + child: widget.isLoading + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator( + color: UiColors.white, + strokeWidth: 2, + ), + ) + : null, + ), + ], + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/widgets/client_sign_up_page/client_sign_up_form.dart b/apps/packages/features/client/authentication/lib/src/presentation/widgets/client_sign_up_page/client_sign_up_form.dart new file mode 100644 index 00000000..cd5e4885 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/widgets/client_sign_up_page/client_sign_up_form.dart @@ -0,0 +1,137 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A form widget for client sign-up. +/// +/// This widget handles user input for company name, email, and password, +/// and delegates registration events to the parent via callbacks. +class ClientSignUpForm extends StatefulWidget { + /// Callback when the sign-up button is pressed. + final void Function({ + required String companyName, + required String email, + required String password, + }) + onSignUp; + + /// Whether the authentication is currently loading. + final bool isLoading; + + /// Creates a [ClientSignUpForm]. + const ClientSignUpForm({ + super.key, + required this.onSignUp, + this.isLoading = false, + }); + + @override + State createState() => _ClientSignUpFormState(); +} + +class _ClientSignUpFormState extends State { + final _companyController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + final _confirmPasswordController = TextEditingController(); + bool _obscurePassword = true; + + @override + void dispose() { + _companyController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + _confirmPasswordController.dispose(); + super.dispose(); + } + + void _handleSubmit() { + if (_passwordController.text != _confirmPasswordController.text) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Passwords do not match'))); + return; + } + + widget.onSignUp( + companyName: _companyController.text, + email: _emailController.text, + password: _passwordController.text, + ); + } + + @override + Widget build(BuildContext context) { + final i18n = t.client_authentication.sign_up_page; + + return Column( + children: [ + // Company Name Field + UiTextField( + label: i18n.company_label, + hintText: i18n.company_hint, + controller: _companyController, + textInputAction: TextInputAction.next, + ), + const SizedBox(height: UiConstants.space4), + + // Email Field + UiTextField( + label: i18n.email_label, + hintText: i18n.email_hint, + controller: _emailController, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + ), + const SizedBox(height: UiConstants.space4), + + // Password Field + UiTextField( + label: i18n.password_label, + hintText: i18n.password_hint, + controller: _passwordController, + obscureText: _obscurePassword, + textInputAction: TextInputAction.next, + suffix: IconButton( + icon: Icon( + _obscurePassword ? UiIcons.eyeOff : UiIcons.eye, + color: UiColors.iconSecondary, + size: 20, + ), + onPressed: () => + setState(() => _obscurePassword = !_obscurePassword), + ), + ), + const SizedBox(height: UiConstants.space4), + + // Confirm Password Field + UiTextField( + label: i18n.confirm_password_label, + hintText: i18n.confirm_password_hint, + controller: _confirmPasswordController, + obscureText: _obscurePassword, + textInputAction: TextInputAction.done, + onSubmitted: (_) => _handleSubmit(), + ), + + const SizedBox(height: UiConstants.space8), + + // Create Account Button + UiButton.primary( + text: widget.isLoading ? null : i18n.create_account_button, + onPressed: widget.isLoading ? null : _handleSubmit, + child: widget.isLoading + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator( + color: UiColors.white, + strokeWidth: 2, + ), + ) + : null, + ), + ], + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/auth_divider.dart b/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/auth_divider.dart new file mode 100644 index 00000000..db898fcb --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/auth_divider.dart @@ -0,0 +1,28 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A divider widget with centered text, typically used to separate +/// email/password auth from social auth headers. +/// +/// Displays a horizontal line with text in the middle (e.g., "Or continue with"). +class AuthDivider extends StatelessWidget { + /// The text to display in the center of the divider. + final String text; + + /// Creates an [AuthDivider]. + const AuthDivider({super.key, required this.text}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + const Expanded(child: Divider()), + Padding( + padding: const EdgeInsets.symmetric(horizontal: UiConstants.space4), + child: Text(text, style: UiTypography.footnote1r.textSecondary), + ), + const Expanded(child: Divider()), + ], + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/auth_social_button.dart b/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/auth_social_button.dart new file mode 100644 index 00000000..35b8a4cc --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/auth_social_button.dart @@ -0,0 +1,38 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A specialized button for social authentication integration. +/// +/// This widget wraps [UiButton.secondary] to provide a consistent look and feel +/// for social sign-in/sign-up buttons (e.g., Google, Apple). +class AuthSocialButton extends StatelessWidget { + /// The localizable text to display on the button (e.g., "Continue with Google"). + final String text; + + /// The icon representing the social provider. + final IconData icon; + + /// Callback to execute when the button is tapped. + final VoidCallback onPressed; + + /// Creates an [AuthSocialButton]. + /// + /// The [text], [icon], and [onPressed] arguments must not be null. + const AuthSocialButton({ + super.key, + required this.text, + required this.icon, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return UiButton.secondary( + onPressed: onPressed, + leadingIcon: icon, + text: text, + // Ensure the button spans the full width available + size: UiButtonSize.large, + ); + } +} diff --git a/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/section_titles.dart b/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/section_titles.dart new file mode 100644 index 00000000..3c548c50 --- /dev/null +++ b/apps/packages/features/client/authentication/lib/src/presentation/widgets/common/section_titles.dart @@ -0,0 +1,24 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A widget that displays a section title with a leading icon. +class SectionTitle extends StatelessWidget { + /// The title of the section. + final String title; + + /// The subtitle of the section. + final String subtitle; + + const SectionTitle({super.key, required this.title, required this.subtitle}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: UiTypography.headline1m), + Text(subtitle, style: UiTypography.body2r.textSecondary), + ], + ); + } +} diff --git a/apps/packages/features/client/authentication/pubspec.yaml b/apps/packages/features/client/authentication/pubspec.yaml new file mode 100644 index 00000000..781d6f07 --- /dev/null +++ b/apps/packages/features/client/authentication/pubspec.yaml @@ -0,0 +1,32 @@ +name: client_authentication +description: Client Authentication and Registration feature. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^8.1.0 + flutter_modular: ^6.3.0 + equatable: ^2.0.5 + + # Architecture Packages + design_system: + path: ../../../../design_system + core_localization: + path: ../../../core_localization + +dev_dependencies: + flutter_test: + sdk: flutter + bloc_test: ^9.1.0 + mocktail: ^1.0.0 + build_runner: ^2.4.15 + +flutter: + uses-material-design: true diff --git a/apps/packages/features/client/home/lib/client_home.dart b/apps/packages/features/client/home/lib/client_home.dart new file mode 100644 index 00000000..d2c1a0ad --- /dev/null +++ b/apps/packages/features/client/home/lib/client_home.dart @@ -0,0 +1,35 @@ +library client_home; + +import 'package:flutter_modular/flutter_modular.dart'; +import 'src/data/repositories_impl/home_repository_impl.dart'; +import 'src/domain/repositories/home_repository_interface.dart'; +import 'src/domain/usecases/get_dashboard_data_usecase.dart'; +import 'src/presentation/blocs/client_home_bloc.dart'; +import 'src/presentation/pages/client_home_page.dart'; + +export 'src/presentation/pages/client_home_page.dart'; +export 'src/presentation/navigation/client_home_navigator.dart'; + +/// A [Module] for the client home feature. +class ClientHomeModule extends Module { + @override + void binds(Injector i) { + // Repositories + i.addLazySingleton(HomeRepositoryImpl.new); + + // UseCases + i.addLazySingleton(GetDashboardDataUseCase.new); + + // BLoCs + i.add( + () => ClientHomeBloc( + getDashboardDataUseCase: i.get(), + ), + ); + } + + @override + void routes(r) { + r.child('/', child: (_) => const ClientHomePage()); + } +} diff --git a/apps/packages/features/client/home/lib/src/data/repositories_impl/home_repository_impl.dart b/apps/packages/features/client/home/lib/src/data/repositories_impl/home_repository_impl.dart new file mode 100644 index 00000000..ff322454 --- /dev/null +++ b/apps/packages/features/client/home/lib/src/data/repositories_impl/home_repository_impl.dart @@ -0,0 +1,19 @@ +import '../../domain/repositories/home_repository_interface.dart'; + +/// Mock implementation of [HomeRepositoryInterface]. +class HomeRepositoryImpl implements HomeRepositoryInterface { + @override + Future> getDashboardData() async { + // Simulate network delay + await Future.delayed(const Duration(milliseconds: 500)); + + return { + 'weeklySpending': 4250.0, + 'next7DaysSpending': 6100.0, + 'weeklyShifts': 12, + 'next7DaysScheduled': 18, + 'totalNeeded': 10, + 'totalFilled': 8, + }; + } +} diff --git a/apps/packages/features/client/home/lib/src/domain/repositories/home_repository_interface.dart b/apps/packages/features/client/home/lib/src/domain/repositories/home_repository_interface.dart new file mode 100644 index 00000000..1dc4f070 --- /dev/null +++ b/apps/packages/features/client/home/lib/src/domain/repositories/home_repository_interface.dart @@ -0,0 +1,5 @@ +/// Interface for the Client Home repository. +abstract class HomeRepositoryInterface { + /// Fetches dashboard data. + Future> getDashboardData(); +} diff --git a/apps/packages/features/client/home/lib/src/domain/usecases/get_dashboard_data_usecase.dart b/apps/packages/features/client/home/lib/src/domain/usecases/get_dashboard_data_usecase.dart new file mode 100644 index 00000000..0329c9b9 --- /dev/null +++ b/apps/packages/features/client/home/lib/src/domain/usecases/get_dashboard_data_usecase.dart @@ -0,0 +1,14 @@ +import '../repositories/home_repository_interface.dart'; + +/// Use case to fetch dashboard data for the client home screen. +class GetDashboardDataUseCase { + final HomeRepositoryInterface _repository; + + /// Creates a [GetDashboardDataUseCase]. + const GetDashboardDataUseCase(this._repository); + + /// Executes the use case. + Future> call() { + return _repository.getDashboardData(); + } +} diff --git a/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_bloc.dart b/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_bloc.dart new file mode 100644 index 00000000..b827df92 --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_bloc.dart @@ -0,0 +1,104 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../domain/usecases/get_dashboard_data_usecase.dart'; +import 'client_home_event.dart'; +import 'client_home_state.dart'; + +/// BLoC to manage Client Home dashboard state. +class ClientHomeBloc extends Bloc { + final GetDashboardDataUseCase _getDashboardDataUseCase; + + ClientHomeBloc({required GetDashboardDataUseCase getDashboardDataUseCase}) + : _getDashboardDataUseCase = getDashboardDataUseCase, + super(const ClientHomeState()) { + on(_onStarted); + on(_onEditModeToggled); + on(_onWidgetVisibilityToggled); + on(_onWidgetReordered); + on(_onLayoutReset); + } + + Future _onStarted( + ClientHomeStarted event, + Emitter emit, + ) async { + emit(state.copyWith(status: ClientHomeStatus.loading)); + try { + final data = await _getDashboardDataUseCase(); + emit( + state.copyWith( + status: ClientHomeStatus.success, + weeklySpending: data['weeklySpending'] as double?, + next7DaysSpending: data['next7DaysSpending'] as double?, + weeklyShifts: data['weeklyShifts'] as int?, + next7DaysScheduled: data['next7DaysScheduled'] as int?, + totalNeeded: data['totalNeeded'] as int?, + totalFilled: data['totalFilled'] as int?, + ), + ); + } catch (e) { + emit( + state.copyWith( + status: ClientHomeStatus.error, + errorMessage: e.toString(), + ), + ); + } + } + + void _onEditModeToggled( + ClientHomeEditModeToggled event, + Emitter emit, + ) { + emit(state.copyWith(isEditMode: !state.isEditMode)); + } + + void _onWidgetVisibilityToggled( + ClientHomeWidgetVisibilityToggled event, + Emitter emit, + ) { + final newVisibility = Map.from(state.widgetVisibility); + newVisibility[event.widgetId] = !(newVisibility[event.widgetId] ?? true); + emit(state.copyWith(widgetVisibility: newVisibility)); + } + + void _onWidgetReordered( + ClientHomeWidgetReordered event, + Emitter emit, + ) { + final newList = List.from(state.widgetOrder); + int oldIndex = event.oldIndex; + int newIndex = event.newIndex; + + if (oldIndex < newIndex) { + newIndex -= 1; + } + final item = newList.removeAt(oldIndex); + newList.insert(newIndex, item); + + emit(state.copyWith(widgetOrder: newList)); + } + + void _onLayoutReset( + ClientHomeLayoutReset event, + Emitter emit, + ) { + emit( + state.copyWith( + widgetOrder: const [ + 'actions', + 'reorder', + 'coverage', + 'spending', + 'liveActivity', + ], + widgetVisibility: const { + 'actions': true, + 'reorder': true, + 'coverage': true, + 'spending': true, + 'liveActivity': true, + }, + ), + ); + } +} diff --git a/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_event.dart b/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_event.dart new file mode 100644 index 00000000..86524041 --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_event.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; + +abstract class ClientHomeEvent extends Equatable { + const ClientHomeEvent(); + + @override + List get props => []; +} + +class ClientHomeStarted extends ClientHomeEvent {} + +class ClientHomeEditModeToggled extends ClientHomeEvent {} + +class ClientHomeWidgetVisibilityToggled extends ClientHomeEvent { + final String widgetId; + const ClientHomeWidgetVisibilityToggled(this.widgetId); + + @override + List get props => [widgetId]; +} + +class ClientHomeWidgetReordered extends ClientHomeEvent { + final int oldIndex; + final int newIndex; + const ClientHomeWidgetReordered(this.oldIndex, this.newIndex); + + @override + List get props => [oldIndex, newIndex]; +} + +class ClientHomeLayoutReset extends ClientHomeEvent {} diff --git a/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_state.dart b/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_state.dart new file mode 100644 index 00000000..50b57e2e --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/blocs/client_home_state.dart @@ -0,0 +1,88 @@ +import 'package:equatable/equatable.dart'; + +enum ClientHomeStatus { initial, loading, success, error } + +class ClientHomeState extends Equatable { + final ClientHomeStatus status; + final List widgetOrder; + final Map widgetVisibility; + final bool isEditMode; + final String? errorMessage; + + // Dashboard Data (Mocked for now) + final double weeklySpending; + final double next7DaysSpending; + final int weeklyShifts; + final int next7DaysScheduled; + final int totalNeeded; + final int totalFilled; + + const ClientHomeState({ + this.status = ClientHomeStatus.initial, + this.widgetOrder = const [ + 'actions', + 'reorder', + 'coverage', + 'spending', + 'liveActivity', + ], + this.widgetVisibility = const { + 'actions': true, + 'reorder': true, + 'coverage': true, + 'spending': true, + 'liveActivity': true, + }, + this.isEditMode = false, + this.errorMessage, + this.weeklySpending = 4250.0, + this.next7DaysSpending = 6100.0, + this.weeklyShifts = 12, + this.next7DaysScheduled = 18, + this.totalNeeded = 10, + this.totalFilled = 8, + }); + + ClientHomeState copyWith({ + ClientHomeStatus? status, + List? widgetOrder, + Map? widgetVisibility, + bool? isEditMode, + String? errorMessage, + double? weeklySpending, + double? next7DaysSpending, + int? weeklyShifts, + int? next7DaysScheduled, + int? totalNeeded, + int? totalFilled, + }) { + return ClientHomeState( + status: status ?? this.status, + widgetOrder: widgetOrder ?? this.widgetOrder, + widgetVisibility: widgetVisibility ?? this.widgetVisibility, + isEditMode: isEditMode ?? this.isEditMode, + errorMessage: errorMessage ?? this.errorMessage, + weeklySpending: weeklySpending ?? this.weeklySpending, + next7DaysSpending: next7DaysSpending ?? this.next7DaysSpending, + weeklyShifts: weeklyShifts ?? this.weeklyShifts, + next7DaysScheduled: next7DaysScheduled ?? this.next7DaysScheduled, + totalNeeded: totalNeeded ?? this.totalNeeded, + totalFilled: totalFilled ?? this.totalFilled, + ); + } + + @override + List get props => [ + status, + widgetOrder, + widgetVisibility, + isEditMode, + errorMessage, + weeklySpending, + next7DaysSpending, + weeklyShifts, + next7DaysScheduled, + totalNeeded, + totalFilled, + ]; +} diff --git a/apps/packages/features/client/home/lib/src/presentation/navigation/client_home_navigator.dart b/apps/packages/features/client/home/lib/src/presentation/navigation/client_home_navigator.dart new file mode 100644 index 00000000..d3ad6e8e --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/navigation/client_home_navigator.dart @@ -0,0 +1,10 @@ +import 'package:flutter_modular/flutter_modular.dart'; + +/// Navigation extension for the Client Home feature. +extension ClientHomeNavigator on IModularNavigator { + /// Navigates to the Client Home page. + void navigateClientHome() => navigate('/client-home/'); + + /// Pushes the Client Home page. + Future pushClientHome() => pushNamed('/client-home/'); +} diff --git a/apps/packages/features/client/home/lib/src/presentation/pages/client_home_page.dart b/apps/packages/features/client/home/lib/src/presentation/pages/client_home_page.dart new file mode 100644 index 00000000..3633f59f --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/pages/client_home_page.dart @@ -0,0 +1,432 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../blocs/client_home_bloc.dart'; +import '../blocs/client_home_event.dart'; +import '../blocs/client_home_state.dart'; +import '../widgets/actions_widget.dart'; +import '../widgets/live_activity_widget.dart'; +import '../widgets/reorder_widget.dart'; +import '../widgets/shift_order_form_sheet.dart'; +import '../widgets/spending_widget.dart'; + +/// The main Home page for client users. +class ClientHomePage extends StatefulWidget { + /// Creates a [ClientHomePage]. + const ClientHomePage({super.key}); + + @override + State createState() => _ClientHomePageState(); +} + +class _ClientHomePageState extends State { + late final ClientHomeBloc _homeBloc; + + @override + void initState() { + super.initState(); + _homeBloc = Modular.get()..add(ClientHomeStarted()); + } + + @override + void dispose() { + _homeBloc.close(); + super.dispose(); + } + + void _openOrderFormSheet( + BuildContext context, + Map? shiftData, + ) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return ShiftOrderFormSheet( + initialData: shiftData, + onSubmit: (data) { + Navigator.pop(context); + }, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + final i18n = t.client_home; + + return BlocProvider.value( + value: _homeBloc, + child: Scaffold( + body: SafeArea( + child: Column( + children: [ + _buildHeader(context, i18n), + _buildEditModeBanner(i18n), + + Flexible( + child: BlocBuilder( + builder: (context, state) { + if (state.isEditMode) { + return ReorderableListView( + padding: const EdgeInsets.fromLTRB( + UiConstants.space4, + 0, + UiConstants.space4, + 100, + ), + onReorder: (oldIndex, newIndex) { + _homeBloc.add( + ClientHomeWidgetReordered(oldIndex, newIndex), + ); + }, + children: state.widgetOrder.map((id) { + return Container( + key: ValueKey(id), + margin: const EdgeInsets.only( + bottom: UiConstants.space4, + ), + child: _buildDraggableWidgetWrapper( + context, + id, + state, + ), + ); + }).toList(), + ); + } + + return ListView( + padding: const EdgeInsets.fromLTRB( + UiConstants.space4, + 0, + UiConstants.space4, + 100, + ), + children: state.widgetOrder.map((id) { + if (!(state.widgetVisibility[id] ?? true)) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.only( + bottom: UiConstants.space4, + ), + child: _buildWidgetContent(context, id, state), + ); + }).toList(), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildHeader(BuildContext context, dynamic i18n) { + return Padding( + padding: const EdgeInsets.fromLTRB( + UiConstants.space4, + UiConstants.space4, + UiConstants.space4, + UiConstants.space3, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: UiColors.primary.withOpacity(0.2), + width: 2, + ), + ), + child: CircleAvatar( + backgroundColor: UiColors.primary.withOpacity(0.1), + child: Text( + 'C', + style: UiTypography.body2b.copyWith( + color: UiColors.primary, + ), + ), + ), + ), + const SizedBox(width: UiConstants.space3), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + i18n.dashboard.welcome_back, + style: UiTypography.footnote2r.textSecondary, + ), + Text('Your Company', style: UiTypography.body1b), + ], + ), + ], + ), + Row( + children: [ + _HeaderIconButton( + icon: UiIcons.edit, + isActive: _homeBloc.state.isEditMode, + onTap: () => _homeBloc.add(ClientHomeEditModeToggled()), + ), + const SizedBox(width: UiConstants.space2), + _HeaderIconButton( + icon: UiIcons.bell, + badgeText: '3', + onTap: () {}, + ), + const SizedBox(width: UiConstants.space2), + _HeaderIconButton(icon: UiIcons.settings, onTap: () {}), + ], + ), + ], + ), + ); + } + + Widget _buildEditModeBanner(dynamic i18n) { + return BlocBuilder( + buildWhen: (prev, curr) => prev.isEditMode != curr.isEditMode, + builder: (context, state) { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + height: state.isEditMode ? 76 : 0, + clipBehavior: Clip.antiAlias, + margin: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space2, + ), + padding: const EdgeInsets.all(UiConstants.space3), + decoration: BoxDecoration( + color: UiColors.primary.withOpacity(0.1), + border: Border.all(color: UiColors.primary.withOpacity(0.3)), + borderRadius: UiConstants.radiusLg, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Icon(UiIcons.edit, size: 16, color: UiColors.primary), + const SizedBox(width: UiConstants.space2), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + i18n.dashboard.edit_mode_active, + style: UiTypography.footnote1b.copyWith( + color: UiColors.primary, + ), + ), + Text( + i18n.dashboard.drag_instruction, + style: UiTypography.footnote2r.textSecondary, + ), + ], + ), + UiButton.secondary( + text: i18n.dashboard.reset, + onPressed: () => _homeBloc.add(ClientHomeLayoutReset()), + size: UiButtonSize.small, + style: OutlinedButton.styleFrom( + minimumSize: const Size(0, 48), + maximumSize: const Size(double.infinity, 48), + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildDraggableWidgetWrapper( + BuildContext context, + String id, + ClientHomeState state, + ) { + final isVisible = state.widgetVisibility[id] ?? true; + final title = _getWidgetTitle(id); + + return Column( + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space2, + vertical: UiConstants.space1, + ), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: UiConstants.radiusMd, + border: Border.all(color: UiColors.border), + ), + child: Row( + children: [ + const Icon( + UiIcons.gripVertical, + size: 14, + color: UiColors.iconSecondary, + ), + const SizedBox(width: UiConstants.space2), + Text(title, style: UiTypography.footnote1m), + ], + ), + ), + const SizedBox(width: UiConstants.space2), + GestureDetector( + onTap: () => _homeBloc.add(ClientHomeWidgetVisibilityToggled(id)), + child: Container( + padding: const EdgeInsets.all(UiConstants.space1), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: UiConstants.radiusMd, + border: Border.all(color: UiColors.border), + ), + child: Icon( + isVisible ? UiIcons.success : UiIcons.error, + size: 14, + color: isVisible ? UiColors.primary : UiColors.iconSecondary, + ), + ), + ), + ], + ), + const SizedBox(height: UiConstants.space2), + Opacity( + opacity: isVisible ? 1.0 : 0.4, + child: IgnorePointer( + ignoring: !isVisible, + child: _buildWidgetContent(context, id, state), + ), + ), + ], + ); + } + + Widget _buildWidgetContent( + BuildContext context, + String id, + ClientHomeState state, + ) { + switch (id) { + case 'actions': + return ActionsWidget( + onRapidPressed: () {}, + onCreateOrderPressed: () => _openOrderFormSheet(context, null), + ); + case 'reorder': + return ReorderWidget( + onReorderPressed: (data) => _openOrderFormSheet(context, data), + ); + case 'spending': + return SpendingWidget( + weeklySpending: state.weeklySpending, + next7DaysSpending: state.next7DaysSpending, + weeklyShifts: state.weeklyShifts, + next7DaysScheduled: state.next7DaysScheduled, + ); + case 'coverage': + case 'liveActivity': + return LiveActivityWidget(onViewAllPressed: () {}); + default: + return const SizedBox.shrink(); + } + } + + String _getWidgetTitle(String id) { + final i18n = t.client_home.widgets; + switch (id) { + case 'actions': + return i18n.actions; + case 'reorder': + return i18n.reorder; + case 'coverage': + return i18n.coverage; + case 'spending': + return i18n.spending; + case 'liveActivity': + return i18n.live_activity; + default: + return ''; + } + } +} + +class _HeaderIconButton extends StatelessWidget { + final IconData icon; + final String? badgeText; + final bool isActive; + final VoidCallback onTap; + + const _HeaderIconButton({ + required this.icon, + this.badgeText, + this.isActive = false, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: isActive ? UiColors.primary : UiColors.white, + borderRadius: UiConstants.radiusMd, + boxShadow: [ + BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 2), + ], + ), + child: Icon( + icon, + color: isActive ? UiColors.white : UiColors.iconSecondary, + size: 16, + ), + ), + if (badgeText != null) + Positioned( + top: -4, + right: -4, + child: Container( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: UiColors.iconError, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints(minWidth: 16, minHeight: 16), + child: Center( + child: Text( + badgeText!, + style: UiTypography.footnote2b.copyWith( + color: UiColors.white, + fontSize: 8, + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/apps/packages/features/client/home/lib/src/presentation/widgets/actions_widget.dart b/apps/packages/features/client/home/lib/src/presentation/widgets/actions_widget.dart new file mode 100644 index 00000000..3d7b695d --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/widgets/actions_widget.dart @@ -0,0 +1,127 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A widget that displays quick actions for the client. +class ActionsWidget extends StatelessWidget { + /// Callback when RAPID is pressed. + final VoidCallback onRapidPressed; + + /// Callback when Create Order is pressed. + final VoidCallback onCreateOrderPressed; + + /// Creates an [ActionsWidget]. + const ActionsWidget({ + super.key, + required this.onRapidPressed, + required this.onCreateOrderPressed, + }); + + @override + Widget build(BuildContext context) { + // Check if client_home exists in t + final i18n = t.client_home.actions; + + return Row( + children: [ + Expanded( + child: _ActionCard( + title: i18n.rapid, + subtitle: i18n.rapid_subtitle, + icon: UiIcons.zap, + color: const Color(0xFFFEF2F2), + borderColor: const Color(0xFFFECACA), + iconBgColor: const Color(0xFFFEE2E2), + iconColor: const Color(0xFFDC2626), + textColor: const Color(0xFF7F1D1D), + subtitleColor: const Color(0xFFB91C1C), + onTap: onRapidPressed, + ), + ), + const SizedBox(width: UiConstants.space2), + Expanded( + child: _ActionCard( + title: i18n.create_order, + subtitle: i18n.create_order_subtitle, + icon: UiIcons.add, + color: UiColors.white, + borderColor: UiColors.border, + iconBgColor: const Color(0xFFEFF6FF), + iconColor: const Color(0xFF2563EB), + textColor: UiColors.textPrimary, + subtitleColor: UiColors.textSecondary, + onTap: onCreateOrderPressed, + ), + ), + ], + ); + } +} + +class _ActionCard extends StatelessWidget { + final String title; + final String subtitle; + final IconData icon; + final Color color; + final Color borderColor; + final Color iconBgColor; + final Color iconColor; + final Color textColor; + final Color subtitleColor; + final VoidCallback onTap; + + const _ActionCard({ + required this.title, + required this.subtitle, + required this.icon, + required this.color, + required this.borderColor, + required this.iconBgColor, + required this.iconColor, + required this.textColor, + required this.subtitleColor, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + height: 100, + padding: const EdgeInsets.all(UiConstants.space3), + decoration: BoxDecoration( + color: color, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: borderColor), + boxShadow: [ + BoxShadow(color: UiColors.black.withOpacity(0.02), blurRadius: 4), + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: iconBgColor, + borderRadius: UiConstants.radiusLg, + ), + child: Icon(icon, color: iconColor, size: 16), + ), + const SizedBox(height: UiConstants.space2), + Text( + title, + style: UiTypography.footnote1b.copyWith(color: textColor), + ), + Text( + subtitle, + style: UiTypography.footnote2r.copyWith(color: subtitleColor), + ), + ], + ), + ), + ); + } +} diff --git a/apps/packages/features/client/home/lib/src/presentation/widgets/coverage_dashboard.dart b/apps/packages/features/client/home/lib/src/presentation/widgets/coverage_dashboard.dart new file mode 100644 index 00000000..3abec6c2 --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/widgets/coverage_dashboard.dart @@ -0,0 +1,222 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A dashboard widget that displays today's coverage status. +class CoverageDashboard extends StatelessWidget { + /// The list of shifts for today. + final List shifts; + + /// The list of applications for today's shifts. + final List applications; + + /// Creates a [CoverageDashboard]. + const CoverageDashboard({ + super.key, + required this.shifts, + required this.applications, + }); + + @override + Widget build(BuildContext context) { + int totalNeeded = 0; + int totalConfirmed = 0; + double todayCost = 0; + + for (final s in shifts) { + final needed = s['workersNeeded'] as int? ?? 0; + final confirmed = s['filled'] as int? ?? 0; + final rate = s['hourlyRate'] as double? ?? 20.0; + + totalNeeded += needed; + totalConfirmed += confirmed; + todayCost += rate * 8 * confirmed; + } + + final coveragePercent = totalNeeded > 0 + ? ((totalConfirmed / totalNeeded) * 100).round() + : 100; + final unfilledPositions = totalNeeded - totalConfirmed; + + final checkedInCount = applications + .where((a) => (a as Map)['checkInTime'] != null) + .length; + final lateWorkersCount = applications + .where((a) => (a as Map)['status'] == 'LATE') + .length; + + final isCoverageGood = coveragePercent >= 90; + final coverageBadgeColor = isCoverageGood + ? const Color(0xFFD1FAE5) // TODO: Use design system color if available + : const Color(0xFFFEF3C7); + final coverageTextColor = isCoverageGood + ? const Color(0xFF047857) + : const Color(0xFFB45309); + + return Container( + padding: const EdgeInsets.all(UiConstants.space4), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), + boxShadow: [ + BoxShadow( + color: UiColors.black.withOpacity(0.02), + blurRadius: 4, + offset: const Offset(0, 1), + ), + ], + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Today's Status", style: UiTypography.body1m.textSecondary), + Container( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space2, + vertical: 2.0, + ), + decoration: BoxDecoration( + color: coverageBadgeColor, + borderRadius: UiConstants.radiusMd, + ), + child: Text( + '$coveragePercent% Covered', + style: UiTypography.footnote1b.copyWith( + color: coverageTextColor, + ), + ), + ), + ], + ), + const SizedBox(height: UiConstants.space4), + + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + children: [ + _StatusCard( + label: 'Unfilled Today', + value: '$unfilledPositions', + icon: UiIcons.warning, + isWarning: unfilledPositions > 0, + ), + if (lateWorkersCount > 0) ...[ + const SizedBox(height: UiConstants.space2), + _StatusCard( + label: 'Running Late', + value: '$lateWorkersCount', + icon: UiIcons.error, + isError: true, + ), + ], + ], + ), + ), + const SizedBox(width: UiConstants.space2), + Expanded( + child: Column( + children: [ + _StatusCard( + label: 'Checked In', + value: '$checkedInCount/$totalConfirmed', + icon: UiIcons.success, + isInfo: true, + ), + const SizedBox(height: UiConstants.space2), + _StatusCard( + label: "Today's Cost", + value: '\$${todayCost.round()}', + icon: UiIcons.dollar, + isInfo: true, + ), + ], + ), + ), + ], + ), + ], + ), + ); + } +} + +class _StatusCard extends StatelessWidget { + final String label; + final String value; + final IconData icon; + final bool isWarning; + final bool isError; + final bool isInfo; + + const _StatusCard({ + required this.label, + required this.value, + required this.icon, + this.isWarning = false, + this.isError = false, + this.isInfo = false, + }); + + @override + Widget build(BuildContext context) { + Color bg = const Color(0xFFF1F5F9); + Color border = const Color(0xFFE2E8F0); + Color iconColor = UiColors.iconSecondary; + Color textColor = UiColors.textPrimary; + + if (isWarning) { + bg = const Color(0xFFFFFBEB); + border = const Color(0xFFFDE68A); + iconColor = const Color(0xFFD97706); + textColor = const Color(0xFFB45309); + } else if (isError) { + bg = const Color(0xFFFEF2F2); + border = const Color(0xFFFECACA); + iconColor = const Color(0xFFDC2626); + textColor = const Color(0xFFB91C1C); + } else if (isInfo) { + bg = const Color(0xFFEFF6FF); + border = const Color(0xFFBFDBFE); + iconColor = const Color(0xFF2563EB); + textColor = const Color(0xFF1D4ED8); + } + + return Container( + padding: const EdgeInsets.all(UiConstants.space3), + decoration: BoxDecoration( + color: bg, + border: Border.all(color: border), + borderRadius: UiConstants.radiusMd, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 16, color: iconColor), + const SizedBox(width: UiConstants.space2), + Expanded( + child: Text( + label, + style: UiTypography.footnote1m.copyWith( + color: textColor.withOpacity(0.8), + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: UiConstants.space1), + Text( + value, + style: UiTypography.headline3m.copyWith(color: textColor), + ), + ], + ), + ); + } +} diff --git a/apps/packages/features/client/home/lib/src/presentation/widgets/live_activity_widget.dart b/apps/packages/features/client/home/lib/src/presentation/widgets/live_activity_widget.dart new file mode 100644 index 00000000..037eb7d2 --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/widgets/live_activity_widget.dart @@ -0,0 +1,69 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'coverage_dashboard.dart'; + +/// A widget that displays live activity information. +class LiveActivityWidget extends StatelessWidget { + /// Callback when "View all" is pressed. + final VoidCallback onViewAllPressed; + + /// Creates a [LiveActivityWidget]. + const LiveActivityWidget({super.key, required this.onViewAllPressed}); + + @override + Widget build(BuildContext context) { + final i18n = t.client_home; + + // Mock data + final shifts = [ + { + 'workersNeeded': 5, + 'filled': 4, + 'hourlyRate': 20.0, + 'status': 'OPEN', + 'date': DateTime.now().toIso8601String().split('T')[0], + }, + { + 'workersNeeded': 5, + 'filled': 5, + 'hourlyRate': 22.0, + 'status': 'FILLED', + 'date': DateTime.now().toIso8601String().split('T')[0], + }, + ]; + final applications = [ + {'status': 'CONFIRMED', 'checkInTime': '09:00'}, + {'status': 'CONFIRMED', 'checkInTime': '09:05'}, + {'status': 'CONFIRMED'}, + {'status': 'LATE'}, + ]; + + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + i18n.widgets.live_activity.toUpperCase(), + style: UiTypography.footnote1b.textSecondary.copyWith( + letterSpacing: 0.5, + ), + ), + GestureDetector( + onTap: onViewAllPressed, + child: Text( + i18n.dashboard.view_all, + style: UiTypography.footnote1m.copyWith( + color: UiColors.primary, + ), + ), + ), + ], + ), + const SizedBox(height: UiConstants.space2), + CoverageDashboard(shifts: shifts, applications: applications), + ], + ); + } +} diff --git a/apps/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart b/apps/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart new file mode 100644 index 00000000..839f5dce --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart @@ -0,0 +1,231 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A widget that allows clients to reorder recent shifts. +class ReorderWidget extends StatelessWidget { + /// Callback when a reorder button is pressed. + final Function(Map shiftData) onReorderPressed; + + /// Creates a [ReorderWidget]. + const ReorderWidget({super.key, required this.onReorderPressed}); + + @override + Widget build(BuildContext context) { + final i18n = t.client_home.reorder; + + // Mock recent orders + final recentOrders = [ + { + 'title': 'Server', + 'location': 'Downtown Restaurant', + 'hourlyRate': 18.0, + 'hours': 6, + 'workers': 3, + 'type': 'One Day', + }, + { + 'title': 'Bartender', + 'location': 'Rooftop Bar', + 'hourlyRate': 22.0, + 'hours': 7, + 'workers': 2, + 'type': 'One Day', + }, + { + 'title': 'Event Staff', + 'location': 'Convention Center', + 'hourlyRate': 20.0, + 'hours': 10, + 'workers': 5, + 'type': 'Multi-Day', + }, + ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + i18n.title, + style: UiTypography.footnote1b.textSecondary.copyWith( + letterSpacing: 0.5, + ), + ), + const SizedBox(height: UiConstants.space2), + SizedBox( + height: 140, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: recentOrders.length, + separatorBuilder: (context, index) => + const SizedBox(width: UiConstants.space3), + itemBuilder: (context, index) { + final order = recentOrders[index]; + final totalCost = + (order['hourlyRate'] as double) * + (order['hours'] as int) * + (order['workers'] as int); + + return Container( + width: 260, + padding: const EdgeInsets.all(UiConstants.space3), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), + boxShadow: [ + BoxShadow( + color: UiColors.black.withOpacity(0.02), + blurRadius: 4, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: UiColors.primary.withOpacity(0.1), + borderRadius: UiConstants.radiusLg, + ), + child: const Icon( + UiIcons.building, + size: 16, + color: UiColors.primary, + ), + ), + const SizedBox(width: UiConstants.space2), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + order['title'] as String, + style: UiTypography.body2b, + overflow: TextOverflow.ellipsis, + ), + Text( + order['location'] as String, + style: + UiTypography.footnote1r.textSecondary, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + '\$${totalCost.toStringAsFixed(0)}', + style: UiTypography.body1b, + ), + Text( + i18n.per_hr( + amount: order['hourlyRate'].toString(), + ) + + ' · ${order['hours']}h', + style: UiTypography.footnote2r.textSecondary, + ), + ], + ), + ], + ), + const SizedBox(height: UiConstants.space3), + Row( + children: [ + _Badge( + icon: UiIcons.success, + text: order['type'] as String, + color: const Color(0xFF2563EB), + bg: const Color(0xFF2563EB), + textColor: UiColors.white, + ), + const SizedBox(width: UiConstants.space2), + _Badge( + icon: UiIcons.building, + text: '${order['workers']}', + color: const Color(0xFF334155), + bg: const Color(0xFFF1F5F9), + textColor: const Color(0xFF334155), + ), + ], + ), + const Spacer(), + SizedBox( + height: 28, + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () => onReorderPressed(order), + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.primary, + foregroundColor: UiColors.white, + padding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: UiConstants.radiusMd, + ), + elevation: 0, + ), + icon: const Icon(UiIcons.zap, size: 12), + label: Text( + i18n.reorder_button, + style: UiTypography.footnote1m, + ), + ), + ), + ], + ), + ); + }, + ), + ), + ], + ); + } +} + +class _Badge extends StatelessWidget { + final IconData icon; + final String text; + final Color color; + final Color bg; + final Color textColor; + + const _Badge({ + required this.icon, + required this.text, + required this.color, + required this.bg, + required this.textColor, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space2, + vertical: UiConstants.space1, + ), + decoration: BoxDecoration(color: bg, borderRadius: UiConstants.radiusSm), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 10, color: bg == textColor ? UiColors.white : color), + const SizedBox(width: UiConstants.space1), + Text(text, style: UiTypography.footnote2b.copyWith(color: textColor)), + ], + ), + ); + } +} diff --git a/apps/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart b/apps/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart new file mode 100644 index 00000000..f8dd0683 --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart @@ -0,0 +1,436 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A bottom sheet form for creating or reordering shifts. +class ShiftOrderFormSheet extends StatefulWidget { + /// Initial data for the form (e.g. from a reorder action). + final Map? initialData; + + /// Callback when the form is submitted. + final Function(Map data) onSubmit; + + /// Whether the submission is loading. + final bool isLoading; + + /// Creates a [ShiftOrderFormSheet]. + const ShiftOrderFormSheet({ + super.key, + this.initialData, + required this.onSubmit, + this.isLoading = false, + }); + + @override + State createState() => _ShiftOrderFormSheetState(); +} + +class _ShiftOrderFormSheetState extends State { + late Map _formData; + final List _roles = [ + 'Server', + 'Bartender', + 'Busser', + 'Cook', + 'Dishwasher', + 'Event Staff', + 'Warehouse Worker', + 'Retail Associate', + 'Host/Hostess', + ]; + + @override + void initState() { + super.initState(); + final defaultPosition = { + 'title': '', + 'start_time': '', + 'end_time': '', + 'workers_needed': 1, + 'hourly_rate': 18.0, + }; + + final defaults = { + 'date': '', + 'location': '', + 'recurring': false, + 'duration_days': null, + 'permanent': false, + 'duration_months': null, + 'positions': [Map.from(defaultPosition)], + }; + + if (widget.initialData != null) { + final input = widget.initialData!; + final firstPosition = { + ...defaultPosition, + 'title': input['title'] ?? input['role'] ?? '', + 'start_time': input['startTime'] ?? input['start_time'] ?? '', + 'end_time': input['endTime'] ?? input['end_time'] ?? '', + 'hourly_rate': (input['hourlyRate'] ?? input['hourly_rate'] ?? 18.0) + .toDouble(), + 'workers_needed': (input['workers'] ?? input['workers_needed'] ?? 1) + .toInt(), + }; + + _formData = { + ...defaults, + ...input, + 'positions': [firstPosition], + }; + } else { + _formData = Map.from(defaults); + } + + if (_formData['date'] == null || _formData['date'] == '') { + final tomorrow = DateTime.now().add(const Duration(days: 1)); + _formData['date'] = tomorrow.toIso8601String().split('T')[0]; + } + } + + void _updateField(String field, dynamic value) { + setState(() { + _formData[field] = value; + }); + } + + void _updatePositionField(int index, String field, dynamic value) { + setState(() { + _formData['positions'][index][field] = value; + }); + } + + void _addPosition() { + setState(() { + _formData['positions'].add({ + 'title': '', + 'start_time': '', + 'end_time': '', + 'workers_needed': 1, + 'hourly_rate': 18.0, + }); + }); + } + + void _removePosition(int index) { + if (_formData['positions'].length > 1) { + setState(() { + _formData['positions'].removeAt(index); + }); + } + } + + String _getShiftType() { + if (_formData['permanent'] == true || + _formData['duration_months'] != null) { + return 'Long Term'; + } + if (_formData['recurring'] == true || _formData['duration_days'] != null) { + return 'Multi-Day'; + } + return 'One Day'; + } + + @override + Widget build(BuildContext context) { + final i18n = t.client_home.form; + + return Container( + height: MediaQuery.of(context).size.height * 0.9, + decoration: const BoxDecoration( + color: UiColors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(UiConstants.space5), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: UiColors.border)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.initialData != null + ? i18n.edit_reorder + : i18n.post_new, + style: UiTypography.headline3m.copyWith( + fontWeight: FontWeight.bold, + ), + ), + IconButton( + icon: const Icon( + UiIcons.close, + color: UiColors.iconSecondary, + ), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ), + if (widget.initialData != null) + Padding( + padding: const EdgeInsets.only( + left: UiConstants.space5, + right: UiConstants.space5, + bottom: UiConstants.space5, + ), + child: Text( + i18n.review_subtitle, + style: UiTypography.body2r.textSecondary, + ), + ), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Shift Type Badge + Container( + margin: const EdgeInsets.only(bottom: UiConstants.space5), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space3, + vertical: UiConstants.space2, + ), + decoration: BoxDecoration( + color: const Color(0xFFEFF6FF), + borderRadius: UiConstants.radiusFull, + border: Border.all(color: const Color(0xFFBFDBFE)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF3B82F6), + ), + ), + const SizedBox(width: UiConstants.space2), + Text( + _getShiftType(), + style: UiTypography.footnote1b.copyWith( + color: const Color(0xFF1D4ED8), + ), + ), + ], + ), + ), + + _buildLabel(i18n.date_label), + UiTextField( + hintText: i18n.date_hint, + controller: TextEditingController(text: _formData['date']), + readOnly: true, + onTap: () async { + final selectedDate = await showDatePicker( + context: context, + initialDate: + _formData['date'] != null && + _formData['date'].isNotEmpty + ? DateTime.parse(_formData['date']) + : DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add( + const Duration(days: 365 * 5), + ), + ); + if (selectedDate != null) { + _updateField( + 'date', + selectedDate.toIso8601String().split('T')[0], + ); + } + }, + ), + const SizedBox(height: UiConstants.space5), + + _buildLabel(i18n.location_label), + UiTextField( + hintText: i18n.location_hint, + controller: TextEditingController( + text: _formData['location'], + ), + onChanged: (value) => _updateField('location', value), + ), + const SizedBox(height: UiConstants.space5), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(i18n.positions_title, style: UiTypography.body1b), + UiButton.text( + onPressed: _addPosition, + text: i18n.add_position, + leadingIcon: UiIcons.add, + size: UiButtonSize.small, + style: TextButton.styleFrom( + minimumSize: const Size(0, 48), + maximumSize: const Size(double.infinity, 48), + ), + ), + ], + ), + const SizedBox(height: UiConstants.space4), + + ...(_formData['positions'] as List).asMap().entries.map(( + entry, + ) { + final index = entry.key; + final position = entry.value; + return _PositionCard( + index: index, + position: position, + showDelete: _formData['positions'].length > 1, + onDelete: () => _removePosition(index), + roles: _roles, + onUpdate: (field, value) => + _updatePositionField(index, field, value), + labels: i18n, + ); + }), + const SizedBox(height: UiConstants.space10), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(UiConstants.space5), + child: UiButton.primary( + text: i18n.post_shift, + onPressed: () => widget.onSubmit(_formData), + ), + ), + ], + ), + ); + } + + Widget _buildLabel(String text) { + return Padding( + padding: const EdgeInsets.only(bottom: UiConstants.space2), + child: Text(text, style: UiTypography.body2b), + ); + } +} + +class _PositionCard extends StatelessWidget { + final int index; + final Map position; + final bool showDelete; + final VoidCallback onDelete; + final List roles; + final Function(String field, dynamic value) onUpdate; + final dynamic labels; + + const _PositionCard({ + required this.index, + required this.position, + required this.showDelete, + required this.onDelete, + required this.roles, + required this.onUpdate, + required this.labels, + }); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 0, + margin: const EdgeInsets.only(bottom: UiConstants.space5), + shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg), + color: const Color(0xFFF8FAFC), + child: Padding( + padding: const EdgeInsets.all(UiConstants.space4), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 32, + height: 32, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: UiColors.primary, + ), + child: Center( + child: Text( + '${index + 1}', + style: UiTypography.footnote1b.copyWith( + color: UiColors.white, + ), + ), + ), + ), + const SizedBox(width: UiConstants.space2), + Text('Position ${index + 1}', style: UiTypography.body2b), + ], + ), + if (showDelete) + IconButton( + icon: const Icon( + UiIcons.close, + size: 18, + color: UiColors.iconError, + ), + onPressed: onDelete, + ), + ], + ), + const SizedBox(height: UiConstants.space4), + + // Simplified for brevity in prototype-to-feature move + DropdownButtonFormField( + value: position['title'].isEmpty ? null : position['title'], + hint: Text(labels.role_hint), + items: roles + .map( + (role) => DropdownMenuItem(value: role, child: Text(role)), + ) + .toList(), + onChanged: (value) => onUpdate('title', value), + decoration: InputDecoration( + labelText: labels.role_label, + border: const OutlineInputBorder(), + ), + ), + + const SizedBox(height: UiConstants.space4), + Row( + children: [ + Expanded( + child: UiTextField( + label: labels.start_time, + controller: TextEditingController( + text: position['start_time'], + ), + onChanged: (v) => onUpdate('start_time', v), + ), + ), + const SizedBox(width: UiConstants.space2), + Expanded( + child: UiTextField( + label: labels.end_time, + controller: TextEditingController( + text: position['end_time'], + ), + onChanged: (v) => onUpdate('end_time', v), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/apps/packages/features/client/home/lib/src/presentation/widgets/spending_widget.dart b/apps/packages/features/client/home/lib/src/presentation/widgets/spending_widget.dart new file mode 100644 index 00000000..6e23c2ad --- /dev/null +++ b/apps/packages/features/client/home/lib/src/presentation/widgets/spending_widget.dart @@ -0,0 +1,175 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A widget that displays spending insights for the client. +class SpendingWidget extends StatelessWidget { + /// The spending this week. + final double weeklySpending; + + /// The spending for the next 7 days. + final double next7DaysSpending; + + /// The number of shifts this week. + final int weeklyShifts; + + /// The number of scheduled shifts for next 7 days. + final int next7DaysScheduled; + + /// Creates a [SpendingWidget]. + const SpendingWidget({ + super.key, + required this.weeklySpending, + required this.next7DaysSpending, + required this.weeklyShifts, + required this.next7DaysScheduled, + }); + + @override + Widget build(BuildContext context) { + final i18n = t.client_home; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + i18n.widgets.spending.toUpperCase(), + style: UiTypography.footnote1b.textSecondary.copyWith( + letterSpacing: 0.5, + ), + ), + const SizedBox(height: UiConstants.space2), + Container( + padding: const EdgeInsets.all(UiConstants.space3), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [UiColors.primary, Color(0xFF0830B8)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: UiConstants.radiusLg, + boxShadow: [ + BoxShadow( + color: UiColors.primary.withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'This Week', + style: TextStyle(color: Colors.white70, fontSize: 9), + ), + const SizedBox(height: UiConstants.space1), + Text( + '\$${weeklySpending.toStringAsFixed(0)}', + style: UiTypography.headline3m.copyWith( + color: UiColors.white, + fontWeight: FontWeight.bold, + ), + ), + Text( + '$weeklyShifts shifts', + style: TextStyle( + color: Colors.white.withOpacity(0.6), + fontSize: 9, + ), + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const Text( + 'Next 7 Days', + style: TextStyle(color: Colors.white70, fontSize: 9), + ), + const SizedBox(height: UiConstants.space1), + Text( + '\$${next7DaysSpending.toStringAsFixed(0)}', + style: UiTypography.headline4m.copyWith( + color: UiColors.white, + fontWeight: FontWeight.bold, + ), + ), + Text( + '$next7DaysScheduled scheduled', + style: TextStyle( + color: Colors.white.withOpacity(0.6), + fontSize: 9, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: UiConstants.space3), + Container( + padding: const EdgeInsets.only(top: UiConstants.space3), + decoration: const BoxDecoration( + border: Border(top: BorderSide(color: Colors.white24)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: const Center( + child: Icon( + UiIcons.sparkles, + color: UiColors.white, + size: 14, + ), + ), + ), + const SizedBox(width: UiConstants.space2), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '💡 ' + + i18n.dashboard.insight_lightbulb(amount: '180'), + style: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 1), + Text( + i18n.dashboard.insight_tip, + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 9, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/apps/packages/features/client/home/pubspec.yaml b/apps/packages/features/client/home/pubspec.yaml new file mode 100644 index 00000000..3ee5f5f7 --- /dev/null +++ b/apps/packages/features/client/home/pubspec.yaml @@ -0,0 +1,31 @@ +name: client_home +description: Home screen and dashboard for the client application. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^8.1.0 + flutter_modular: ^6.3.0 + equatable: ^2.0.5 + lucide_icons: ^0.257.0 + + design_system: + path: ../../../design_system + core_localization: + path: ../../../core_localization + +dev_dependencies: + flutter_test: + sdk: flutter + bloc_test: ^9.1.0 + mocktail: ^1.0.0 + +flutter: + uses-material-design: true diff --git a/apps/packages/features/shared/template_feature/feature_manifest.md b/apps/packages/features/shared/template_feature/feature_manifest.md new file mode 100644 index 00000000..20dc21e3 --- /dev/null +++ b/apps/packages/features/shared/template_feature/feature_manifest.md @@ -0,0 +1,29 @@ +# Feature Manifest: Template Feature + +## Overview +**Feature Name:** Template Feature +**Package Path:** `packages/features/shared/template_feature` +**Owner:** [Team Name/Agent] + +## Responsibilities +* Describe what this feature does. +* Describe what this feature does NOT do. + +## Architecture +* **Domain**: + * `TemplateEntity` (imported from `krow_domain`) + * `TemplateRepositoryInterface` +* **Data**: + * `TemplateRepositoryImpl` (uses `krow_data_connect`) +* **Presentation**: + * `TemplatePage` + * `TemplateBloc` + +## Dependencies +* `krow_domain`: For entities. +* `krow_data_connect`: For backend mocking/access. +* `design_system`: For UI components. + +## Routes +* `/`: Main template page. +* `/:id`: Detail page (example). diff --git a/apps/packages/features/shared/template_feature/lib/src/data/repositories_impl/template_repository_impl.dart b/apps/packages/features/shared/template_feature/lib/src/data/repositories_impl/template_repository_impl.dart new file mode 100644 index 00000000..e9e5f82e --- /dev/null +++ b/apps/packages/features/shared/template_feature/lib/src/data/repositories_impl/template_repository_impl.dart @@ -0,0 +1,20 @@ +import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../../domain/repositories/template_repository_interface.dart'; + +class TemplateRepositoryImpl implements TemplateRepositoryInterface { + // In a real scenario, you might inject a specific DataSource here. + // For now, we can use the static/singleton mocks from krow_data_connect or inject them. + final AuthRepositoryMock _authMock; + + TemplateRepositoryImpl({AuthRepositoryMock? authMock}) + : _authMock = authMock ?? AuthRepositoryMock(); + + @override + Future getData() async { + // Mapping from DataConnect models to Domain models happens here if needed. + // For the mock stage, we just return the entity. + final result = await _authMock.currentUser.first; + return result; + } +} diff --git a/apps/packages/features/shared/template_feature/lib/src/domain/repositories/template_repository_interface.dart b/apps/packages/features/shared/template_feature/lib/src/domain/repositories/template_repository_interface.dart new file mode 100644 index 00000000..750fc81c --- /dev/null +++ b/apps/packages/features/shared/template_feature/lib/src/domain/repositories/template_repository_interface.dart @@ -0,0 +1,7 @@ +import 'package:krow_domain/krow_domain.dart'; + +/// Abstract interface for data access. +/// Must be implemented in the Data layer. +abstract interface class TemplateRepositoryInterface { + Future getData(); +} diff --git a/apps/packages/features/shared/template_feature/lib/src/domain/usecases/get_template_data_usecase.dart b/apps/packages/features/shared/template_feature/lib/src/domain/usecases/get_template_data_usecase.dart new file mode 100644 index 00000000..8abbd899 --- /dev/null +++ b/apps/packages/features/shared/template_feature/lib/src/domain/usecases/get_template_data_usecase.dart @@ -0,0 +1,13 @@ +import 'package:krow_core/krow_core.dart'; // Assuming Result/UseCases might be here later +import 'package:krow_domain/krow_domain.dart'; +import '../repositories/template_repository_interface.dart'; + +class GetTemplateDataUseCase { + final TemplateRepositoryInterface _repository; + + GetTemplateDataUseCase(this._repository); + + Future call() async { + return _repository.getData(); + } +} diff --git a/apps/packages/features/shared/template_feature/lib/src/presentation/blocs/template_bloc.dart b/apps/packages/features/shared/template_feature/lib/src/presentation/blocs/template_bloc.dart new file mode 100644 index 00000000..8808947c --- /dev/null +++ b/apps/packages/features/shared/template_feature/lib/src/presentation/blocs/template_bloc.dart @@ -0,0 +1,60 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../../domain/usecases/get_template_data_usecase.dart'; + +// Events +abstract class TemplateEvent extends Equatable { + const TemplateEvent(); + @override + List get props => []; +} + +class TemplateStarted extends TemplateEvent {} + +// States +abstract class TemplateState extends Equatable { + const TemplateState(); + @override + List get props => []; +} + +class TemplateInitial extends TemplateState {} +class TemplateLoading extends TemplateState {} +class TemplateLoaded extends TemplateState { + final User user; + const TemplateLoaded(this.user); + @override + List get props => [user]; +} +class TemplateError extends TemplateState { + final String message; + const TemplateError(this.message); + @override + List get props => [message]; +} + +// BLoC +class TemplateBloc extends Bloc { + final GetTemplateDataUseCase _getDataUseCase; + + TemplateBloc({required GetTemplateDataUseCase getDataUseCase}) + : _getDataUseCase = getDataUseCase, + super(TemplateInitial()) { + on(_onStarted); + } + + Future _onStarted(TemplateStarted event, Emitter emit) async { + emit(TemplateLoading()); + try { + final user = await _getDataUseCase(); + if (user != null) { + emit(TemplateLoaded(user)); + } else { + emit(const TemplateError('No data found')); + } + } catch (e) { + emit(TemplateError(e.toString())); + } + } +} diff --git a/apps/packages/features/shared/template_feature/lib/src/presentation/pages/template_page.dart b/apps/packages/features/shared/template_feature/lib/src/presentation/pages/template_page.dart new file mode 100644 index 00000000..d1edbc08 --- /dev/null +++ b/apps/packages/features/shared/template_feature/lib/src/presentation/pages/template_page.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:design_system/design_system.dart'; // Import Design System +import '../blocs/template_bloc.dart'; + +class TemplatePage extends StatelessWidget { + const TemplatePage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => Modular.get()..add(TemplateStarted()), + child: Scaffold( + appBar: AppBar(title: const Text('Template Feature')), + body: BlocBuilder( + builder: (context, state) { + if (state is TemplateLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is TemplateLoaded) { + return Center(child: Text('User: ${state.user.email}')); + } else if (state is TemplateError) { + return Center(child: Text('Error: ${state.message}')); + } + return const Center(child: Text('Welcome')); + }, + ), + ), + ); + } +} diff --git a/apps/packages/features/shared/template_feature/lib/template_feature.dart b/apps/packages/features/shared/template_feature/lib/template_feature.dart new file mode 100644 index 00000000..8bacf559 --- /dev/null +++ b/apps/packages/features/shared/template_feature/lib/template_feature.dart @@ -0,0 +1,29 @@ +library template_feature; + +import 'package:flutter_modular/flutter_modular.dart'; +import 'src/data/repositories_impl/template_repository_impl.dart'; +import 'src/domain/repositories/template_repository_interface.dart'; +import 'src/domain/usecases/get_template_data_usecase.dart'; +import 'src/presentation/blocs/template_bloc.dart'; +import 'src/presentation/pages/template_page.dart'; + +export 'src/presentation/pages/template_page.dart'; + +class TemplateFeatureModule extends Module { + @override + void binds(Injector i) { + // 1. Repositories + i.add(TemplateRepositoryImpl.new); + + // 2. Use Cases + i.add(GetTemplateDataUseCase.new); + + // 3. BLoCs + i.add(TemplateBloc.new); + } + + @override + void routes(r) { + r.child('/', child: (_) => const TemplatePage()); + } +} diff --git a/apps/packages/features/shared/template_feature/pubspec.yaml b/apps/packages/features/shared/template_feature/pubspec.yaml new file mode 100644 index 00000000..300ceef4 --- /dev/null +++ b/apps/packages/features/shared/template_feature/pubspec.yaml @@ -0,0 +1,32 @@ +name: template_feature +description: A template feature package following KROW architecture. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^8.1.0 + flutter_modular: ^6.3.0 + equatable: ^2.0.5 + + # Core Architecture + krow_domain: + path: ../../../domain + krow_data_connect: + path: ../../../data_connect + krow_core: + path: ../../../core + design_system: + path: ../../../design_system + +dev_dependencies: + flutter_test: + sdk: flutter + bloc_test: ^9.1.0 + mocktail: ^1.0.0 diff --git a/apps/packages/features/staff/authentication/feature_manifest.md b/apps/packages/features/staff/authentication/feature_manifest.md new file mode 100644 index 00000000..cd530b30 --- /dev/null +++ b/apps/packages/features/staff/authentication/feature_manifest.md @@ -0,0 +1,33 @@ +# Feature Manifest: Staff Authentication + +## Overview +**Feature Name:** Staff Authentication & Onboarding +**Package Path:** `packages/features/staff/authentication` + +## Responsibilities +* Handle user sign-up and log-in via Phone Auth. +* Verify OTP codes. +* Manage the Onboarding Wizard for new staff. +* Persist onboarding progress. + +## Architecture +* **Domain**: + * `AuthRepositoryInterface` + * `SignInWithPhoneUseCase` + * `VerifyOtpUseCase` +* **Data**: + * `AuthRepositoryImpl` (uses `AuthRepositoryMock` from `krow_data_connect`) +* **Presentation**: + * `AuthBloc`: Manages auth state (phone, otp, user status). + * `OnboardingBloc`: Manages wizard steps. + * Pages: `GetStartedPage`, `PhoneVerificationPage`, `ProfileSetupPage`. + +## Dependencies +* `krow_domain`: User entities. +* `krow_data_connect`: Auth mocks. +* `design_system`: UI components. + +## Routes +* `/`: Get Started (Welcome) +* `/phone-verification`: OTP Entry +* `/profile-setup`: Onboarding Wizard diff --git a/apps/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart new file mode 100644 index 00000000..d8935c46 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -0,0 +1,34 @@ +import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../../domain/repositories/auth_repository_interface.dart'; + +/// Implementation of [AuthRepositoryInterface]. +class AuthRepositoryImpl implements AuthRepositoryInterface { + final AuthRepositoryMock mock; + + AuthRepositoryImpl({required this.mock}); + + @override + Stream get currentUser => mock.currentUser; + + /// Signs in with a phone number and returns a verification ID. + @override + Future signInWithPhone({required String phoneNumber}) { + return mock.signInWithPhone(phoneNumber); + } + + /// Signs out the current user. + @override + Future signOut() { + return mock.signOut(); + } + + /// Verifies an OTP code and returns the authenticated user. + @override + Future verifyOtp({ + required String verificationId, + required String smsCode, + }) { + return mock.verifyOtp(verificationId, smsCode); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/domain/arguments/sign_in_with_phone_arguments.dart b/apps/packages/features/staff/authentication/lib/src/domain/arguments/sign_in_with_phone_arguments.dart new file mode 100644 index 00000000..08d95047 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/domain/arguments/sign_in_with_phone_arguments.dart @@ -0,0 +1,17 @@ +import 'package:krow_core/core.dart'; + +/// Represents the arguments required for the [SignInWithPhoneUseCase]. +/// +/// Encapsulates the phone number needed to initiate the sign-in process. +class SignInWithPhoneArguments extends UseCaseArgument { + /// The phone number to be used for sign-in or sign-up. + final String phoneNumber; + + /// Creates a [SignInWithPhoneArguments] instance. + /// + /// The [phoneNumber] is required. + const SignInWithPhoneArguments({required this.phoneNumber}); + + @override + List get props => [phoneNumber]; +} diff --git a/apps/packages/features/staff/authentication/lib/src/domain/arguments/verify_otp_arguments.dart b/apps/packages/features/staff/authentication/lib/src/domain/arguments/verify_otp_arguments.dart new file mode 100644 index 00000000..b5797126 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/domain/arguments/verify_otp_arguments.dart @@ -0,0 +1,24 @@ +import 'package:krow_core/core.dart'; + +/// Represents the arguments required for the [VerifyOtpUseCase]. +/// +/// Encapsulates the verification ID and the SMS code needed to verify +/// a phone number during the authentication process. +class VerifyOtpArguments extends UseCaseArgument { + /// The unique identifier received after requesting an OTP. + final String verificationId; + + /// The one-time password (OTP) sent to the user's phone. + final String smsCode; + + /// Creates a [VerifyOtpArguments] instance. + /// + /// Both [verificationId] and [smsCode] are required. + const VerifyOtpArguments({ + required this.verificationId, + required this.smsCode, + }); + + @override + List get props => [verificationId, smsCode]; +} diff --git a/apps/packages/features/staff/authentication/lib/src/domain/repositories/auth_repository_interface.dart b/apps/packages/features/staff/authentication/lib/src/domain/repositories/auth_repository_interface.dart new file mode 100644 index 00000000..19cfcb2e --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/domain/repositories/auth_repository_interface.dart @@ -0,0 +1,19 @@ +import 'package:krow_domain/krow_domain.dart'; + +/// Interface for authentication repository. +abstract interface class AuthRepositoryInterface { + Stream get currentUser; + + /// Signs in with a phone number and returns a verification ID. + Future signInWithPhone({required String phoneNumber}); + + /// Verifies the OTP code and returns the authenticated user. + Future verifyOtp({ + required String verificationId, + required String smsCode, + }); + + /// Signs out the current user. + Future signOut(); + // Future getStaffProfile(String userId); // Could be moved to a separate repository if needed, but useful here for routing logic. +} diff --git a/apps/packages/features/staff/authentication/lib/src/domain/ui_entities/auth_mode.dart b/apps/packages/features/staff/authentication/lib/src/domain/ui_entities/auth_mode.dart new file mode 100644 index 00000000..574d51e9 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/domain/ui_entities/auth_mode.dart @@ -0,0 +1,8 @@ +/// Represents the authentication mode: either signing up or logging in. +enum AuthMode { + /// User is creating a new account. + signup, + + /// User is logging into an existing account. + login, +} diff --git a/apps/packages/features/staff/authentication/lib/src/domain/usecases/sign_in_with_phone_usecase.dart b/apps/packages/features/staff/authentication/lib/src/domain/usecases/sign_in_with_phone_usecase.dart new file mode 100644 index 00000000..061fd08e --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/domain/usecases/sign_in_with_phone_usecase.dart @@ -0,0 +1,21 @@ +import 'package:krow_core/core.dart'; +import '../arguments/sign_in_with_phone_arguments.dart'; +import '../repositories/auth_repository_interface.dart'; + +/// Use case for signing in with a phone number. +/// +/// This use case delegates the sign-in logic to the [AuthRepositoryInterface]. +class SignInWithPhoneUseCase + implements UseCase { + final AuthRepositoryInterface _repository; + + /// Creates a [SignInWithPhoneUseCase]. + /// + /// Requires an [AuthRepositoryInterface] to interact with the authentication data source. + SignInWithPhoneUseCase(this._repository); + + @override + Future call(SignInWithPhoneArguments arguments) { + return _repository.signInWithPhone(phoneNumber: arguments.phoneNumber); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/domain/usecases/verify_otp_usecase.dart b/apps/packages/features/staff/authentication/lib/src/domain/usecases/verify_otp_usecase.dart new file mode 100644 index 00000000..5e11a01d --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/domain/usecases/verify_otp_usecase.dart @@ -0,0 +1,24 @@ +import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../arguments/verify_otp_arguments.dart'; +import '../repositories/auth_repository_interface.dart'; + +/// Use case for verifying an OTP code. +/// +/// This use case delegates the OTP verification logic to the [AuthRepositoryInterface]. +class VerifyOtpUseCase implements UseCase { + final AuthRepositoryInterface _repository; + + /// Creates a [VerifyOtpUseCase]. + /// + /// Requires an [AuthRepositoryInterface] to interact with the authentication data source. + VerifyOtpUseCase(this._repository); + + @override + Future call(VerifyOtpArguments arguments) { + return _repository.verifyOtp( + verificationId: arguments.verificationId, + smsCode: arguments.smsCode, + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_bloc.dart b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_bloc.dart new file mode 100644 index 00000000..5044b5ce --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_bloc.dart @@ -0,0 +1,111 @@ +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:bloc/bloc.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../../domain/arguments/sign_in_with_phone_arguments.dart'; +import '../../domain/arguments/verify_otp_arguments.dart'; +import '../../domain/usecases/sign_in_with_phone_usecase.dart'; +import '../../domain/usecases/verify_otp_usecase.dart'; +import 'auth_event.dart'; +import 'auth_state.dart'; + +/// BLoC responsible for handling authentication logic. +class AuthBloc extends Bloc implements Disposable { + /// The use case for signing in with a phone number. + final SignInWithPhoneUseCase _signInUseCase; + + /// The use case for verifying an OTP. + final VerifyOtpUseCase _verifyOtpUseCase; + + /// Creates an [AuthBloc]. + AuthBloc({ + required SignInWithPhoneUseCase signInUseCase, + required VerifyOtpUseCase verifyOtpUseCase, + }) : _signInUseCase = signInUseCase, + _verifyOtpUseCase = verifyOtpUseCase, + super(const AuthState()) { + on(_onSignInRequested); + on(_onOtpSubmitted); + on(_onErrorCleared); + on(_onOtpUpdated); + on(_onPhoneUpdated); + } + + /// Clears any authentication error from the state. + void _onErrorCleared(AuthErrorCleared event, Emitter emit) { + emit(state.copyWith(status: AuthStatus.codeSent, errorMessage: null)); + } + + /// Updates the internal OTP state without triggering a submission. + void _onOtpUpdated(AuthOtpUpdated event, Emitter emit) { + emit( + state.copyWith( + otp: event.otp, + status: AuthStatus.codeSent, + errorMessage: null, + ), + ); + } + + /// Updates the internal phone number state without triggering a submission. + void _onPhoneUpdated(AuthPhoneUpdated event, Emitter emit) { + emit(state.copyWith(phoneNumber: event.phoneNumber, errorMessage: null)); + } + + /// Handles the sign-in request, initiating the phone authentication process. + Future _onSignInRequested( + AuthSignInRequested event, + Emitter emit, + ) async { + emit( + state.copyWith( + status: AuthStatus.loading, + mode: event.mode, + phoneNumber: event.phoneNumber, + ), + ); + try { + final String? verificationId = await _signInUseCase( + SignInWithPhoneArguments( + phoneNumber: event.phoneNumber ?? state.phoneNumber, + ), + ); + emit( + state.copyWith( + status: AuthStatus.codeSent, + verificationId: verificationId, + ), + ); + } catch (e) { + emit( + state.copyWith(status: AuthStatus.error, errorMessage: e.toString()), + ); + } + } + + /// Handles OTP submission and verification. + Future _onOtpSubmitted( + AuthOtpSubmitted event, + Emitter emit, + ) async { + emit(state.copyWith(status: AuthStatus.loading)); + try { + final User? user = await _verifyOtpUseCase( + VerifyOtpArguments( + verificationId: event.verificationId, + smsCode: event.smsCode, + ), + ); + emit(state.copyWith(status: AuthStatus.authenticated, user: user)); + } catch (e) { + emit( + state.copyWith(status: AuthStatus.error, errorMessage: e.toString()), + ); + } + } + + /// Disposes the BLoC resources. + @override + void dispose() { + close(); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_event.dart b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_event.dart new file mode 100644 index 00000000..ad47bfc6 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_event.dart @@ -0,0 +1,65 @@ +import 'package:equatable/equatable.dart'; +import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart'; + +/// Abstract base class for all authentication events. +abstract class AuthEvent extends Equatable { + const AuthEvent(); + @override + List get props => []; +} + +/// Event for requesting a sign-in with a phone number. +class AuthSignInRequested extends AuthEvent { + /// The phone number provided by the user. + final String? phoneNumber; + + /// The authentication mode (login or signup). + final AuthMode mode; + + const AuthSignInRequested({this.phoneNumber, required this.mode}); + + @override + List get props => [mode]; +} + +/// Event for submitting an OTP (One-Time Password) for verification. +/// +/// This event is dispatched after the user has received an OTP and +/// submits it for verification. +class AuthOtpSubmitted extends AuthEvent { + /// The verification ID received after the phone number submission. + final String verificationId; + + /// The SMS code (OTP) entered by the user. + final String smsCode; + + const AuthOtpSubmitted({required this.verificationId, required this.smsCode}); + + @override + List get props => [verificationId, smsCode]; +} + +/// Event for clearing any authentication error in the state. +class AuthErrorCleared extends AuthEvent {} + +/// Event for updating the current draft OTP in the state. +class AuthOtpUpdated extends AuthEvent { + /// The current draft OTP. + final String otp; + + const AuthOtpUpdated(this.otp); + + @override + List get props => [otp]; +} + +/// Event for updating the current draft phone number in the state. +class AuthPhoneUpdated extends AuthEvent { + /// The current draft phone number. + final String phoneNumber; + + const AuthPhoneUpdated(this.phoneNumber); + + @override + List get props => [phoneNumber]; +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_state.dart b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_state.dart new file mode 100644 index 00000000..8e6248ba --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/auth_state.dart @@ -0,0 +1,93 @@ +import 'package:equatable/equatable.dart'; +import 'package:krow_domain/krow_domain.dart'; +import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart'; + +/// Enum representing the current status of the authentication process. +enum AuthStatus { + /// Initial state, awaiting phone number entry. + initial, + + /// Authentication operation in progress. + loading, + + /// OTP has been sent, awaiting code verification. + codeSent, + + /// User has been successfully authenticated. + authenticated, + + /// An error occurred during the process. + error, +} + +/// A unified state class for the authentication process. +class AuthState extends Equatable { + /// The current status of the authentication flow. + final AuthStatus status; + + /// The ID received from the authentication service, used to verify the OTP. + final String? verificationId; + + /// The authentication mode (login or signup). + final AuthMode mode; + + /// The current draft OTP entered by the user. + final String otp; + + /// The phone number entered by the user. + final String phoneNumber; + + /// A descriptive message for any error that occurred. + final String? errorMessage; + + /// The authenticated user's data (available when status is [AuthStatus.authenticated]). + final User? user; + + const AuthState({ + this.status = AuthStatus.initial, + this.verificationId, + this.mode = AuthMode.login, + this.otp = '', + this.phoneNumber = '', + this.errorMessage, + this.user, + }); + + @override + List get props => [ + status, + verificationId, + mode, + otp, + phoneNumber, + errorMessage, + user, + ]; + + /// Convenient helper to check if the status is [AuthStatus.loading]. + bool get isLoading => status == AuthStatus.loading; + + /// Convenient helper to check if the status is [AuthStatus.error]. + bool get hasError => status == AuthStatus.error; + + /// Copies the state with optional new values. + AuthState copyWith({ + AuthStatus? status, + String? verificationId, + AuthMode? mode, + String? otp, + String? phoneNumber, + String? errorMessage, + User? user, + }) { + return AuthState( + status: status ?? this.status, + verificationId: verificationId ?? this.verificationId, + mode: mode ?? this.mode, + otp: otp ?? this.otp, + phoneNumber: phoneNumber ?? this.phoneNumber, + errorMessage: errorMessage ?? this.errorMessage, + user: user ?? this.user, + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_bloc.dart b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_bloc.dart new file mode 100644 index 00000000..94ff1f20 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_bloc.dart @@ -0,0 +1,100 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'profile_setup_event.dart'; +import 'profile_setup_state.dart'; + +export 'profile_setup_event.dart'; +export 'profile_setup_state.dart'; + +/// BLoC responsible for managing the profile setup state and logic. +class ProfileSetupBloc extends Bloc { + /// Creates a [ProfileSetupBloc] with an initial state. + ProfileSetupBloc() : super(const ProfileSetupState()) { + on(_onFullNameChanged); + on(_onBioChanged); + on(_onLocationsChanged); + on(_onDistanceChanged); + on(_onSkillsChanged); + on(_onIndustriesChanged); + on(_onSubmitted); + } + + /// Handles the [ProfileSetupFullNameChanged] event. + void _onFullNameChanged( + ProfileSetupFullNameChanged event, + Emitter emit, + ) { + emit(state.copyWith(fullName: event.fullName)); + } + + /// Handles the [ProfileSetupBioChanged] event. + void _onBioChanged( + ProfileSetupBioChanged event, + Emitter emit, + ) { + emit(state.copyWith(bio: event.bio)); + } + + /// Handles the [ProfileSetupLocationsChanged] event. + void _onLocationsChanged( + ProfileSetupLocationsChanged event, + Emitter emit, + ) { + emit(state.copyWith(preferredLocations: event.locations)); + } + + /// Handles the [ProfileSetupDistanceChanged] event. + void _onDistanceChanged( + ProfileSetupDistanceChanged event, + Emitter emit, + ) { + emit(state.copyWith(maxDistanceMiles: event.distance)); + } + + /// Handles the [ProfileSetupSkillsChanged] event. + void _onSkillsChanged( + ProfileSetupSkillsChanged event, + Emitter emit, + ) { + emit(state.copyWith(skills: event.skills)); + } + + /// Handles the [ProfileSetupIndustriesChanged] event. + void _onIndustriesChanged( + ProfileSetupIndustriesChanged event, + Emitter emit, + ) { + emit(state.copyWith(industries: event.industries)); + } + + /// Handles the [ProfileSetupSubmitted] event. + Future _onSubmitted( + ProfileSetupSubmitted event, + Emitter emit, + ) async { + emit(state.copyWith(status: ProfileSetupStatus.loading)); + + try { + // In a real app, we would send this data to a UseCase + debugPrint('Submitting Profile:'); + debugPrint('Name: ${state.fullName}'); + debugPrint('Bio: ${state.bio}'); + debugPrint('Locations: ${state.preferredLocations}'); + debugPrint('Distance: ${state.maxDistanceMiles}'); + debugPrint('Skills: ${state.skills}'); + debugPrint('Industries: ${state.industries}'); + + // Mocking profile creation delay + await Future.delayed(const Duration(milliseconds: 1500)); + + emit(state.copyWith(status: ProfileSetupStatus.success)); + } catch (e) { + emit( + state.copyWith( + status: ProfileSetupStatus.failure, + errorMessage: e.toString(), + ), + ); + } + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_event.dart b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_event.dart new file mode 100644 index 00000000..59acfa1b --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_event.dart @@ -0,0 +1,87 @@ +import 'package:equatable/equatable.dart'; + +/// Base class for all profile setup events. +abstract class ProfileSetupEvent extends Equatable { + const ProfileSetupEvent(); + + @override + List get props => []; +} + +/// Event triggered when the full name changes. +class ProfileSetupFullNameChanged extends ProfileSetupEvent { + /// The new full name value. + final String fullName; + + /// Creates a [ProfileSetupFullNameChanged] event. + const ProfileSetupFullNameChanged(this.fullName); + + @override + List get props => [fullName]; +} + +/// Event triggered when the bio changes. +class ProfileSetupBioChanged extends ProfileSetupEvent { + /// The new bio value. + final String bio; + + /// Creates a [ProfileSetupBioChanged] event. + const ProfileSetupBioChanged(this.bio); + + @override + List get props => [bio]; +} + +/// Event triggered when the preferred locations change. +class ProfileSetupLocationsChanged extends ProfileSetupEvent { + /// The new list of locations. + final List locations; + + /// Creates a [ProfileSetupLocationsChanged] event. + const ProfileSetupLocationsChanged(this.locations); + + @override + List get props => [locations]; +} + +/// Event triggered when the max distance changes. +class ProfileSetupDistanceChanged extends ProfileSetupEvent { + /// The new max distance value in miles. + final double distance; + + /// Creates a [ProfileSetupDistanceChanged] event. + const ProfileSetupDistanceChanged(this.distance); + + @override + List get props => [distance]; +} + +/// Event triggered when the skills change. +class ProfileSetupSkillsChanged extends ProfileSetupEvent { + /// The new list of selected skills. + final List skills; + + /// Creates a [ProfileSetupSkillsChanged] event. + const ProfileSetupSkillsChanged(this.skills); + + @override + List get props => [skills]; +} + +/// Event triggered when the industries change. +class ProfileSetupIndustriesChanged extends ProfileSetupEvent { + /// The new list of selected industries. + final List industries; + + /// Creates a [ProfileSetupIndustriesChanged] event. + const ProfileSetupIndustriesChanged(this.industries); + + @override + List get props => [industries]; +} + +/// Event triggered when the profile submission is requested. +class ProfileSetupSubmitted extends ProfileSetupEvent { + /// Creates a [ProfileSetupSubmitted] event. + const ProfileSetupSubmitted(); +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_state.dart b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_state.dart new file mode 100644 index 00000000..bcfc7832 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/blocs/profile_setup/profile_setup_state.dart @@ -0,0 +1,78 @@ +import 'package:equatable/equatable.dart'; + +/// Enum defining the status of the profile setup process. +enum ProfileSetupStatus { initial, loading, success, failure } + +/// State for the ProfileSetupBloc. +class ProfileSetupState extends Equatable { + /// The user's full name. + final String fullName; + + /// The user's bio or short description. + final String bio; + + /// List of preferred work locations (e.g., cities, zip codes). + final List preferredLocations; + + /// Maximum distance in miles the user is willing to travel. + final double maxDistanceMiles; + + /// List of skills selected by the user. + final List skills; + + /// List of industries selected by the user. + final List industries; + + /// The current status of the profile setup process. + final ProfileSetupStatus status; + + /// Error message if the status is [ProfileSetupStatus.failure]. + final String? errorMessage; + + /// Creates a [ProfileSetupState] instance. + const ProfileSetupState({ + this.fullName = '', + this.bio = '', + this.preferredLocations = const [], + this.maxDistanceMiles = 25, + this.skills = const [], + this.industries = const [], + this.status = ProfileSetupStatus.initial, + this.errorMessage, + }); + + /// Creates a copy of the current state with updated values. + ProfileSetupState copyWith({ + String? fullName, + String? bio, + List? preferredLocations, + double? maxDistanceMiles, + List? skills, + List? industries, + ProfileSetupStatus? status, + String? errorMessage, + }) { + return ProfileSetupState( + fullName: fullName ?? this.fullName, + bio: bio ?? this.bio, + preferredLocations: preferredLocations ?? this.preferredLocations, + maxDistanceMiles: maxDistanceMiles ?? this.maxDistanceMiles, + skills: skills ?? this.skills, + industries: industries ?? this.industries, + status: status ?? this.status, + errorMessage: errorMessage, + ); + } + + @override + List get props => [ + fullName, + bio, + preferredLocations, + maxDistanceMiles, + skills, + industries, + status, + errorMessage, + ]; +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/navigation/auth_navigator.dart b/apps/packages/features/staff/authentication/lib/src/presentation/navigation/auth_navigator.dart new file mode 100644 index 00000000..66cfd92a --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/navigation/auth_navigator.dart @@ -0,0 +1,21 @@ +import 'package:flutter_modular/flutter_modular.dart'; +import '../../domain/ui_entities/auth_mode.dart'; + +/// Extension on [IModularNavigator] to provide strongly-typed navigation +/// for the staff authentication feature. +extension AuthNavigator on IModularNavigator { + /// Navigates to the phone verification page. + void pushPhoneVerification(AuthMode mode) { + pushNamed('./phone-verification', arguments: {'mode': mode.name}); + } + + /// Navigates to the profile setup page, replacing the current route. + void pushReplacementProfileSetup() { + pushReplacementNamed('./profile-setup'); + } + + /// Navigates to the worker home (external to this module). + void pushWorkerHome() { + pushNamed('/worker-home'); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/pages/get_started_page.dart b/apps/packages/features/staff/authentication/lib/src/presentation/pages/get_started_page.dart new file mode 100644 index 00000000..9132ab92 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/pages/get_started_page.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart'; +import '../navigation/auth_navigator.dart'; // Import the extension +import '../widgets/get_started_page/get_started_actions.dart'; +import '../widgets/get_started_page/get_started_background.dart'; +import '../widgets/get_started_page/get_started_header.dart'; + +/// The entry point page for staff authentication. +/// +/// This page provides the user with the initial options to either sign up +/// for a new account or log in to an existing one. It uses a series of +/// sub-widgets to maintain a clean and modular structure. +class GetStartedPage extends StatelessWidget { + /// Creates a [GetStartedPage]. + const GetStartedPage({super.key}); + + /// On sign up pressed callback. + void onSignUpPressed() { + Modular.to.pushPhoneVerification(AuthMode.signup); + } + + /// On login pressed callback. + void onLoginPressed() { + Modular.to.pushPhoneVerification(AuthMode.login); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Column( + children: [ + // Background + const Expanded(child: GetStartedBackground()), + + // Content Overlay + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Main text and actions + const GetStartedHeader(), + + const SizedBox(height: 48), + + // Actions + GetStartedActions( + onSignUpPressed: onSignUpPressed, + onLoginPressed: onLoginPressed, + ), + + const SizedBox(height: 32), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/pages/phone_verification_page.dart b/apps/packages/features/staff/authentication/lib/src/presentation/pages/phone_verification_page.dart new file mode 100644 index 00000000..68efe765 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/pages/phone_verification_page.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:design_system/design_system.dart'; +import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_event.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_state.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart'; +import '../widgets/phone_verification_page/phone_input.dart'; +import '../widgets/phone_verification_page/otp_verification.dart'; +import 'package:staff_authentication/staff_authentication.dart'; +import '../navigation/auth_navigator.dart'; // Import the extension + +/// A combined page for phone number entry and OTP verification. +/// +/// This page coordinates the authentication flow by switching between +/// [PhoneInput] and [OtpVerification] based on the current [AuthState]. +class PhoneVerificationPage extends StatelessWidget { + /// The authentication mode (login or signup). + final AuthMode mode; + + /// Creates a [PhoneVerificationPage]. + const PhoneVerificationPage({super.key, required this.mode}); + + /// Handles the request to send a verification code to the provided phone number. + void _onSendCode({ + required BuildContext context, + required String phoneNumber, + }) { + if (phoneNumber.length == 10) { + BlocProvider.of( + context, + ).add(AuthSignInRequested(phoneNumber: '+1$phoneNumber', mode: mode)); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + t.staff_authentication.phone_verification_page.validation_error, + ), + ), + ); + } + } + + /// Handles the submission of the OTP code. + void _onOtpSubmitted({ + required BuildContext context, + required String otp, + required String verificationId, + }) { + BlocProvider.of( + context, + ).add(AuthOtpSubmitted(verificationId: verificationId, smsCode: otp)); + } + + /// Handles the request to resend the verification code using the phone number in the state. + void _onResend({required BuildContext context}) { + BlocProvider.of(context).add(AuthSignInRequested(mode: mode)); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => Modular.get(), + child: Builder( + builder: (context) { + return BlocListener( + listener: (context, state) { + if (state.status == AuthStatus.authenticated) { + if (state.mode == AuthMode.signup) { + Modular.to.pushReplacementProfileSetup(); + } else { + Modular.to.pushWorkerHome(); + } + } + }, + child: BlocBuilder( + builder: (context, state) { + // Check if we are in the OTP step + final bool isOtpStep = + state.status == AuthStatus.codeSent || + (state.status == AuthStatus.error && + state.verificationId != null) || + (state.status == AuthStatus.loading && + state.verificationId != null); + + return Scaffold( + appBar: const UiAppBar( + centerTitle: true, + showBackButton: true, + ), + body: SafeArea( + child: isOtpStep + ? OtpVerification( + state: state, + onOtpSubmitted: (otp) => _onOtpSubmitted( + context: context, + otp: otp, + verificationId: state.verificationId ?? '', + ), + onResend: () => _onResend(context: context), + onContinue: () => _onOtpSubmitted( + context: context, + otp: state.otp, + verificationId: state.verificationId ?? '', + ), + ) + : PhoneInput( + state: state, + onSendCode: () => _onSendCode( + context: context, + phoneNumber: state.phoneNumber, + ), + ), + ), + ); + }, + ), + ); + }, + ), + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/pages/profile_setup_page.dart b/apps/packages/features/staff/authentication/lib/src/presentation/pages/profile_setup_page.dart new file mode 100644 index 00000000..0704f6d4 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/pages/profile_setup_page.dart @@ -0,0 +1,236 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_modular/flutter_modular.dart' + hide ModularWatchExtension; +import '../blocs/profile_setup/profile_setup_bloc.dart'; +import '../widgets/profile_setup_page/profile_setup_basic_info.dart'; +import '../widgets/profile_setup_page/profile_setup_location.dart'; +import '../widgets/profile_setup_page/profile_setup_experience.dart'; +import '../widgets/profile_setup_page/profile_setup_header.dart'; +import 'package:staff_authentication/staff_authentication.dart'; +import '../navigation/auth_navigator.dart'; // Import the extension + +/// Page for setting up the user profile after authentication. +class ProfileSetupPage extends StatefulWidget { + const ProfileSetupPage({super.key}); + + @override + State createState() => _ProfileSetupPageState(); +} + +class _ProfileSetupPageState extends State { + /// Current step index. + int _currentStep = 0; + + /// List of steps in the profile setup process. + List> get _steps => [ + { + 'id': 'basic', + 'title': t.staff_authentication.profile_setup_page.steps.basic, + 'icon': UiIcons.user, + }, + { + 'id': 'location', + 'title': t.staff_authentication.profile_setup_page.steps.location, + 'icon': UiIcons.mapPin, + }, + { + 'id': 'experience', + 'title': t.staff_authentication.profile_setup_page.steps.experience, + 'icon': UiIcons.briefcase, + }, + ]; + + /// Handles the "Next" button tap logic. + void _handleNext({ + required BuildContext context, + required ProfileSetupState state, + required int stepsCount, + }) { + if (_currStepValid(state: state)) { + if (_currentStep < stepsCount - 1) { + setState(() => _currentStep++); + } else { + BlocProvider.of( + context, + ).add(const ProfileSetupSubmitted()); + } + } + } + + /// Handles the "Back" button tap logic. + void _handleBack() { + if (_currentStep > 0) { + setState(() => _currentStep--); + } + } + + /// Checks if the current step is valid. + bool _currStepValid({required ProfileSetupState state}) { + switch (_currentStep) { + case 0: + return state.fullName.trim().length >= 2; + case 1: + return state.preferredLocations.isNotEmpty; + case 2: + return state.skills.isNotEmpty; + default: + return true; + } + } + + @override + /// Builds the profile setup page UI. + Widget build(BuildContext context) { + final steps = _steps; + + // Calculate progress + final double progress = (_currentStep + 1) / steps.length; + + return BlocProvider( + create: (context) => Modular.get(), + child: BlocConsumer( + listener: (context, state) { + if (state.status == ProfileSetupStatus.success) { + Modular.to.pushWorkerHome(); + } else if (state.status == ProfileSetupStatus.failure) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + state.errorMessage ?? + t.staff_authentication.profile_setup_page.error_occurred, + ), + ), + ); + } + }, + builder: (context, state) { + final isCreatingProfile = state.status == ProfileSetupStatus.loading; + + return Scaffold( + body: SafeArea( + child: Column( + children: [ + // Progress Bar + LinearProgressIndicator(value: progress), + + // Header (Back + Step Count) + ProfileSetupHeader( + currentStep: _currentStep, + totalSteps: steps.length, + onBackTap: _handleBack, + ), + + // Step Indicators + UiStepIndicator( + stepIcons: steps + .map((step) => step['icon'] as IconData) + .toList(), + currentStep: _currentStep, + ), + + // Content Area + Expanded( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: SingleChildScrollView( + key: ValueKey(_currentStep), + padding: const EdgeInsets.all(UiConstants.space6), + child: _buildStepContent( + context: context, + state: state, + ), + ), + ), + ), + + // Footer + Container( + padding: const EdgeInsets.all(UiConstants.space6), + decoration: const BoxDecoration( + border: Border( + top: BorderSide(color: UiColors.separatorSecondary), + ), + ), + child: isCreatingProfile + ? ElevatedButton( + onPressed: null, + child: const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ) + : UiButton.primary( + text: _currentStep == steps.length - 1 + ? t + .staff_authentication + .profile_setup_page + .complete_setup_button + : t.common.continue_text, + trailingIcon: _currentStep < steps.length - 1 + ? UiIcons.arrowRight + : null, + onPressed: _currStepValid(state: state) + ? () => _handleNext( + context: context, + state: state, + stepsCount: steps.length, + ) + : null, + ), + ), + ], + ), + ), + ); + }, + ), + ); + } + + /// Builds the content for the current step. + Widget _buildStepContent({ + required BuildContext context, + required ProfileSetupState state, + }) { + switch (_currentStep) { + case 0: + return ProfileSetupBasicInfo( + fullName: state.fullName, + bio: state.bio, + onFullNameChanged: (val) => BlocProvider.of( + context, + ).add(ProfileSetupFullNameChanged(val)), + onBioChanged: (val) => BlocProvider.of( + context, + ).add(ProfileSetupBioChanged(val)), + ); + case 1: + return ProfileSetupLocation( + preferredLocations: state.preferredLocations, + maxDistanceMiles: state.maxDistanceMiles, + onLocationsChanged: (val) => BlocProvider.of( + context, + ).add(ProfileSetupLocationsChanged(val)), + onDistanceChanged: (val) => BlocProvider.of( + context, + ).add(ProfileSetupDistanceChanged(val)), + ); + case 2: + return ProfileSetupExperience( + skills: state.skills, + industries: state.industries, + onSkillsChanged: (val) => BlocProvider.of( + context, + ).add(ProfileSetupSkillsChanged(val)), + onIndustriesChanged: (val) => BlocProvider.of( + context, + ).add(ProfileSetupIndustriesChanged(val)), + ); + default: + return const SizedBox.shrink(); + } + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/common/auth_trouble_link.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/common/auth_trouble_link.dart new file mode 100644 index 00000000..8adf684f --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/common/auth_trouble_link.dart @@ -0,0 +1,27 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A common widget that displays a "Having trouble? Contact Support" link. +class AuthTroubleLink extends StatelessWidget { + /// Creates an [AuthTroubleLink]. + const AuthTroubleLink({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: UiConstants.space1, + children: [ + Text( + t.staff_authentication.common.trouble_question, + style: UiTypography.body2r.textSecondary, + ), + Text( + t.staff_authentication.common.contact_support, + style: UiTypography.body2b.textLink, + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/common/section_title_subtitle.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/common/section_title_subtitle.dart new file mode 100644 index 00000000..e4e75e76 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/common/section_title_subtitle.dart @@ -0,0 +1,32 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A widget for displaying a section title and subtitle +class SectionTitleSubtitle extends StatelessWidget { + /// The title of the section + final String title; + + /// The subtitle of the section + final String subtitle; + + const SectionTitleSubtitle({ + super.key, + required this.title, + required this.subtitle, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: UiConstants.space1, + children: [ + // Title + Text(title, style: UiTypography.headline1m), + + // Subtitle + Text(subtitle, style: UiTypography.body2r.textSecondary), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_actions.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_actions.dart new file mode 100644 index 00000000..ac8878b0 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_actions.dart @@ -0,0 +1,42 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget that displays the primary action buttons (Sign Up and Log In) +/// for the Get Started page. +class GetStartedActions extends StatelessWidget { + /// Void callback for when the Sign Up button is pressed. + final VoidCallback onSignUpPressed; + + /// Void callback for when the Log In button is pressed. + final VoidCallback onLoginPressed; + + /// Creates a [GetStartedActions]. + const GetStartedActions({ + super.key, + required this.onSignUpPressed, + required this.onLoginPressed, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Sign Up Button + UiButton.primary( + text: t.staff_authentication.get_started_page.sign_up_button, + onPressed: onSignUpPressed, + ), + + const SizedBox(height: 12), + + // Log In Button + UiButton.secondary( + text: t.staff_authentication.get_started_page.log_in_button, + onPressed: onLoginPressed, + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_background.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_background.dart new file mode 100644 index 00000000..bd9c6376 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_background.dart @@ -0,0 +1,51 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A widget that displays the background for the Get Started page. +class GetStartedBackground extends StatelessWidget { + /// Creates a [GetStartedBackground]. + const GetStartedBackground({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Column( + children: [ + // Logo + Image.asset(UiImageAssets.logoBlue, height: 40), + + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Hero Image + Container( + width: 288, + height: 288, + margin: const EdgeInsets.only(bottom: 32), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: UiColors.secondaryForeground.withAlpha( + 64, + ), // 0.5 opacity + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ClipOval( + child: Image.network( + 'https://images.unsplash.com/photo-1577219491135-ce391730fb2c?w=400&h=400&fit=crop&crop=faces', + fit: BoxFit.cover, + ), + ), + ), + ), + const SizedBox(height: 32), + ], + ), + ), + ], + ), + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_header.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_header.dart new file mode 100644 index 00000000..4ef77b9e --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_header.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:design_system/design_system.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget that displays the welcome text and description on the Get Started page. +class GetStartedHeader extends StatelessWidget { + /// Creates a [GetStartedHeader]. + const GetStartedHeader({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: UiTypography.displayM, + children: [ + TextSpan( + text: t.staff_authentication.get_started_page.title_part1, + ), + TextSpan( + text: t.staff_authentication.get_started_page.title_part2, + style: UiTypography.displayMb.textLink, + ), + ], + ), + ), + const SizedBox(height: 16), + Text( + t.staff_authentication.get_started_page.subtitle, + textAlign: TextAlign.center, + style: UiTypography.body1r.textSecondary, + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification.dart new file mode 100644 index 00000000..daf95684 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification.dart @@ -0,0 +1,65 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_state.dart'; +import 'otp_verification/otp_input_field.dart'; +import 'otp_verification/otp_resend_section.dart'; +import 'otp_verification/otp_verification_actions.dart'; +import 'otp_verification/otp_verification_header.dart'; + +/// A widget that displays the OTP verification UI. +class OtpVerification extends StatelessWidget { + /// The current state of the authentication process. + final AuthState state; + + /// Callback for when the OTP is submitted. + final ValueChanged onOtpSubmitted; + + /// Callback for when a new code is requested. + final VoidCallback onResend; + + /// Callback for the "Continue" action. + final VoidCallback onContinue; + + /// Creates an [OtpVerification]. + const OtpVerification({ + super.key, + required this.state, + required this.onOtpSubmitted, + required this.onResend, + required this.onContinue, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space6, + vertical: UiConstants.space8, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OtpVerificationHeader(phoneNumber: state.phoneNumber), + const SizedBox(height: UiConstants.space8), + OtpInputField( + error: state.errorMessage ?? '', + onCompleted: onOtpSubmitted, + ), + const SizedBox(height: UiConstants.space6), + OtpResendSection(onResend: onResend, hasError: state.hasError), + ], + ), + ), + ), + OtpVerificationActions( + isLoading: state.isLoading, + canSubmit: state.otp.length == 6, + onContinue: onContinue, + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_input_field.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_input_field.dart new file mode 100644 index 00000000..78c2d4ba --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_input_field.dart @@ -0,0 +1,127 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../blocs/auth_event.dart'; +import '../../../blocs/auth_bloc.dart'; + +/// A widget that displays a 6-digit OTP input field. +/// +/// This widget handles its own internal [TextEditingController]s and focus nodes. +/// It dispatches [AuthOtpUpdated] to the [AuthBloc] on every change. +class OtpInputField extends StatefulWidget { + /// Callback for when the OTP code is fully entered (6 digits). + final ValueChanged onCompleted; + + /// The error message to display, if any. + final String error; + + /// Creates an [OtpInputField]. + const OtpInputField({ + super.key, + required this.onCompleted, + required this.error, + }); + + @override + State createState() => _OtpInputFieldState(); +} + +class _OtpInputFieldState extends State { + final List _controllers = List.generate( + 6, + (_) => TextEditingController(), + ); + final List _focusNodes = List.generate(6, (_) => FocusNode()); + + @override + void dispose() { + for (final controller in _controllers) { + controller.dispose(); + } + for (final node in _focusNodes) { + node.dispose(); + } + super.dispose(); + } + + /// Helper getter to compute the current OTP code from all controllers. + String get _otpCode => _controllers.map((c) => c.text).join(); + + /// Handles changes to the OTP input fields. + void _onChanged({ + required BuildContext context, + required int index, + required String value, + }) { + if (value.length == 1 && index < 5) { + _focusNodes[index + 1].requestFocus(); + } + + // Notify the Bloc of the change + BlocProvider.of(context).add(AuthOtpUpdated(_otpCode)); + + if (_otpCode.length == 6) { + widget.onCompleted(_otpCode); + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(6, (index) { + return SizedBox( + width: 56, + height: 56, + child: TextField( + controller: _controllers[index], + focusNode: _focusNodes[index], + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textAlign: TextAlign.center, + maxLength: 1, + style: UiTypography.headline3m, + decoration: InputDecoration( + counterText: '', + border: OutlineInputBorder( + borderSide: BorderSide( + color: widget.error.isNotEmpty + ? UiColors.textError + : (_controllers[index].text.isNotEmpty + ? UiColors.primary + : UiColors.border), + width: 2, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: UiConstants.radiusMd, + borderSide: BorderSide( + color: widget.error.isNotEmpty + ? UiColors.textError + : (_controllers[index].text.isNotEmpty + ? UiColors.primary + : UiColors.border), + width: 2, + ), + ), + ), + onChanged: (value) => + _onChanged(context: context, index: index, value: value), + ), + ); + }), + ), + if (widget.error.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: UiConstants.space4), + child: Center( + child: Text(widget.error, style: UiTypography.body2r.textError), + ), + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_resend_section.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_resend_section.dart new file mode 100644 index 00000000..c6fecde7 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_resend_section.dart @@ -0,0 +1,75 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget that handles the OTP resend logic and countdown timer. +class OtpResendSection extends StatefulWidget { + /// Callback for when the resend link is pressed. + final VoidCallback onResend; + + /// Whether an error is currently displayed. (Used for layout tweaks in the original code) + final bool hasError; + + /// Creates an [OtpResendSection]. + const OtpResendSection({ + super.key, + required this.onResend, + this.hasError = false, + }); + + @override + State createState() => _OtpResendSectionState(); +} + +class _OtpResendSectionState extends State { + int _countdown = 30; + + @override + void initState() { + super.initState(); + _startCountdown(); + } + + /// Starts the countdown timer. + void _startCountdown() { + Future.delayed(const Duration(seconds: 1), () { + if (mounted && _countdown > 0) { + setState(() => _countdown--); + _startCountdown(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Center( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: widget.hasError + ? '' + : '${t.staff_authentication.otp_verification.did_not_get_code} ', + style: UiTypography.body2r.textSecondary, + ), + WidgetSpan( + child: GestureDetector( + onTap: _countdown > 0 ? null : widget.onResend, + child: Text( + _countdown > 0 + ? t.staff_authentication.otp_verification.resend_in( + seconds: _countdown.toString(), + ) + : t.staff_authentication.otp_verification.resend_code, + style: (_countdown > 0 + ? UiTypography.body2r.textSecondary + : UiTypography.body2b.textPrimary), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_actions.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_actions.dart new file mode 100644 index 00000000..ed9ad086 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_actions.dart @@ -0,0 +1,56 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/staff_authentication.dart'; +import '../../common/auth_trouble_link.dart'; + +/// A widget that displays the primary action button and trouble link for OTP verification. +class OtpVerificationActions extends StatelessWidget { + /// Whether the verification process is currently loading. + final bool isLoading; + + /// Whether the submit button should be enabled. + final bool canSubmit; + + /// Callback for when the Continue button is pressed. + final VoidCallback? onContinue; + + /// Creates an [OtpVerificationActions]. + const OtpVerificationActions({ + super.key, + required this.isLoading, + required this.canSubmit, + this.onContinue, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(UiConstants.space6), + decoration: const BoxDecoration( + border: Border( + top: BorderSide(color: UiColors.separatorSecondary, width: 1), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + isLoading + ? ElevatedButton( + onPressed: null, + child: const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ) + : UiButton.primary( + text: t.common.continue_text, + onPressed: canSubmit ? onContinue : null, + ), + const SizedBox(height: UiConstants.space4), + const AuthTroubleLink(), + ], + ), + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_header.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_header.dart new file mode 100644 index 00000000..ecc9a953 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_header.dart @@ -0,0 +1,44 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget that displays the title and subtitle for the OTP Verification page. +class OtpVerificationHeader extends StatelessWidget { + /// The phone number to which the code was sent. + final String phoneNumber; + + /// Creates an [OtpVerificationHeader]. + const OtpVerificationHeader({super.key, required this.phoneNumber}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.staff_authentication.phone_verification_page.enter_code_title, + style: UiTypography.headline1m, + ), + const SizedBox(height: UiConstants.space2), + Text.rich( + TextSpan( + text: t + .staff_authentication + .phone_verification_page + .code_sent_message, + style: UiTypography.body2r.textSecondary, + children: [ + TextSpan(text: '+1 $phoneNumber', style: UiTypography.body2b), + TextSpan( + text: t + .staff_authentication + .phone_verification_page + .code_sent_instruction, + ), + ], + ), + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input.dart new file mode 100644 index 00000000..082b09a7 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input.dart @@ -0,0 +1,54 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_event.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_state.dart'; +import 'phone_input/phone_input_actions.dart'; +import 'phone_input/phone_input_form_field.dart'; +import 'phone_input/phone_input_header.dart'; + +/// A widget that displays the phone number entry UI. +class PhoneInput extends StatelessWidget { + /// The current state of the authentication process. + final AuthState state; + + /// Callback for when the "Send Code" action is triggered. + final VoidCallback onSendCode; + + /// Creates a [PhoneInput]. + const PhoneInput({super.key, required this.state, required this.onSendCode}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space6, + vertical: UiConstants.space8, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const PhoneInputHeader(), + const SizedBox(height: UiConstants.space8), + PhoneInputFormField( + initialValue: state.phoneNumber, + error: state.errorMessage ?? '', + onChanged: (value) { + BlocProvider.of( + context, + ).add(AuthPhoneUpdated(value)); + }, + ), + ], + ), + ), + ), + PhoneInputActions(isLoading: state.isLoading, onSendCode: onSendCode), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_actions.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_actions.dart new file mode 100644 index 00000000..8d321eb3 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_actions.dart @@ -0,0 +1,53 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/src/presentation/widgets/common/auth_trouble_link.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget that displays the primary action button and trouble link for Phone Input. +class PhoneInputActions extends StatelessWidget { + /// Whether the sign-in process is currently loading. + final bool isLoading; + + /// Callback for when the Send Code button is pressed. + final VoidCallback? onSendCode; + + /// Creates a [PhoneInputActions]. + const PhoneInputActions({ + super.key, + required this.isLoading, + this.onSendCode, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(UiConstants.space6), + decoration: const BoxDecoration( + border: Border(top: BorderSide(color: UiColors.separatorSecondary)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + isLoading + ? UiButton.secondary( + onPressed: null, + child: const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ) + : UiButton.primary( + text: t + .staff_authentication + .phone_verification_page + .send_code_button, + onPressed: onSendCode, + ), + const SizedBox(height: UiConstants.space4), + const AuthTroubleLink(), + ], + ), + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_form_field.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_form_field.dart new file mode 100644 index 00000000..5e13a8ee --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_form_field.dart @@ -0,0 +1,96 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget that displays the phone number input field with country code. +/// +/// This widget handles its own [TextEditingController] to manage input. +class PhoneInputFormField extends StatefulWidget { + /// The initial value for the phone number. + final String initialValue; + + /// The error message to display, if any. + final String error; + + /// Callback for when the text field value changes. + final ValueChanged onChanged; + + /// Creates a [PhoneInputFormField]. + const PhoneInputFormField({ + super.key, + this.initialValue = '', + required this.error, + required this.onChanged, + }); + + @override + State createState() => _PhoneInputFormFieldState(); +} + +class _PhoneInputFormFieldState extends State { + late final TextEditingController _controller; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(text: widget.initialValue); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.staff_authentication.phone_input.label, + style: UiTypography.footnote1m.textSecondary, + ), + const SizedBox(height: UiConstants.space2), + Row( + children: [ + Container( + width: 100, + height: 48, + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('🇺🇸', style: UiTypography.headline2m), + const SizedBox(width: UiConstants.space1), + Text('+1', style: UiTypography.body1m), + ], + ), + ), + const SizedBox(width: UiConstants.space2), + Expanded( + child: TextField( + controller: _controller, + keyboardType: TextInputType.phone, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(10), + ], + decoration: InputDecoration( + hintText: t.staff_authentication.phone_input.hint, + ), + onChanged: widget.onChanged, + ), + ), + ], + ), + if (widget.error.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: UiConstants.space2), + child: Text(widget.error, style: UiTypography.body2r.textError), + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_header.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_header.dart new file mode 100644 index 00000000..2b8360c1 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_header.dart @@ -0,0 +1,27 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget that displays the title and subtitle for the Phone Input page. +class PhoneInputHeader extends StatelessWidget { + /// Creates a [PhoneInputHeader]. + const PhoneInputHeader({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.staff_authentication.phone_input.title, + style: UiTypography.headline1m, + ), + const SizedBox(height: UiConstants.space1), + Text( + t.staff_authentication.phone_input.subtitle, + style: UiTypography.body2r.textSecondary, + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_basic_info.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_basic_info.dart new file mode 100644 index 00000000..c7b1e46b --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_basic_info.dart @@ -0,0 +1,100 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget for setting up basic profile information (photo, name, bio). +class ProfileSetupBasicInfo extends StatelessWidget { + /// The user's full name. + final String fullName; + + /// The user's bio. + final String bio; + + /// Callback for when the full name changes. + final ValueChanged onFullNameChanged; + + /// Callback for when the bio changes. + final ValueChanged onBioChanged; + + /// Creates a [ProfileSetupBasicInfo] widget. + const ProfileSetupBasicInfo({ + super.key, + required this.fullName, + required this.bio, + required this.onFullNameChanged, + required this.onBioChanged, + }); + + @override + /// Builds the basic info step UI. + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SectionTitleSubtitle( + title: t.staff_authentication.profile_setup_page.basic_info.title, + subtitle: + t.staff_authentication.profile_setup_page.basic_info.subtitle, + ), + const SizedBox(height: UiConstants.space8), + + // Photo Upload + Center( + child: Stack( + children: [ + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: UiColors.secondary, + border: Border.all( + color: UiColors.secondaryForeground.withAlpha(24), + width: 4, + ), + ), + child: const Icon( + UiIcons.user, + size: 48, + color: UiColors.iconSecondary, + ), + ), + Positioned( + bottom: 0, + right: 0, + child: UiIconButton.secondary(icon: UiIcons.camera), + ), + ], + ), + ), + const SizedBox(height: UiConstants.space8), + + // Full Name + UiTextField( + label: t + .staff_authentication + .profile_setup_page + .basic_info + .full_name_label, + hintText: t + .staff_authentication + .profile_setup_page + .basic_info + .full_name_hint, + onChanged: onFullNameChanged, + ), + const SizedBox(height: UiConstants.space6), + + // Bio + UiTextField( + label: t.staff_authentication.profile_setup_page.basic_info.bio_label, + hintText: + t.staff_authentication.profile_setup_page.basic_info.bio_hint, + maxLines: 3, + onChanged: onBioChanged, + ), + ], + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_experience.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_experience.dart new file mode 100644 index 00000000..7babdb60 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_experience.dart @@ -0,0 +1,265 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget for setting up skills and preferred industries. +class ProfileSetupExperience extends StatelessWidget { + /// The list of selected skills. + final List skills; + + /// The list of selected industries. + final List industries; + + /// Callback for when skills change. + final ValueChanged> onSkillsChanged; + + /// Callback for when industries change. + final ValueChanged> onIndustriesChanged; + + static const List _allSkillKeys = [ + 'food_service', + 'bartending', + 'warehouse', + 'retail', + 'events', + 'customer_service', + 'cleaning', + 'security', + 'driving', + 'cooking', + ]; + + static const List _allIndustryKeys = [ + 'hospitality', + 'food_service', + 'warehouse', + 'events', + 'retail', + 'healthcare', + ]; + + /// Creates a [ProfileSetupExperience] widget. + const ProfileSetupExperience({ + super.key, + required this.skills, + required this.industries, + required this.onSkillsChanged, + required this.onIndustriesChanged, + }); + + /// Toggles a skill. + void _toggleSkill({required String skill}) { + final updatedList = List.from(skills); + if (updatedList.contains(skill)) { + updatedList.remove(skill); + } else { + updatedList.add(skill); + } + onSkillsChanged(updatedList); + } + + /// Toggles an industry. + void _toggleIndustry({required String industry}) { + final updatedList = List.from(industries); + if (updatedList.contains(industry)) { + updatedList.remove(industry); + } else { + updatedList.add(industry); + } + onIndustriesChanged(updatedList); + } + + @override + /// Builds the experience setup step UI. + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SectionTitleSubtitle( + title: t.staff_authentication.profile_setup_page.experience.title, + subtitle: + t.staff_authentication.profile_setup_page.experience.subtitle, + ), + const SizedBox(height: UiConstants.space8), + + // Skills + Text( + t.staff_authentication.profile_setup_page.experience.skills_label, + style: UiTypography.body2m, + ), + const SizedBox(height: UiConstants.space3), + Wrap( + spacing: UiConstants.space2, + runSpacing: UiConstants.space2, + children: _allSkillKeys.map((key) { + final isSelected = skills.contains(key); + // Dynamic translation access + final label = _getSkillLabel(key); + + return UiChip( + label: label, + isSelected: isSelected, + onTap: () => _toggleSkill(skill: key), + leadingIcon: isSelected ? UiIcons.check : null, + variant: UiChipVariant.primary, + ); + }).toList(), + ), + + const SizedBox(height: UiConstants.space8), + + // Industries + Text( + t.staff_authentication.profile_setup_page.experience.industries_label, + style: UiTypography.body2m, + ), + const SizedBox(height: UiConstants.space3), + Wrap( + spacing: UiConstants.space2, + runSpacing: UiConstants.space2, + children: _allIndustryKeys.map((key) { + final isSelected = industries.contains(key); + final label = _getIndustryLabel(key); + + return UiChip( + label: label, + isSelected: isSelected, + onTap: () => _toggleIndustry(industry: key), + leadingIcon: isSelected ? UiIcons.check : null, + variant: isSelected + ? UiChipVariant.accent + : UiChipVariant.primary, + ); + }).toList(), + ), + ], + ); + } + + String _getSkillLabel(String key) { + switch (key) { + case 'food_service': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .food_service; + case 'bartending': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .bartending; + case 'warehouse': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .warehouse; + case 'retail': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .retail; + case 'events': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .events; + case 'customer_service': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .customer_service; + case 'cleaning': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .cleaning; + case 'security': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .security; + case 'driving': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .driving; + case 'cooking': + return t + .staff_authentication + .profile_setup_page + .experience + .skills + .cooking; + default: + return key; + } + } + + String _getIndustryLabel(String key) { + switch (key) { + case 'hospitality': + return t + .staff_authentication + .profile_setup_page + .experience + .industries + .hospitality; + case 'food_service': + return t + .staff_authentication + .profile_setup_page + .experience + .industries + .food_service; + case 'warehouse': + return t + .staff_authentication + .profile_setup_page + .experience + .industries + .warehouse; + case 'events': + return t + .staff_authentication + .profile_setup_page + .experience + .industries + .events; + case 'retail': + return t + .staff_authentication + .profile_setup_page + .experience + .industries + .retail; + case 'healthcare': + return t + .staff_authentication + .profile_setup_page + .experience + .industries + .healthcare; + default: + return key; + } + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_header.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_header.dart new file mode 100644 index 00000000..738e7539 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_header.dart @@ -0,0 +1,57 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A header widget for the profile setup page showing back button and step count. +class ProfileSetupHeader extends StatelessWidget { + /// The current step index (0-based). + final int currentStep; + + /// The total number of steps. + final int totalSteps; + + /// Callback when the back button is tapped. + final VoidCallback? onBackTap; + + /// Creates a [ProfileSetupHeader]. + const ProfileSetupHeader({ + super.key, + required this.currentStep, + required this.totalSteps, + this.onBackTap, + }); + + @override + /// Builds the header UI. + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + vertical: UiConstants.space4, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (currentStep > 0 && onBackTap != null) + GestureDetector( + onTap: onBackTap, + child: const Icon( + UiIcons.chevronLeft, + size: 20, + color: UiColors.textSecondary, + ), + ) + else + const SizedBox(width: UiConstants.space6), + Text( + t.staff_authentication.profile_setup_page.step_indicator( + current: currentStep + 1, + total: totalSteps, + ), + style: UiTypography.footnote1r.textSecondary, + ), + ], + ), + ); + } +} diff --git a/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_location.dart b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_location.dart new file mode 100644 index 00000000..9e3f6c76 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_location.dart @@ -0,0 +1,165 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; +import 'package:staff_authentication/staff_authentication.dart'; + +/// A widget for setting up preferred work locations and distance. +class ProfileSetupLocation extends StatefulWidget { + /// The list of preferred locations. + final List preferredLocations; + + /// The maximum distance in miles. + final double maxDistanceMiles; + + /// Callback for when the preferred locations list changes. + final ValueChanged> onLocationsChanged; + + /// Callback for when the max distance changes. + final ValueChanged onDistanceChanged; + + /// Creates a [ProfileSetupLocation] widget. + const ProfileSetupLocation({ + super.key, + required this.preferredLocations, + required this.maxDistanceMiles, + required this.onLocationsChanged, + required this.onDistanceChanged, + }); + + @override + State createState() => _ProfileSetupLocationState(); +} + +class _ProfileSetupLocationState extends State { + final TextEditingController _locationController = TextEditingController(); + + @override + void dispose() { + _locationController.dispose(); + super.dispose(); + } + + /// Adds the current text from the controller as a location. + void _addLocation() { + final loc = _locationController.text.trim(); + if (loc.isNotEmpty && !widget.preferredLocations.contains(loc)) { + final updatedList = List.from(widget.preferredLocations) + ..add(loc); + widget.onLocationsChanged(updatedList); + _locationController.clear(); + } + } + + @override + /// Builds the location setup step UI. + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SectionTitleSubtitle( + title: t.staff_authentication.profile_setup_page.location.title, + subtitle: t.staff_authentication.profile_setup_page.location.subtitle, + ), + const SizedBox(height: UiConstants.space8), + + // Add Location input + Row( + crossAxisAlignment: CrossAxisAlignment.end, + spacing: UiConstants.space2, + children: [ + Expanded( + child: UiTextField( + label: t + .staff_authentication + .profile_setup_page + .location + .add_location_label, + controller: _locationController, + hintText: t + .staff_authentication + .profile_setup_page + .location + .add_location_hint, + onSubmitted: (_) => _addLocation(), + ), + ), + UiButton.secondary( + text: + t.staff_authentication.profile_setup_page.location.add_button, + onPressed: _addLocation, + style: OutlinedButton.styleFrom( + minimumSize: const Size(0, 48), + maximumSize: const Size(double.infinity, 48), + ), + ), + ], + ), + + const SizedBox(height: UiConstants.space4), + + // Location Badges + if (widget.preferredLocations.isNotEmpty) + Wrap( + spacing: UiConstants.space2, + runSpacing: UiConstants.space2, + children: widget.preferredLocations.map((loc) { + return UiChip( + label: loc, + leadingIcon: UiIcons.mapPin, + trailingIcon: UiIcons.close, + onTrailingIconTap: () => _removeLocation(location: loc), + variant: UiChipVariant.secondary, + ); + }).toList(), + ), + + const SizedBox(height: UiConstants.space8), + // Slider + Text( + t.staff_authentication.profile_setup_page.location.max_distance( + distance: widget.maxDistanceMiles.round().toString(), + ), + style: UiTypography.body2m, + ), + const SizedBox(height: UiConstants.space2), + Slider( + value: widget.maxDistanceMiles, + min: 5, + max: 50, + onChanged: widget.onDistanceChanged, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: UiConstants.space2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + t + .staff_authentication + .profile_setup_page + .location + .min_dist_label, + style: UiTypography.footnote1r.textSecondary, + ), + Text( + t + .staff_authentication + .profile_setup_page + .location + .max_dist_label, + style: UiTypography.footnote1r.textSecondary, + ), + ], + ), + ), + ], + ); + } + + /// Removes the specified [location] from the list. + void _removeLocation({required String location}) { + final updatedList = List.from(widget.preferredLocations) + ..remove(location); + widget.onLocationsChanged(updatedList); + } +} diff --git a/apps/packages/features/staff/authentication/lib/staff_authentication.dart b/apps/packages/features/staff/authentication/lib/staff_authentication.dart new file mode 100644 index 00000000..2c272187 --- /dev/null +++ b/apps/packages/features/staff/authentication/lib/staff_authentication.dart @@ -0,0 +1,65 @@ +library staff_authentication; + +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart'; +import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart'; +import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart'; +import 'package:staff_authentication/src/domain/usecases/verify_otp_usecase.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart'; +import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart'; +import 'package:staff_authentication/src/presentation/pages/get_started_page.dart'; +import 'package:staff_authentication/src/presentation/pages/phone_verification_page.dart'; +import 'package:staff_authentication/src/presentation/pages/profile_setup_page.dart'; +import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart'; + +export 'src/domain/ui_entities/auth_mode.dart'; +export 'src/presentation/pages/get_started_page.dart'; +export 'src/presentation/pages/phone_verification_page.dart'; +export 'src/presentation/pages/profile_setup_page.dart'; +export 'package:core_localization/core_localization.dart'; + +/// A [Module] for the staff authentication feature. +class StaffAuthenticationModule extends Module { + @override + List get imports => [DataConnectModule()]; + + @override + void binds(Injector i) { + // Repositories + i.addLazySingleton( + () => AuthRepositoryImpl(mock: i.get()), + ); + + // UseCases + i.addLazySingleton(SignInWithPhoneUseCase.new); + i.addLazySingleton(VerifyOtpUseCase.new); + + // BLoCs + i.addLazySingleton( + () => AuthBloc( + signInUseCase: i.get(), + verifyOtpUseCase: i.get(), + ), + ); + i.add(ProfileSetupBloc.new); + } + + @override + void routes(r) { + r.child('/', child: (_) => const GetStartedPage()); + r.child( + '/phone-verification', + child: (context) { + final Map? data = r.args.data; + final String? modeName = data?['mode']; + final AuthMode mode = AuthMode.values.firstWhere( + (e) => e.name == modeName, + orElse: () => AuthMode.login, + ); + return PhoneVerificationPage(mode: mode); + }, + ); + r.child('/profile-setup', child: (_) => const ProfileSetupPage()); + } +} diff --git a/apps/packages/features/staff/authentication/pubspec.yaml b/apps/packages/features/staff/authentication/pubspec.yaml new file mode 100644 index 00000000..ec275da3 --- /dev/null +++ b/apps/packages/features/staff/authentication/pubspec.yaml @@ -0,0 +1,40 @@ +name: staff_authentication +description: Staff Authentication and Onboarding feature. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.10.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^8.1.0 + flutter_modular: ^6.3.0 + equatable: ^2.0.5 + lucide_icons: ^0.257.0 + + # Architecture Packages + krow_domain: + path: ../../../../domain + krow_data_connect: + path: ../../../../data_connect + krow_core: + path: ../../../../core + design_system: + path: ../../../../design_system + core_localization: + path: ../../../core_localization + + +dev_dependencies: + flutter_test: + sdk: flutter + bloc_test: ^9.1.0 + mocktail: ^1.0.0 + build_runner: ^2.4.15 + +flutter: + uses-material-design: true diff --git a/apps/pubspec.lock b/apps/pubspec.lock new file mode 100644 index 00000000..a3f8c48f --- /dev/null +++ b/apps/pubspec.lock @@ -0,0 +1,1079 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + url: "https://pub.dev" + source: hosted + version: "91.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + url: "https://pub.dev" + source: hosted + version: "8.4.1" + ansi_styles: + dependency: transitive + description: + name: ansi_styles + sha256: "9c656cc12b3c27b17dd982b2cc5c0cfdfbdabd7bc8f3ae5e8542d9867b47ce8a" + url: "https://pub.dev" + source: hosted + version: "0.3.2+1" + 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" + auto_injector: + dependency: transitive + description: + name: auto_injector + sha256: "1fc2624898e92485122eb2b1698dd42511d7ff6574f84a3a8606fc4549a1e8f8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + bloc_test: + dependency: transitive + description: + name: bloc_test + sha256: "165a6ec950d9252ebe36dc5335f2e6eb13055f33d56db0eeb7642768849b43d2" + url: "https://pub.dev" + source: hosted + version: "9.1.7" + 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: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: transitive + description: + name: build_runner + sha256: b4d854962a32fd9f8efc0b76f98214790b833af8b2e9b2df6bfc927c0415a072 + url: "https://pub.dev" + source: hosted + version: "2.10.5" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" + url: "https://pub.dev" + source: hosted + version: "8.12.3" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + 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" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + cli_launcher: + dependency: transitive + description: + name: cli_launcher + sha256: "17d2744fb9a254c49ec8eda582536abe714ea0131533e24389843a4256f82eac" + url: "https://pub.dev" + source: hosted + version: "0.3.2+1" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: ae0db647e668cbb295a3527f0938e4039e004c80099dce2f964102373f5ce0b5 + url: "https://pub.dev" + source: hosted + version: "0.19.10" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + conventional_commit: + dependency: transitive + description: + name: conventional_commit + sha256: c40b1b449ce2a63fa2ce852f35e3890b1e182f5951819934c0e4a66254bc0dc3 + url: "https://pub.dev" + source: hosted + version: "0.6.1+1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csv: + dependency: transitive + description: + name: csv + sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c + url: "https://pub.dev" + source: hosted + version: "6.0.0" + cupertino_icons: + dependency: transitive + 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: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b + url: "https://pub.dev" + source: hosted + version: "3.1.3" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" + 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: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + url: "https://pub.dev" + source: hosted + version: "2.1.5" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: transitive + description: + name: flutter_bloc + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + url: "https://pub.dev" + source: hosted + version: "8.1.6" + flutter_lints: + dependency: transitive + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_modular: + dependency: transitive + description: + name: flutter_modular + sha256: "33a63d9fe61429d12b3dfa04795ed890f17d179d3d38e988ba7969651fcd5586" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + flutter_test: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: transitive + description: + name: font_awesome_flutter + sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0 + url: "https://pub.dev" + source: hosted + version: "10.12.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + google_fonts: + dependency: transitive + description: + name: google_fonts + sha256: ca1cc501704c47e478f69a667d7f2d882755ddf7baad3f60c3b1256594467022 + url: "https://pub.dev" + source: hosted + version: "7.0.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: "5410b9f4f6c9f01e8ff0eb81c9801ea13a3c3d39f8f0b1613cda08e27eab3c18" + url: "https://pub.dev" + source: hosted + version: "0.20.5" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + 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: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + logger: + dependency: transitive + description: + name: logger + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 + url: "https://pub.dev" + source: hosted + version: "2.6.2" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + lucide_icons: + dependency: transitive + description: + name: lucide_icons + sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4 + url: "https://pub.dev" + source: hosted + version: "0.257.0" + 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" + melos: + dependency: "direct dev" + description: + name: melos + sha256: ff2da25990d83b0db883eb257e4fa25eb78150a329e7bfab7a379499d0f5f6f7 + url: "https://pub.dev" + source: hosted + version: "7.3.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + modular_core: + dependency: transitive + description: + name: modular_core + sha256: "1db0420a0dfb8a2c6dca846e7cbaa4ffeb778e247916dbcb27fb25aa566e5436" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + mustache_template: + dependency: transitive + description: + name: mustache_template + sha256: "4326d0002ff58c74b9486990ccbdab08157fca3c996fe9e197aff9d61badf307" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: f8872ea6c7a50ce08db9ae280ca2b8efdd973157ce462826c82f3c3051d154ce + url: "https://pub.dev" + source: hosted + version: "0.17.2" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "55eb67ede1002d9771b3f9264d2c9d30bc364f0267bc1c6cc0883280d5f0c7cb" + url: "https://pub.dev" + source: hosted + version: "9.2.2" + 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_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: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + 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" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + process: + dependency: transitive + description: + name: process + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 + url: "https://pub.dev" + source: hosted + version: "5.0.5" + prompts: + dependency: transitive + description: + name: prompts + sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + 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" + pub_updater: + dependency: transitive + description: + name: pub_updater + sha256: "739a0161d73a6974c0675b864fb0cf5147305f7b077b7f03a58fa7a9ab3e7e7d" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + rename: + dependency: transitive + description: + name: rename + sha256: da5f4d67f8c68f066ad04edfd6585495dbe595f2baf3b1999eb6af1805d79539 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + result_dart: + dependency: transitive + description: + name: result_dart + sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" + url: "https://pub.dev" + source: hosted + version: "2.4.18" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + 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" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + slang: + dependency: transitive + description: + name: slang + sha256: "13e3b6f07adc51ab751e7889647774d294cbce7a3382f81d9e5029acfe9c37b2" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + slang_build_runner: + dependency: transitive + description: + name: slang_build_runner + sha256: "453d74b5430153a3c4150d5ba8f6380e0785f3939f7511f10ac5b6cf9bb7d2a7" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + slang_flutter: + dependency: transitive + description: + name: slang_flutter + sha256: "0a4545cca5404d6b7487cf61cf1fe56c52daeb08de56a7574ee8381fbad035a0" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + 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" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" + test_api: + dependency: transitive + description: + name: test_api + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + url: "https://pub.dev" + source: hosted + version: "0.6.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + 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" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: ec709065bb2c911b336853b67f3732dd13e0336bd065cc2f1061d7610ddf45e3 + url: "https://pub.dev" + source: hosted + version: "2.2.3" +sdks: + dart: ">=3.10.7 <4.0.0" + flutter: ">=3.38.4" diff --git a/apps/pubspec.yaml b/apps/pubspec.yaml new file mode 100644 index 00000000..9ff2ff69 --- /dev/null +++ b/apps/pubspec.yaml @@ -0,0 +1,20 @@ +name: flutter_melos_modular_scaffold +publish_to: 'none' +description: "A sample project using melos and modular scaffold." +environment: + sdk: '>=3.10.0 <4.0.0' +workspace: + - packages/design_system + - packages/core + - packages/domain + - packages/data_connect + - packages/core_localization + - packages/features/staff/authentication + - packages/features/client/authentication + - packages/features/client/home + - apps/staff + - apps/client + - apps/design_system_viewer + +dev_dependencies: + melos: ^7.3.0