diff --git a/StudioProjects/yimaru_app/.gitignore b/StudioProjects/yimaru_app/.gitignore
new file mode 100644
index 0000000..3820a95
--- /dev/null
+++ b/StudioProjects/yimaru_app/.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/StudioProjects/yimaru_app/.metadata b/StudioProjects/yimaru_app/.metadata
new file mode 100644
index 0000000..b5527e1
--- /dev/null
+++ b/StudioProjects/yimaru_app/.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: "66dd93f9a27ffe2a9bfc8297506ce066ff51265f"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ - platform: android
+ create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ - platform: ios
+ create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ - platform: linux
+ create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ - platform: macos
+ create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ - platform: web
+ create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ - platform: windows
+ create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+ base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
+
+ # 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/StudioProjects/yimaru_app/.vscode/settings.json b/StudioProjects/yimaru_app/.vscode/settings.json
new file mode 100644
index 0000000..25d97f4
--- /dev/null
+++ b/StudioProjects/yimaru_app/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "explorer.fileNesting.enabled": true,
+ "explorer.fileNesting.patterns": {
+ "*.dart": "${capture}.mobile.dart, ${capture}.tablet.dart, ${capture}.desktop.dart, ${capture}.form.dart, ${capture}.g.dart, ${capture}.freezed.dart, ${capture}.logger.dart, ${capture}.locator.dart, ${capture}.router.dart, ${capture}.dialogs.dart, ${capture}.bottomsheets.dart"
+ }
+}
+
diff --git a/StudioProjects/yimaru_app/README.md b/StudioProjects/yimaru_app/README.md
new file mode 100644
index 0000000..6679e57
--- /dev/null
+++ b/StudioProjects/yimaru_app/README.md
@@ -0,0 +1,13 @@
+# yimaru_app
+
+A new Flutter project.
+
+## Golden Tests
+
+Golden tests are already setup for this project. To run the tests and update the golden files, run:
+
+```bash
+flutter test --update-goldens
+```
+
+The golden test screenshots will be stored under `test/golden/`.
diff --git a/StudioProjects/yimaru_app/analysis_options.yaml b/StudioProjects/yimaru_app/analysis_options.yaml
new file mode 100644
index 0000000..f9b3034
--- /dev/null
+++ b/StudioProjects/yimaru_app/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:flutter_lints/flutter.yaml
diff --git a/StudioProjects/yimaru_app/android/.gitignore b/StudioProjects/yimaru_app/android/.gitignore
new file mode 100644
index 0000000..be3943c
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/android/app/build.gradle.kts b/StudioProjects/yimaru_app/android/app/build.gradle.kts
new file mode 100644
index 0000000..19de2ad
--- /dev/null
+++ b/StudioProjects/yimaru_app/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.yimaru_app"
+ 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.yimaru_app"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/StudioProjects/yimaru_app/android/app/src/debug/AndroidManifest.xml b/StudioProjects/yimaru_app/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/StudioProjects/yimaru_app/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/StudioProjects/yimaru_app/android/app/src/main/AndroidManifest.xml b/StudioProjects/yimaru_app/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6561fcc
--- /dev/null
+++ b/StudioProjects/yimaru_app/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/android/app/src/main/kotlin/com/example/yimaru_app/MainActivity.kt b/StudioProjects/yimaru_app/android/app/src/main/kotlin/com/example/yimaru_app/MainActivity.kt
new file mode 100644
index 0000000..3e34068
--- /dev/null
+++ b/StudioProjects/yimaru_app/android/app/src/main/kotlin/com/example/yimaru_app/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.yimaru_app
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/drawable-v21/launch_background.xml b/StudioProjects/yimaru_app/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/StudioProjects/yimaru_app/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/drawable/launch_background.xml b/StudioProjects/yimaru_app/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/StudioProjects/yimaru_app/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/StudioProjects/yimaru_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/values-night/styles.xml b/StudioProjects/yimaru_app/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/StudioProjects/yimaru_app/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/android/app/src/main/res/values/styles.xml b/StudioProjects/yimaru_app/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/StudioProjects/yimaru_app/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/android/app/src/profile/AndroidManifest.xml b/StudioProjects/yimaru_app/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/StudioProjects/yimaru_app/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/StudioProjects/yimaru_app/android/build.gradle.kts b/StudioProjects/yimaru_app/android/build.gradle.kts
new file mode 100644
index 0000000..dbee657
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/android/gradle.properties b/StudioProjects/yimaru_app/android/gradle.properties
new file mode 100644
index 0000000..fbee1d8
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/android/gradle/wrapper/gradle-wrapper.properties b/StudioProjects/yimaru_app/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e4ef43f
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/android/settings.gradle.kts b/StudioProjects/yimaru_app/android/settings.gradle.kts
new file mode 100644
index 0000000..ca7fe06
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/ios/.gitignore b/StudioProjects/yimaru_app/ios/.gitignore
new file mode 100644
index 0000000..7a7f987
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/ios/Flutter/AppFrameworkInfo.plist b/StudioProjects/yimaru_app/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..1dc6cf7
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/ios/Flutter/Debug.xcconfig b/StudioProjects/yimaru_app/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/StudioProjects/yimaru_app/ios/Flutter/Release.xcconfig b/StudioProjects/yimaru_app/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.pbxproj b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..79e3510
--- /dev/null
+++ b/StudioProjects/yimaru_app/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.yimaruApp;
+ 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.yimaruApp.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.yimaruApp.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.yimaruApp.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.yimaruApp;
+ 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.yimaruApp;
+ 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/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..e3773d4
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner.xcworkspace/contents.xcworkspacedata b/StudioProjects/yimaru_app/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/StudioProjects/yimaru_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/StudioProjects/yimaru_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner/AppDelegate.swift b/StudioProjects/yimaru_app/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..6266644
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..dc9ada4
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..7353c41
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..797d452
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..6ed2d93
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cd7b00
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..fe73094
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..321773c
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..797d452
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..502f463
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..0ec3034
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..0ec3034
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..e9f5fea
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..84ac32a
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..8953cba
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..0467bf1
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/StudioProjects/yimaru_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/StudioProjects/yimaru_app/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner/Base.lproj/Main.storyboard b/StudioProjects/yimaru_app/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StudioProjects/yimaru_app/ios/Runner/Info.plist b/StudioProjects/yimaru_app/ios/Runner/Info.plist
new file mode 100644
index 0000000..ed66cce
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner/Info.plist
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Yimaru App
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ yimaru_app
+ 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/StudioProjects/yimaru_app/ios/Runner/Runner-Bridging-Header.h b/StudioProjects/yimaru_app/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/StudioProjects/yimaru_app/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/StudioProjects/yimaru_app/ios/RunnerTests/RunnerTests.swift b/StudioProjects/yimaru_app/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 0000000..86a7c3b
--- /dev/null
+++ b/StudioProjects/yimaru_app/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/StudioProjects/yimaru_app/lib/app/app.bottomsheets.dart b/StudioProjects/yimaru_app/lib/app/app.bottomsheets.dart
new file mode 100644
index 0000000..777004c
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/app/app.bottomsheets.dart
@@ -0,0 +1,25 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// **************************************************************************
+// StackedBottomsheetGenerator
+// **************************************************************************
+
+import 'package:stacked_services/stacked_services.dart';
+
+import 'app.locator.dart';
+import '../ui/bottom_sheets/notice/notice_sheet.dart';
+
+enum BottomSheetType {
+ notice,
+}
+
+void setupBottomSheetUi() {
+ final bottomsheetService = locator();
+
+ final Map builders = {
+ BottomSheetType.notice: (context, request, completer) =>
+ NoticeSheet(request: request, completer: completer),
+ };
+
+ bottomsheetService.setCustomSheetBuilders(builders);
+}
diff --git a/StudioProjects/yimaru_app/lib/app/app.dart b/StudioProjects/yimaru_app/lib/app/app.dart
new file mode 100644
index 0000000..4e96242
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/app/app.dart
@@ -0,0 +1,32 @@
+import 'package:yimaru_app/ui/bottom_sheets/notice/notice_sheet.dart';
+import 'package:yimaru_app/ui/dialogs/info_alert/info_alert_dialog.dart';
+import 'package:yimaru_app/ui/views/home/home_view.dart';
+import 'package:yimaru_app/ui/views/startup/startup_view.dart';
+import 'package:stacked/stacked_annotations.dart';
+import 'package:stacked_services/stacked_services.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart';
+// @stacked-import
+
+@StackedApp(
+ routes: [
+ MaterialRoute(page: HomeView),
+ MaterialRoute(page: StartupView),
+ MaterialRoute(page: OnboardingView),
+// @stacked-route
+ ],
+ dependencies: [
+ LazySingleton(classType: BottomSheetService),
+ LazySingleton(classType: DialogService),
+ LazySingleton(classType: NavigationService),
+ // @stacked-service
+ ],
+ bottomsheets: [
+ StackedBottomsheet(classType: NoticeSheet),
+ // @stacked-bottom-sheet
+ ],
+ dialogs: [
+ StackedDialog(classType: InfoAlertDialog),
+ // @stacked-dialog
+ ],
+)
+class App {}
diff --git a/StudioProjects/yimaru_app/lib/app/app.dialogs.dart b/StudioProjects/yimaru_app/lib/app/app.dialogs.dart
new file mode 100644
index 0000000..2a4e944
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/app/app.dialogs.dart
@@ -0,0 +1,25 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// **************************************************************************
+// StackedDialogGenerator
+// **************************************************************************
+
+import 'package:stacked_services/stacked_services.dart';
+
+import 'app.locator.dart';
+import '../ui/dialogs/info_alert/info_alert_dialog.dart';
+
+enum DialogType {
+ infoAlert,
+}
+
+void setupDialogUi() {
+ final dialogService = locator();
+
+ final Map builders = {
+ DialogType.infoAlert: (context, request, completer) =>
+ InfoAlertDialog(request: request, completer: completer),
+ };
+
+ dialogService.registerCustomDialogBuilders(builders);
+}
diff --git a/StudioProjects/yimaru_app/lib/app/app.locator.dart b/StudioProjects/yimaru_app/lib/app/app.locator.dart
new file mode 100644
index 0000000..9d4a8da
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/app/app.locator.dart
@@ -0,0 +1,28 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// **************************************************************************
+// StackedLocatorGenerator
+// **************************************************************************
+
+// ignore_for_file: public_member_api_docs, implementation_imports, depend_on_referenced_packages
+
+import 'package:stacked_services/src/bottom_sheet/bottom_sheet_service.dart';
+import 'package:stacked_services/src/dialog/dialog_service.dart';
+import 'package:stacked_services/src/navigation/navigation_service.dart';
+import 'package:stacked_shared/stacked_shared.dart';
+
+final locator = StackedLocator.instance;
+
+Future setupLocator({
+ String? environment,
+ EnvironmentFilter? environmentFilter,
+}) async {
+// Register environments
+ locator.registerEnvironment(
+ environment: environment, environmentFilter: environmentFilter);
+
+// Register dependencies
+ locator.registerLazySingleton(() => BottomSheetService());
+ locator.registerLazySingleton(() => DialogService());
+ locator.registerLazySingleton(() => NavigationService());
+}
diff --git a/StudioProjects/yimaru_app/lib/app/app.router.dart b/StudioProjects/yimaru_app/lib/app/app.router.dart
new file mode 100644
index 0000000..b85ee7b
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/app/app.router.dart
@@ -0,0 +1,158 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// **************************************************************************
+// StackedNavigatorGenerator
+// **************************************************************************
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:flutter/material.dart' as _i5;
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart' as _i1;
+import 'package:stacked_services/stacked_services.dart' as _i6;
+import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2;
+import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i4;
+import 'package:yimaru_app/ui/views/startup/startup_view.dart' as _i3;
+
+class Routes {
+ static const homeView = '/home-view';
+
+ static const startupView = '/startup-view';
+
+ static const onboardingView = '/onboarding-view';
+
+ static const all = {
+ homeView,
+ startupView,
+ onboardingView,
+ };
+}
+
+class StackedRouter extends _i1.RouterBase {
+ final _routes = <_i1.RouteDef>[
+ _i1.RouteDef(
+ Routes.homeView,
+ page: _i2.HomeView,
+ ),
+ _i1.RouteDef(
+ Routes.startupView,
+ page: _i3.StartupView,
+ ),
+ _i1.RouteDef(
+ Routes.onboardingView,
+ page: _i4.OnboardingView,
+ ),
+ ];
+
+ final _pagesMap = {
+ _i2.HomeView: (data) {
+ return _i5.MaterialPageRoute(
+ builder: (context) => const _i2.HomeView(),
+ settings: data,
+ );
+ },
+ _i3.StartupView: (data) {
+ return _i5.MaterialPageRoute(
+ builder: (context) => const _i3.StartupView(),
+ settings: data,
+ );
+ },
+ _i4.OnboardingView: (data) {
+ return _i5.MaterialPageRoute(
+ builder: (context) => const _i4.OnboardingView(),
+ settings: data,
+ );
+ },
+ };
+
+ @override
+ List<_i1.RouteDef> get routes => _routes;
+
+ @override
+ Map get pagesMap => _pagesMap;
+}
+
+extension NavigatorStateExtension on _i6.NavigationService {
+ Future navigateToHomeView([
+ int? routerId,
+ bool preventDuplicates = true,
+ Map? parameters,
+ Widget Function(BuildContext, Animation, Animation, Widget)?
+ transition,
+ ]) async {
+ return navigateTo(Routes.homeView,
+ id: routerId,
+ preventDuplicates: preventDuplicates,
+ parameters: parameters,
+ transition: transition);
+ }
+
+ Future navigateToStartupView([
+ int? routerId,
+ bool preventDuplicates = true,
+ Map? parameters,
+ Widget Function(BuildContext, Animation, Animation, Widget)?
+ transition,
+ ]) async {
+ return navigateTo(Routes.startupView,
+ id: routerId,
+ preventDuplicates: preventDuplicates,
+ parameters: parameters,
+ transition: transition);
+ }
+
+ Future navigateToOnboardingView([
+ int? routerId,
+ bool preventDuplicates = true,
+ Map? parameters,
+ Widget Function(BuildContext, Animation, Animation, Widget)?
+ transition,
+ ]) async {
+ return navigateTo(Routes.onboardingView,
+ id: routerId,
+ preventDuplicates: preventDuplicates,
+ parameters: parameters,
+ transition: transition);
+ }
+
+ Future replaceWithHomeView([
+ int? routerId,
+ bool preventDuplicates = true,
+ Map? parameters,
+ Widget Function(BuildContext, Animation, Animation, Widget)?
+ transition,
+ ]) async {
+ return replaceWith(Routes.homeView,
+ id: routerId,
+ preventDuplicates: preventDuplicates,
+ parameters: parameters,
+ transition: transition);
+ }
+
+ Future replaceWithStartupView([
+ int? routerId,
+ bool preventDuplicates = true,
+ Map? parameters,
+ Widget Function(BuildContext, Animation, Animation, Widget)?
+ transition,
+ ]) async {
+ return replaceWith(Routes.startupView,
+ id: routerId,
+ preventDuplicates: preventDuplicates,
+ parameters: parameters,
+ transition: transition);
+ }
+
+ Future replaceWithOnboardingView([
+ int? routerId,
+ bool preventDuplicates = true,
+ Map? parameters,
+ Widget Function(BuildContext, Animation, Animation, Widget)?
+ transition,
+ ]) async {
+ return replaceWith(Routes.onboardingView,
+ id: routerId,
+ preventDuplicates: preventDuplicates,
+ parameters: parameters,
+ transition: transition);
+ }
+}
diff --git a/StudioProjects/yimaru_app/lib/main.dart b/StudioProjects/yimaru_app/lib/main.dart
new file mode 100644
index 0000000..9d7a4de
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/main.dart
@@ -0,0 +1,28 @@
+import 'package:flutter/material.dart';
+import 'package:yimaru_app/app/app.bottomsheets.dart';
+import 'package:yimaru_app/app/app.dialogs.dart';
+import 'package:yimaru_app/app/app.locator.dart';
+import 'package:yimaru_app/app/app.router.dart';
+import 'package:stacked_services/stacked_services.dart';
+
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await setupLocator();
+ setupDialogUi();
+ setupBottomSheetUi();
+ runApp(const MainApp());
+}
+
+class MainApp extends StatelessWidget {
+ const MainApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ initialRoute: Routes.startupView,
+ onGenerateRoute: StackedRouter().onGenerateRoute,
+ navigatorKey: StackedService.navigatorKey,
+ navigatorObservers: [StackedService.routeObserver],
+ );
+ }
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/bottom_sheets/notice/notice_sheet.dart b/StudioProjects/yimaru_app/lib/ui/bottom_sheets/notice/notice_sheet.dart
new file mode 100644
index 0000000..8de3929
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/bottom_sheets/notice/notice_sheet.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:stacked/stacked.dart';
+import 'package:stacked_services/stacked_services.dart';
+
+import 'notice_sheet_model.dart';
+
+class NoticeSheet extends StackedView {
+ final Function(SheetResponse)? completer;
+ final SheetRequest request;
+ const NoticeSheet({Key? key, required this.completer, required this.request})
+ : super(key: key);
+
+ @override
+ Widget builder(
+ BuildContext context,
+ NoticeSheetModel viewModel,
+ Widget? child,
+ ) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(10),
+ topRight: Radius.circular(10),
+ ),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ request.title!,
+ style: const TextStyle(fontSize: 25, fontWeight: FontWeight.w900),
+ ),
+ verticalSpaceTiny,
+ Text(
+ request.description!,
+ style: const TextStyle(fontSize: 14, color: kcMediumGrey),
+ maxLines: 3,
+ softWrap: true,
+ ),
+ verticalSpaceLarge,
+ ],
+ ),
+ );
+ }
+
+ @override
+ NoticeSheetModel viewModelBuilder(BuildContext context) => NoticeSheetModel();
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/bottom_sheets/notice/notice_sheet_model.dart b/StudioProjects/yimaru_app/lib/ui/bottom_sheets/notice/notice_sheet_model.dart
new file mode 100644
index 0000000..f344b19
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/bottom_sheets/notice/notice_sheet_model.dart
@@ -0,0 +1,3 @@
+import 'package:stacked/stacked.dart';
+
+class NoticeSheetModel extends BaseViewModel {}
diff --git a/StudioProjects/yimaru_app/lib/ui/common/app_colors.dart b/StudioProjects/yimaru_app/lib/ui/common/app_colors.dart
new file mode 100644
index 0000000..b9b1b22
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/common/app_colors.dart
@@ -0,0 +1,11 @@
+import 'package:flutter/material.dart';
+
+const Color kcBackgroundColor = kcWhiteColor;
+const Color kcWhiteColor = Color(0xFFFFFFFF);
+const Color kcMediumGrey = Color(0xFF474A54);
+const Color kcPrimaryColor = Color(0xFF9E2891);
+const Color kcDarkGreyColor = Color(0xFF1A1B1E);
+const Color kcVeryLightGrey = Color(0xFFE3E3E3);
+const Color kcPrimaryColorDark = Color(0xFF300151);
+const Color kcPrimaryColorLight = Color(0x149E2891);
+const Color kcLightGrey = Color.fromARGB(255, 187, 187, 187);
diff --git a/StudioProjects/yimaru_app/lib/ui/common/app_strings.dart b/StudioProjects/yimaru_app/lib/ui/common/app_strings.dart
new file mode 100644
index 0000000..06e7c8f
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/common/app_strings.dart
@@ -0,0 +1,3 @@
+const String ksHomeBottomSheetTitle = 'Build Great Apps!';
+const String ksHomeBottomSheetDescription =
+ 'Stacked is built to help you build better apps. Give us a chance and we\'ll prove it to you. Check out stacked.filledstacks.com to learn more';
diff --git a/StudioProjects/yimaru_app/lib/ui/common/ui_helpers.dart b/StudioProjects/yimaru_app/lib/ui/common/ui_helpers.dart
new file mode 100644
index 0000000..1cf232c
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/common/ui_helpers.dart
@@ -0,0 +1,126 @@
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+
+const double _tinySize = 5.0;
+const double _smallSize = 10.0;
+const double _mediumSize = 25.0;
+const double _largeSize = 50.0;
+const double _massiveSize = 120.0;
+
+const Widget horizontalSpaceTiny = SizedBox(width: _tinySize);
+const Widget horizontalSpaceSmall = SizedBox(width: _smallSize);
+const Widget horizontalSpaceMedium = SizedBox(width: _mediumSize);
+const Widget horizontalSpaceLarge = SizedBox(width: _largeSize);
+
+const Widget verticalSpaceTiny = SizedBox(height: _tinySize);
+const Widget verticalSpaceSmall = SizedBox(height: _smallSize);
+const Widget verticalSpaceMedium = SizedBox(height: _mediumSize);
+const Widget verticalSpaceLarge = SizedBox(height: _largeSize);
+const Widget verticalSpaceMassive = SizedBox(height: _massiveSize);
+
+Widget spacedDivider = const Column(
+ children: [
+ verticalSpaceMedium,
+ Divider(color: Colors.blueGrey, height: 5.0),
+ verticalSpaceMedium,
+ ],
+);
+
+Widget verticalSpace(double height) => SizedBox(height: height);
+
+double screenWidth(BuildContext context) => MediaQuery.of(context).size.width;
+
+double screenHeight(BuildContext context) => MediaQuery.of(context).size.height;
+
+double screenHeightFraction(
+ BuildContext context, {
+ int dividedBy = 1,
+ double offsetBy = 0,
+ double max = 3000,
+}) =>
+ min((screenHeight(context) - offsetBy) / dividedBy, max);
+
+double screenWidthFraction(
+ BuildContext context, {
+ int dividedBy = 1,
+ double offsetBy = 0,
+ double max = 3000,
+}) =>
+ min((screenWidth(context) - offsetBy) / dividedBy, max);
+
+double halfScreenWidth(BuildContext context) =>
+ screenWidthFraction(context, dividedBy: 2);
+
+double thirdScreenWidth(BuildContext context) =>
+ screenWidthFraction(context, dividedBy: 3);
+
+double quarterScreenWidth(BuildContext context) =>
+ screenWidthFraction(context, dividedBy: 4);
+
+double getResponsiveHorizontalSpaceMedium(BuildContext context) =>
+ screenWidthFraction(context, dividedBy: 10);
+
+double getResponsiveSmallFontSize(BuildContext context) =>
+ getResponsiveFontSize(context, fontSize: 14, max: 15);
+
+double getResponsiveMediumFontSize(BuildContext context) =>
+ getResponsiveFontSize(context, fontSize: 16, max: 17);
+
+double getResponsiveLargeFontSize(BuildContext context) =>
+ getResponsiveFontSize(context, fontSize: 21, max: 31);
+
+double getResponsiveExtraLargeFontSize(BuildContext context) =>
+ getResponsiveFontSize(context, fontSize: 25);
+
+double getResponsiveMassiveFontSize(BuildContext context) =>
+ getResponsiveFontSize(context, fontSize: 30);
+
+double getResponsiveFontSize(
+ BuildContext context, {
+ double? fontSize,
+ double? max,
+}) {
+ max ??= 100;
+
+ var responsiveSize = min(
+ screenWidthFraction(context, dividedBy: 10) * ((fontSize ?? 100) / 100),
+ max,
+ );
+
+ return responsiveSize;
+}
+
+InputDecoration inputDecoration({
+ String? hint,
+ required bool focus,
+}) =>
+ InputDecoration(
+ hintText: hint,
+ filled: true,
+ border: border,
+ errorBorder: errorBorder,
+ enabledBorder: enabledBorder,
+ focusedBorder: focusedBorder,
+ fillColor: focus ? kcPrimaryColor.withOpacity(0.2) : kcWhiteColor,
+ );
+
+Border rightBorder = Border(
+ right: BorderSide(
+ width: 1,
+ color: kcLightGrey.withOpacity(0.25),
+ ),
+);
+
+OutlineInputBorder border =
+ const OutlineInputBorder(borderSide: BorderSide(color: kcPrimaryColor));
+OutlineInputBorder errorBorder =
+ const OutlineInputBorder(borderSide: BorderSide(color: kcPrimaryColor));
+OutlineInputBorder enabledBorder =
+ const OutlineInputBorder(borderSide: BorderSide(color: kcPrimaryColor));
+OutlineInputBorder focusedBorder =
+ const OutlineInputBorder(borderSide: BorderSide(color: kcPrimaryColor));
+
+UnderlineInputBorder searchBorder =
+ const UnderlineInputBorder(borderSide: BorderSide(color: kcPrimaryColor));
diff --git a/StudioProjects/yimaru_app/lib/ui/common/validators/onboarding_form_validator.dart b/StudioProjects/yimaru_app/lib/ui/common/validators/onboarding_form_validator.dart
new file mode 100644
index 0000000..e0b711e
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/common/validators/onboarding_form_validator.dart
@@ -0,0 +1,12 @@
+class OnboardingFormValidator {
+ static String? validateForm(String? value) {
+ if (value == null) {
+ return null;
+ }
+
+ if (value.isEmpty) {
+ return 'The field is required';
+ }
+ return null;
+ }
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/dialogs/info_alert/info_alert_dialog.dart b/StudioProjects/yimaru_app/lib/ui/dialogs/info_alert/info_alert_dialog.dart
new file mode 100644
index 0000000..fe05f6c
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/dialogs/info_alert/info_alert_dialog.dart
@@ -0,0 +1,107 @@
+import 'package:flutter/material.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:stacked/stacked.dart';
+import 'package:stacked_services/stacked_services.dart';
+
+import 'info_alert_dialog_model.dart';
+
+const double _graphicSize = 60;
+
+class InfoAlertDialog extends StackedView {
+ final DialogRequest request;
+ final Function(DialogResponse) completer;
+
+ const InfoAlertDialog({
+ Key? key,
+ required this.request,
+ required this.completer,
+ }) : super(key: key);
+
+ @override
+ Widget builder(
+ BuildContext context,
+ InfoAlertDialogModel viewModel,
+ Widget? child,
+ ) {
+ return Dialog(
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
+ backgroundColor: Colors.white,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ request.title!,
+ style: const TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w900,
+ ),
+ ),
+ verticalSpaceTiny,
+ Text(
+ request.description!,
+ style: const TextStyle(
+ fontSize: 14,
+ color: kcMediumGrey,
+ ),
+ maxLines: 3,
+ softWrap: true,
+ ),
+ ],
+ ),
+ ),
+ Container(
+ width: _graphicSize,
+ height: _graphicSize,
+ decoration: const BoxDecoration(
+ color: Color(0xffF6E7B0),
+ borderRadius: BorderRadius.all(
+ Radius.circular(_graphicSize / 2),
+ ),
+ ),
+ alignment: Alignment.center,
+ child: const Text('⭐️', style: TextStyle(fontSize: 30)),
+ ),
+ ],
+ ),
+ verticalSpaceMedium,
+ GestureDetector(
+ onTap: () => completer(DialogResponse(confirmed: true)),
+ child: Container(
+ height: 50,
+ width: double.infinity,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ color: Colors.black,
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: const Text(
+ 'Got it',
+ style: TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ @override
+ InfoAlertDialogModel viewModelBuilder(BuildContext context) =>
+ InfoAlertDialogModel();
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/dialogs/info_alert/info_alert_dialog_model.dart b/StudioProjects/yimaru_app/lib/ui/dialogs/info_alert/info_alert_dialog_model.dart
new file mode 100644
index 0000000..0070aa1
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/dialogs/info_alert/info_alert_dialog_model.dart
@@ -0,0 +1,3 @@
+import 'package:stacked/stacked.dart';
+
+class InfoAlertDialogModel extends BaseViewModel {}
diff --git a/StudioProjects/yimaru_app/lib/ui/shared/widgets/custom_elevated_button.dart b/StudioProjects/yimaru_app/lib/ui/shared/widgets/custom_elevated_button.dart
new file mode 100644
index 0000000..c5b94e0
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/shared/widgets/custom_elevated_button.dart
@@ -0,0 +1,44 @@
+import 'package:flutter/material.dart';
+
+class CustomElevatedButton extends StatelessWidget {
+ final String text;
+ final double width;
+ final double height;
+ final double borderRadius;
+ final Color backgroundColor;
+ final Color foregroundColor;
+ final GestureTapCallback? onTap;
+
+ const CustomElevatedButton({
+ super.key,
+ this.onTap,
+ required this.text,
+ required this.height,
+ this.borderRadius = 0,
+ required this.backgroundColor,
+ required this.foregroundColor,
+ this.width = double.maxFinite,
+ });
+
+ @override
+ Widget build(BuildContext context) => _buildButtonWrapper();
+
+ Widget _buildButtonWrapper() =>
+ SizedBox(height: 50, width: width, child: _buildButton());
+
+ Widget _buildButton() => OutlinedButton(
+ onPressed: onTap,
+ style: OutlinedButton.styleFrom(
+ side: BorderSide.none,
+ backgroundColor: backgroundColor,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(borderRadius)),
+ ),
+ child: _buildText(),
+ );
+
+ Widget _buildText() => Text(
+ text,
+ style: TextStyle(color: foregroundColor, fontWeight: FontWeight.bold),
+ );
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/home/home_view.dart b/StudioProjects/yimaru_app/lib/ui/views/home/home_view.dart
new file mode 100644
index 0000000..26b32ed
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/home/home_view.dart
@@ -0,0 +1,74 @@
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+
+import 'home_viewmodel.dart';
+
+class HomeView extends StackedView {
+ const HomeView({Key? key}) : super(key: key);
+
+ @override
+ Widget builder(BuildContext context, HomeViewModel viewModel, Widget? child) {
+ return Scaffold(
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 25.0),
+ child: Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ verticalSpaceLarge,
+ Column(
+ children: [
+ const Text(
+ 'Hello, STACKED!',
+ style: TextStyle(
+ fontSize: 35,
+ fontWeight: FontWeight.w900,
+ ),
+ ),
+ verticalSpaceMedium,
+ MaterialButton(
+ color: Colors.black,
+ onPressed: viewModel.incrementCounter,
+ child: Text(
+ viewModel.counterLabel,
+ style: const TextStyle(color: Colors.white),
+ ),
+ ),
+ ],
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ MaterialButton(
+ color: kcDarkGreyColor,
+ onPressed: viewModel.showDialog,
+ child: const Text(
+ 'Show Dialog',
+ style: TextStyle(color: Colors.white),
+ ),
+ ),
+ MaterialButton(
+ color: kcDarkGreyColor,
+ onPressed: viewModel.showBottomSheet,
+ child: const Text(
+ 'Show Bottom Sheet',
+ style: TextStyle(color: Colors.white),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ @override
+ HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel();
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/home/home_viewmodel.dart b/StudioProjects/yimaru_app/lib/ui/views/home/home_viewmodel.dart
new file mode 100644
index 0000000..5928270
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/home/home_viewmodel.dart
@@ -0,0 +1,36 @@
+import 'package:yimaru_app/app/app.bottomsheets.dart';
+import 'package:yimaru_app/app/app.dialogs.dart';
+import 'package:yimaru_app/app/app.locator.dart';
+import 'package:yimaru_app/ui/common/app_strings.dart';
+import 'package:stacked/stacked.dart';
+import 'package:stacked_services/stacked_services.dart';
+
+class HomeViewModel extends BaseViewModel {
+ final _dialogService = locator();
+ final _bottomSheetService = locator();
+
+ String get counterLabel => 'Counter is: $_counter';
+
+ int _counter = 0;
+
+ void incrementCounter() {
+ _counter++;
+ rebuildUi();
+ }
+
+ void showDialog() {
+ _dialogService.showCustomDialog(
+ variant: DialogType.infoAlert,
+ title: 'Stacked Rocks!',
+ description: 'Give stacked $_counter stars on Github',
+ );
+ }
+
+ void showBottomSheet() {
+ _bottomSheetService.showCustomSheet(
+ variant: BottomSheetType.notice,
+ title: ksHomeBottomSheetTitle,
+ description: ksHomeBottomSheetDescription,
+ );
+ }
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/age_group_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/age_group_form.dart
new file mode 100644
index 0000000..a4ee919
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/age_group_form.dart
@@ -0,0 +1,116 @@
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:yimaru_app/ui/shared/widgets/custom_elevated_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/custom_small_radio_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart';
+
+class AgeGroupForm extends ViewModelWidget {
+ const AgeGroupForm({super.key});
+
+ @override
+ Widget build(BuildContext context, OnboardingViewModel viewModel) =>
+ _buildScaffoldWrapper(viewModel);
+
+ Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
+ backgroundColor: kcBackgroundColor,
+ body: _buildScaffold(viewModel),
+ );
+
+ Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildScaffoldChildren(viewModel),
+ );
+
+ List _buildScaffoldChildren(OnboardingViewModel viewModel) =>
+ [_buildAppBar(), _buildExpandedBody(viewModel)];
+
+ Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
+ Expanded(child: _buildBodyWrapper(viewModel));
+
+ Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: _buildBody(viewModel),
+ );
+
+ Widget _buildBody(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: _buildBodyChildren(viewModel),
+ );
+
+ List _buildBodyChildren(OnboardingViewModel viewModel) =>
+ [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
+
+ Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildUpperColumnChildren(viewModel),
+ );
+
+ List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
+ verticalSpaceMedium,
+ _buildTitle(),
+ verticalSpaceSmall,
+ _buildSubTitle(),
+ verticalSpaceMedium,
+ _buildAgeGroups(viewModel)
+ ];
+
+ Widget _buildAppBar() => const OnboardingAppBar();
+
+ Widget _buildTitle() => const Text(
+ 'Which age range are you in?',
+ style: TextStyle(
+ fontSize: 25,
+ color: kcDarkGreyColor,
+ fontWeight: FontWeight.w600,
+ ),
+ );
+
+ Widget _buildSubTitle() => const Text(
+ 'We’ll personalize your learning experience based on your age.',
+ style: TextStyle(
+ color: kcMediumGrey,
+ ),
+ );
+
+ Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: viewModel.ageGroups.length,
+ itemBuilder: (context, index) => _buildAgeGroup(
+ title: viewModel.ageGroups[index],
+ selected: viewModel.isSelectedAgeGroup(viewModel.ageGroups[index]),
+ onTap: () =>
+ viewModel.setSelectedAgeGroup(viewModel.ageGroups[index]),
+ ),
+ );
+
+ Widget _buildAgeGroup(
+ {required String title,
+ required bool selected,
+ required GestureTapCallback onTap}) =>
+ CustomSmallRadioButton(
+ title: title,
+ onTap: onTap,
+ selected: selected,
+ );
+
+ Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.only(bottom: 50),
+ child: _buildContinueButton(viewModel),
+ );
+
+ Widget _buildContinueButton(OnboardingViewModel viewModel) =>
+ CustomElevatedButton(
+ height: 55,
+ text: 'Continue',
+ borderRadius: 12,
+ onTap: () => viewModel.next(),
+ foregroundColor: kcWhiteColor,
+ backgroundColor: kcPrimaryColor,
+ );
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/country_region_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/country_region_form.dart
new file mode 100644
index 0000000..b8a58a8
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/country_region_form.dart
@@ -0,0 +1,118 @@
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:yimaru_app/ui/shared/widgets/custom_elevated_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/custom_dropdown.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart';
+
+class CountryRegionForm extends ViewModelWidget {
+ const CountryRegionForm({super.key});
+
+ @override
+ Widget build(BuildContext context, OnboardingViewModel viewModel) =>
+ _buildScaffoldWrapper(viewModel);
+
+ Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
+ backgroundColor: kcBackgroundColor,
+ body: _buildScaffold(viewModel),
+ );
+
+ Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildScaffoldChildren(viewModel),
+ );
+
+ List _buildScaffoldChildren(OnboardingViewModel viewModel) =>
+ [_buildAppBar(), _buildExpandedBody(viewModel)];
+
+ Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
+ Expanded(child: _buildBodyWrapper(viewModel));
+
+ Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: _buildBody(viewModel),
+ );
+
+ Widget _buildBody(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: _buildBodyChildren(viewModel),
+ );
+
+ List _buildBodyChildren(OnboardingViewModel viewModel) =>
+ [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
+
+ Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildUpperColumnChildren(viewModel),
+ );
+
+ List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
+ verticalSpaceMedium,
+ _buildTitle(),
+ verticalSpaceSmall,
+ _buildSubTitle(),
+ verticalSpaceMedium,
+ _buildCountryDropDown(viewModel),
+ verticalSpaceMedium,
+ _buildRegionDropDown(viewModel),
+ verticalSpaceMedium,
+ ];
+
+ Widget _buildAppBar() => const OnboardingAppBar();
+
+ Widget _buildTitle() => const Text(
+ 'Where are you from?',
+ style: TextStyle(
+ fontSize: 25,
+ color: kcDarkGreyColor,
+ fontWeight: FontWeight.w600,
+ ),
+ );
+
+ Widget _buildSubTitle() => const Text(
+ 'Select your country and region from the dropdown',
+ style: TextStyle(
+ color: kcMediumGrey,
+ ),
+ );
+
+ Widget _buildCountryDropDown(OnboardingViewModel viewModel) =>
+ CustomDropDownPicker(
+ hint: 'Select country',
+ icon: _buildSearchIcon(),
+ items: (value, props) => viewModel.getCountries(),
+ onChanged: (value) {},
+ selectedItem: 'Ethiopia');
+
+ Widget _buildRegionDropDown(OnboardingViewModel viewModel) =>
+ CustomDropDownPicker(
+ hint: 'Select region',
+ icon: _buildSearchIcon(),
+ items: (value, props) => viewModel.getRegions('Addis Ababa'),
+ onChanged: (value) {},
+ selectedItem: 'Addis Ababa');
+
+ Icon _buildSearchIcon() => const Icon(
+ Icons.search,
+ color: kcPrimaryColor,
+ );
+
+ Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.only(bottom: 50),
+ child: _buildContinueButton(viewModel),
+ );
+
+ Widget _buildContinueButton(OnboardingViewModel viewModel) =>
+ CustomElevatedButton(
+ height: 55,
+ text: 'Continue',
+ borderRadius: 12,
+ onTap: () => viewModel.next(),
+ foregroundColor: kcWhiteColor,
+ backgroundColor: kcPrimaryColor,
+ );
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/educational_background_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/educational_background_form.dart
new file mode 100644
index 0000000..9dc61c6
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/educational_background_form.dart
@@ -0,0 +1,118 @@
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:yimaru_app/ui/shared/widgets/custom_elevated_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/custom_small_radio_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart';
+
+class EducationalBackgroundForm extends ViewModelWidget {
+ const EducationalBackgroundForm({super.key});
+
+ @override
+ Widget build(BuildContext context, OnboardingViewModel viewModel) =>
+ _buildScaffoldWrapper(viewModel);
+
+ Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
+ backgroundColor: kcBackgroundColor,
+ body: _buildScaffold(viewModel),
+ );
+
+ Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildScaffoldChildren(viewModel),
+ );
+
+ List _buildScaffoldChildren(OnboardingViewModel viewModel) =>
+ [_buildAppBar(), _buildExpandedBody(viewModel)];
+
+ Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
+ Expanded(child: _buildBodyWrapper(viewModel));
+
+ Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: _buildBody(viewModel),
+ );
+
+ Widget _buildBody(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: _buildBodyChildren(viewModel),
+ );
+
+ List _buildBodyChildren(OnboardingViewModel viewModel) =>
+ [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
+
+ Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildUpperColumnChildren(viewModel),
+ );
+
+ List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
+ verticalSpaceMedium,
+ _buildTitle(),
+ verticalSpaceSmall,
+ _buildSubTitle(),
+ verticalSpaceMedium,
+ _buildEducationalLevels(viewModel)
+ ];
+
+ Widget _buildAppBar() => const OnboardingAppBar();
+
+ Widget _buildTitle() => const Text(
+ 'What’s your current educational level?',
+ style: TextStyle(
+ fontSize: 25,
+ color: kcDarkGreyColor,
+ fontWeight: FontWeight.w600,
+ ),
+ );
+
+ Widget _buildSubTitle() => const Text(
+ 'This helps us tailor your lessons to your experience.',
+ style: TextStyle(
+ color: kcMediumGrey,
+ ),
+ );
+
+ Widget _buildEducationalLevels(OnboardingViewModel viewModel) =>
+ ListView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: viewModel.educationalBackgrounds.length,
+ itemBuilder: (context, index) => _buildEducationalLevel(
+ title: viewModel.educationalBackgrounds[index],
+ selected: viewModel.isSelectedEducationalBackground(
+ viewModel.educationalBackgrounds[index]),
+ onTap: () => viewModel.setSelectedEducationalBackground(
+ viewModel.educationalBackgrounds[index]),
+ ),
+ );
+
+ Widget _buildEducationalLevel(
+ {required String title,
+ required bool selected,
+ required GestureTapCallback onTap}) =>
+ CustomSmallRadioButton(
+ title: title,
+ onTap: onTap,
+ selected: selected,
+ );
+
+ Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.only(bottom: 50),
+ child: _buildContinueButton(viewModel),
+ );
+
+ Widget _buildContinueButton(OnboardingViewModel viewModel) =>
+ CustomElevatedButton(
+ height: 55,
+ text: 'Continue',
+ borderRadius: 12,
+ onTap: () => viewModel.next(),
+ foregroundColor: kcWhiteColor,
+ backgroundColor: kcPrimaryColor,
+ );
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/full_name_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/full_name_form.dart
new file mode 100644
index 0000000..0432480
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/full_name_form.dart
@@ -0,0 +1,125 @@
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:yimaru_app/ui/shared/widgets/custom_elevated_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart';
+
+import '../onboarding_view.form.dart';
+
+class FullNameForm extends ViewModelWidget {
+ final TextEditingController fullNameController;
+
+ const FullNameForm({super.key, required this.fullNameController});
+
+ @override
+ Widget build(BuildContext context, OnboardingViewModel viewModel) =>
+ _buildScaffoldWrapper(viewModel);
+
+ Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
+ backgroundColor: kcBackgroundColor,
+ body: _buildScaffold(viewModel),
+ );
+
+ Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildScaffoldChildren(viewModel),
+ );
+
+ List _buildScaffoldChildren(OnboardingViewModel viewModel) =>
+ [_buildAppBar(), _buildExpandedBody(viewModel)];
+
+ Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
+ Expanded(child: _buildBodyWrapper(viewModel));
+
+ Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: _buildBody(viewModel),
+ );
+
+ Widget _buildBody(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: _buildBodyChildren(viewModel),
+ );
+
+ List _buildBodyChildren(OnboardingViewModel viewModel) =>
+ [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
+
+ Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildUpperColumnChildren(viewModel),
+ );
+
+ List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
+ verticalSpaceMedium,
+ _buildTitle(),
+ verticalSpaceSmall,
+ _buildSubTitle(),
+ verticalSpaceLarge,
+ _buildFullNameFormField(viewModel),
+ if (viewModel.hasFullNameValidationMessage && viewModel.focusFullName)
+ verticalSpaceTiny,
+ if (viewModel.hasFullNameValidationMessage && viewModel.focusFullName)
+ _buildFullNameValidatorWrapper(viewModel)
+ ];
+
+ Widget _buildAppBar() => const OnboardingAppBar(
+ showBackButton: false,
+ );
+
+ Widget _buildTitle() => const Text(
+ 'What should we call you? 😊',
+ style: TextStyle(
+ fontSize: 25,
+ color: kcDarkGreyColor,
+ fontWeight: FontWeight.w600,
+ ),
+ );
+
+ Widget _buildSubTitle() => const Text(
+ 'We’ll use your name to personalize your learning journey.',
+ style: TextStyle(
+ color: kcMediumGrey,
+ ),
+ );
+
+ Widget _buildFullNameFormField(OnboardingViewModel viewModel) =>
+ TextFormField(
+ controller: fullNameController,
+ onTap: viewModel.setFullNameFocus,
+ decoration: inputDecoration(focus:viewModel.focusFullName),
+
+ );
+
+ Widget _buildFullNameValidatorWrapper(OnboardingViewModel viewModel) =>
+ viewModel.hasFullNameValidationMessage
+ ? _buildFullNameValidator(viewModel)
+ : Container();
+
+ Widget _buildFullNameValidator(OnboardingViewModel viewModel) => Text(
+ viewModel.fullNameValidationMessage!,
+ style: const TextStyle(
+ color: Colors.red,
+ fontSize: 12,
+ fontWeight: FontWeight.w700,
+ ),
+ );
+
+ Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.only(bottom: 50),
+ child: _buildContinueButton(viewModel),
+ );
+
+ Widget _buildContinueButton(OnboardingViewModel viewModel) =>
+ CustomElevatedButton(
+ height: 55,
+ text: 'Continue',
+ borderRadius: 12,
+ onTap: () => viewModel.next(),
+ foregroundColor: kcWhiteColor,
+ backgroundColor: kcPrimaryColor,
+ );
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_goal_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_goal_form.dart
new file mode 100644
index 0000000..3f38523
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_goal_form.dart
@@ -0,0 +1,127 @@
+import 'package:flutter/material.dart';
+import 'package:iconsax/iconsax.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:yimaru_app/ui/shared/widgets/custom_elevated_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/custom_large_radio_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart';
+
+class LearningGoalForm extends ViewModelWidget {
+ const LearningGoalForm({super.key});
+
+ IconData getIcon(int icon) {
+ switch (icon) {
+ case 0:
+ return Iconsax.book;
+ case 1:
+ return Iconsax.microphone;
+ case 2:
+ return Iconsax.bag;
+ }
+ return Icons.book;
+ }
+
+ @override
+ Widget build(BuildContext context, OnboardingViewModel viewModel) =>
+ _buildScaffoldWrapper(viewModel);
+
+ Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
+ backgroundColor: kcBackgroundColor,
+ body: _buildScaffold(viewModel),
+ );
+
+ Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildScaffoldChildren(viewModel),
+ );
+
+ List _buildScaffoldChildren(OnboardingViewModel viewModel) =>
+ [_buildAppBar(), _buildExpandedBody(viewModel)];
+
+ Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
+ Expanded(child: _buildBodyWrapper(viewModel));
+
+ Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: _buildBody(viewModel),
+ );
+
+ Widget _buildBody(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: _buildBodyChildren(viewModel),
+ );
+
+ List _buildBodyChildren(OnboardingViewModel viewModel) =>
+ [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
+
+ Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildUpperColumnChildren(viewModel),
+ );
+
+ List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
+ verticalSpaceMedium,
+ _buildTitle(),
+ verticalSpaceMedium,
+ _buildLearningGoals(viewModel)
+ ];
+
+ Widget _buildAppBar() => const OnboardingAppBar();
+
+ Widget _buildTitle() => const Text(
+ 'Hi Johnny, Choose your learning goal.',
+ style: TextStyle(
+ fontSize: 25,
+ color: kcDarkGreyColor,
+ fontWeight: FontWeight.w600,
+ ),
+ );
+
+ Widget _buildLearningGoals(OnboardingViewModel viewModel) => ListView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: viewModel.learningGoals.length,
+ itemBuilder: (context, index) => _buildLearningGoal(
+ title: viewModel.learningGoals[index]['title'],
+ icon: getIcon(viewModel.learningGoals[index]['icon']),
+ subtitle: viewModel.learningGoals[index]['subtitle'],
+ selected: viewModel
+ .isSelectedLearningGoal(viewModel.learningGoals[index]['title']),
+ onTap: () => viewModel
+ .setSelectedLearningGoal(viewModel.learningGoals[index]['title']),
+ ),
+ );
+
+ Widget _buildLearningGoal(
+ {required String title,
+ required bool selected,
+ required IconData icon,
+ required String subtitle,
+ required GestureTapCallback onTap}) =>
+ CustomLargeRadioButton(
+ icon: icon,
+ title: title,
+ onTap: onTap,
+ subtitle: subtitle,
+ selected: selected,
+ );
+
+ Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.only(bottom: 50),
+ child: _buildContinueButton(viewModel),
+ );
+
+ Widget _buildContinueButton(OnboardingViewModel viewModel) =>
+ CustomElevatedButton(
+ height: 55,
+ text: 'Continue',
+ borderRadius: 12,
+ onTap: () => viewModel.next(),
+ foregroundColor: kcWhiteColor,
+ backgroundColor: kcPrimaryColor,
+ );
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_reason_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_reason_form.dart
new file mode 100644
index 0000000..7abdd27
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_reason_form.dart
@@ -0,0 +1,155 @@
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:yimaru_app/ui/shared/widgets/custom_elevated_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_view.form.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/custom_small_radio_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart';
+
+class LearningReasonForm extends ViewModelWidget {
+ final TextEditingController learningReasonController;
+
+ const LearningReasonForm({super.key, required this.learningReasonController});
+
+ @override
+ Widget build(BuildContext context, OnboardingViewModel viewModel) =>
+ _buildScaffoldWrapper(viewModel);
+
+ Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
+ backgroundColor: kcBackgroundColor,
+ body: _buildScaffold(viewModel),
+ );
+
+ Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildScaffoldChildren(viewModel),
+ );
+
+ List _buildScaffoldChildren(OnboardingViewModel viewModel) =>
+ [_buildAppBar(), _buildExpandedBody(viewModel)];
+
+ Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
+ Expanded(child: _buildBodyWrapper(viewModel));
+
+ Widget _buildBodyWrapper(OnboardingViewModel viewModel) =>
+ SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: _buildBody(viewModel),
+ ),
+ );
+
+ Widget _buildBody(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: _buildBodyChildren(viewModel),
+ );
+
+ List _buildBodyChildren(OnboardingViewModel viewModel) =>
+ [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
+
+ Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildUpperColumnChildren(viewModel),
+ );
+
+ List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
+ verticalSpaceMedium,
+ _buildTitle(),
+ verticalSpaceSmall,
+ _buildSubTitle(),
+ verticalSpaceMedium,
+ _buildReasons(viewModel),
+ if (viewModel.showTextBox) _buildReasonFormField(viewModel),
+ if (viewModel.showTextBox &&
+ viewModel.hasLearningReasonValidationMessage &&
+ viewModel.focusLearningReason)
+ verticalSpaceTiny,
+ if (viewModel.showTextBox &&
+ viewModel.hasLearningReasonValidationMessage &&
+ viewModel.focusLearningReason)
+ _buildReasonValidatorWrapper(viewModel),
+ verticalSpaceMedium,
+ ];
+
+ Widget _buildAppBar() => const OnboardingAppBar();
+
+ Widget _buildTitle() => const Text(
+ 'What’s your main goal for improving your English?',
+ style: TextStyle(
+ fontSize: 25,
+ color: kcDarkGreyColor,
+ fontWeight: FontWeight.w600,
+ ),
+ );
+
+ Widget _buildSubTitle() => const Text(
+ 'Your goal helps us tailor your learning journey.',
+ style: TextStyle(
+ color: kcMediumGrey,
+ ),
+ );
+
+ Widget _buildReasons(OnboardingViewModel viewModel) => ListView.builder(
+ shrinkWrap: true,
+ padding: EdgeInsets.zero,
+ itemCount: viewModel.learningReasons.length,
+ physics: const NeverScrollableScrollPhysics(),
+ itemBuilder: (context, index) => _buildLearningReason(
+ title: viewModel.learningReasons[index],
+ selected: viewModel
+ .isSelectedLearningReason(viewModel.learningReasons[index]),
+ onTap: () => viewModel
+ .setSelectedLearningReason(viewModel.learningReasons[index]),
+ ),
+ );
+
+ Widget _buildLearningReason(
+ {required String title,
+ required bool selected,
+ required GestureTapCallback onTap}) =>
+ CustomSmallRadioButton(
+ title: title,
+ onTap: onTap,
+ selected: selected,
+ );
+
+ Widget _buildReasonFormField(OnboardingViewModel viewModel) => TextFormField(
+ maxLines: 3,
+ controller: learningReasonController,
+ onTap: viewModel.setLearningReasonFocus,
+ decoration: inputDecoration(focus: true, hint: 'Write your goal…'),
+ );
+
+ Widget _buildReasonValidatorWrapper(OnboardingViewModel viewModel) =>
+ viewModel.hasLearningReasonValidationMessage
+ ? _buildReasonValidator(viewModel)
+ : Container();
+
+ Widget _buildReasonValidator(OnboardingViewModel viewModel) => Text(
+ viewModel.learningReasonValidationMessage!,
+ style: const TextStyle(
+ fontSize: 12,
+ color: Colors.red,
+ fontWeight: FontWeight.w700,
+ ),
+ );
+
+ Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.only(bottom: 50),
+ child: _buildContinueButton(viewModel),
+ );
+
+ Widget _buildContinueButton(OnboardingViewModel viewModel) =>
+ CustomElevatedButton(
+ height: 55,
+ text: 'Continue',
+ borderRadius: 12,
+ onTap: () => viewModel.next(),
+ foregroundColor: kcWhiteColor,
+ backgroundColor: kcPrimaryColor,
+ );
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/occupation_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/occupation_form.dart
new file mode 100644
index 0000000..51f6c14
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/occupation_form.dart
@@ -0,0 +1,124 @@
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/app_colors.dart';
+import 'package:yimaru_app/ui/common/ui_helpers.dart';
+import 'package:yimaru_app/ui/shared/widgets/custom_elevated_button.dart';
+import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
+import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart';
+
+import '../onboarding_view.form.dart';
+
+class OccupationForm extends ViewModelWidget {
+ final TextEditingController occupationController;
+
+ const OccupationForm({super.key, required this.occupationController});
+
+ @override
+ Widget build(BuildContext context, OnboardingViewModel viewModel) =>
+ _buildScaffoldWrapper(viewModel);
+
+ Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
+ backgroundColor: kcBackgroundColor,
+ body: _buildScaffold(viewModel),
+ );
+
+ Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildScaffoldChildren(viewModel),
+ );
+
+ List _buildScaffoldChildren(OnboardingViewModel viewModel) =>
+ [_buildAppBar(), _buildExpandedBody(viewModel)];
+
+ Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
+ Expanded(child: _buildBodyWrapper(viewModel));
+
+ Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: _buildBody(viewModel),
+ );
+
+ Widget _buildBody(OnboardingViewModel viewModel) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: _buildBodyChildren(viewModel),
+ );
+
+ List _buildBodyChildren(OnboardingViewModel viewModel) =>
+ [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
+
+ Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: _buildUpperColumnChildren(viewModel),
+ );
+
+ List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
+ verticalSpaceMedium,
+ _buildTitle(),
+ verticalSpaceSmall,
+ _buildSubTitle(),
+ verticalSpaceLarge,
+ _buildOccupationFormField(viewModel),
+ if (viewModel.hasOccupationValidationMessage &&
+ viewModel.focusOccupation)
+ verticalSpaceTiny,
+ if (viewModel.hasOccupationValidationMessage &&
+ viewModel.focusOccupation)
+ _buildOccupationValidatorWrapper(viewModel)
+ ];
+
+ Widget _buildAppBar() => const OnboardingAppBar();
+
+ Widget _buildTitle() => const Text(
+ 'What’s your occupation?',
+ style: TextStyle(
+ fontSize: 25,
+ color: kcDarkGreyColor,
+ fontWeight: FontWeight.w600,
+ ),
+ );
+
+ Widget _buildSubTitle() => const Text(
+ 'We’ll personalize your learning experience based on your occupation.',
+ style: TextStyle(
+ color: kcMediumGrey,
+ ),
+ );
+
+ Widget _buildOccupationFormField(OnboardingViewModel viewModel) =>
+ TextFormField(
+ controller: occupationController,
+ onTap: viewModel.setOccupationFocus,
+ decoration: inputDecoration(focus: viewModel.focusOccupation),
+ );
+
+ Widget _buildOccupationValidatorWrapper(OnboardingViewModel viewModel) =>
+ viewModel.hasOccupationValidationMessage
+ ? _buildOccupationValidator(viewModel)
+ : Container();
+
+ Widget _buildOccupationValidator(OnboardingViewModel viewModel) => Text(
+ viewModel.occupationValidationMessage!,
+ style: const TextStyle(
+ color: Colors.red,
+ fontSize: 12,
+ fontWeight: FontWeight.w700,
+ ),
+ );
+
+ Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
+ padding: const EdgeInsets.only(bottom: 50),
+ child: _buildContinueButton(viewModel),
+ );
+
+ Widget _buildContinueButton(OnboardingViewModel viewModel) =>
+ CustomElevatedButton(
+ height: 55,
+ text: 'Continue',
+ borderRadius: 12,
+ onTap: () => viewModel.next(),
+ foregroundColor: kcWhiteColor,
+ backgroundColor: kcPrimaryColor,
+ );
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.dart
new file mode 100644
index 0000000..e4200db
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.dart
@@ -0,0 +1,82 @@
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:stacked/stacked_annotations.dart';
+import 'package:yimaru_app/ui/views/onboarding/forms/age_group_form.dart';
+import 'package:yimaru_app/ui/views/onboarding/forms/country_region_form.dart';
+import 'package:yimaru_app/ui/views/onboarding/forms/educational_background_form.dart';
+import 'package:yimaru_app/ui/views/onboarding/forms/full_name_form.dart';
+import 'package:yimaru_app/ui/views/onboarding/forms/learning_goal_form.dart';
+import 'package:yimaru_app/ui/views/onboarding/forms/learning_reason_form.dart';
+import 'package:yimaru_app/ui/views/onboarding/forms/occupation_form.dart';
+
+import '../../common/validators/onboarding_form_validator.dart';
+import 'onboarding_viewmodel.dart';
+import 'onboarding_view.form.dart';
+
+@FormView(fields: [
+ FormTextField(
+ name: 'fullName', validator: OnboardingFormValidator.validateForm),
+ FormTextField(
+ name: 'occupation', validator: OnboardingFormValidator.validateForm),
+ FormTextField(
+ name: 'learningReason', validator: OnboardingFormValidator.validateForm)
+])
+class OnboardingView extends StackedView
+ with $OnboardingView {
+ const OnboardingView({Key? key}) : super(key: key);
+
+ @override
+ Widget builder(
+ BuildContext context,
+ OnboardingViewModel viewModel,
+ Widget? child,
+ ) =>
+ _buildOnboardingScreens(viewModel);
+
+ Widget _buildOnboardingScreens(OnboardingViewModel viewModel) => IndexedStack(
+ index: viewModel.currentStep,
+ children: _buildScreens(),
+ );
+
+ List _buildScreens() => [
+ _buildFullNameForm(),
+ _buildEducationalBackgroundForm(),
+ _buildAgeGroupForm(),
+ _buildOccupationForm(),
+ _buildCountryRegionForm(),
+ _buildLearningGoalForm(),
+ _buildLearningReasonForm()
+ ];
+
+ Widget _buildFullNameForm() => FullNameForm(
+ fullNameController: fullNameController,
+ );
+
+ Widget _buildEducationalBackgroundForm() => const EducationalBackgroundForm();
+
+ Widget _buildAgeGroupForm() => const AgeGroupForm();
+
+ Widget _buildOccupationForm() => OccupationForm(
+ occupationController: occupationController,
+ );
+
+ Widget _buildCountryRegionForm() => const CountryRegionForm();
+
+ Widget _buildLearningGoalForm() => const LearningGoalForm();
+
+ Widget _buildLearningReasonForm() => LearningReasonForm(
+ learningReasonController: learningReasonController,
+ );
+
+ @override
+ void onViewModelReady(OnboardingViewModel viewModel) {
+ syncFormWithViewModel(viewModel);
+ super.onViewModelReady(viewModel);
+ }
+
+ @override
+ OnboardingViewModel viewModelBuilder(
+ BuildContext context,
+ ) =>
+ OnboardingViewModel();
+}
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.form.dart
new file mode 100644
index 0000000..58d4ae0
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.form.dart
@@ -0,0 +1,246 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// **************************************************************************
+// StackedFormGenerator
+// **************************************************************************
+
+// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this
+
+import 'package:flutter/material.dart';
+import 'package:stacked/stacked.dart';
+import 'package:yimaru_app/ui/common/validators/onboarding_form_validator.dart';
+
+const bool _autoTextFieldValidation = true;
+
+const String FullNameValueKey = 'fullName';
+const String OccupationValueKey = 'occupation';
+const String LearningReasonValueKey = 'learningReason';
+
+final Map _OnboardingViewTextEditingControllers =
+ {};
+
+final Map _OnboardingViewFocusNodes = {};
+
+final Map _OnboardingViewTextValidations = {
+ FullNameValueKey: OnboardingFormValidator.validateForm,
+ OccupationValueKey: OnboardingFormValidator.validateForm,
+ LearningReasonValueKey: OnboardingFormValidator.validateForm,
+};
+
+mixin $OnboardingView {
+ TextEditingController get fullNameController =>
+ _getFormTextEditingController(FullNameValueKey);
+ TextEditingController get occupationController =>
+ _getFormTextEditingController(OccupationValueKey);
+ TextEditingController get learningReasonController =>
+ _getFormTextEditingController(LearningReasonValueKey);
+
+ FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
+ FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
+ FocusNode get learningReasonFocusNode =>
+ _getFormFocusNode(LearningReasonValueKey);
+
+ TextEditingController _getFormTextEditingController(
+ String key, {
+ String? initialValue,
+ }) {
+ if (_OnboardingViewTextEditingControllers.containsKey(key)) {
+ return _OnboardingViewTextEditingControllers[key]!;
+ }
+
+ _OnboardingViewTextEditingControllers[key] =
+ TextEditingController(text: initialValue);
+ return _OnboardingViewTextEditingControllers[key]!;
+ }
+
+ FocusNode _getFormFocusNode(String key) {
+ if (_OnboardingViewFocusNodes.containsKey(key)) {
+ return _OnboardingViewFocusNodes[key]!;
+ }
+ _OnboardingViewFocusNodes[key] = FocusNode();
+ return _OnboardingViewFocusNodes[key]!;
+ }
+
+ /// Registers a listener on every generated controller that calls [model.setData()]
+ /// with the latest textController values
+ void syncFormWithViewModel(FormStateHelper model) {
+ fullNameController.addListener(() => _updateFormData(model));
+ occupationController.addListener(() => _updateFormData(model));
+ learningReasonController.addListener(() => _updateFormData(model));
+
+ _updateFormData(model, forceValidate: _autoTextFieldValidation);
+ }
+
+ /// Registers a listener on every generated controller that calls [model.setData()]
+ /// with the latest textController values
+ @Deprecated(
+ 'Use syncFormWithViewModel instead.'
+ 'This feature was deprecated after 3.1.0.',
+ )
+ void listenToFormUpdated(FormViewModel model) {
+ fullNameController.addListener(() => _updateFormData(model));
+ occupationController.addListener(() => _updateFormData(model));
+ learningReasonController.addListener(() => _updateFormData(model));
+
+ _updateFormData(model, forceValidate: _autoTextFieldValidation);
+ }
+
+ /// Updates the formData on the FormViewModel
+ void _updateFormData(FormStateHelper model, {bool forceValidate = false}) {
+ model.setData(
+ model.formValueMap
+ ..addAll({
+ FullNameValueKey: fullNameController.text,
+ OccupationValueKey: occupationController.text,
+ LearningReasonValueKey: learningReasonController.text,
+ }),
+ );
+
+ if (_autoTextFieldValidation || forceValidate) {
+ updateValidationData(model);
+ }
+ }
+
+ bool validateFormFields(FormViewModel model) {
+ _updateFormData(model, forceValidate: true);
+ return model.isFormValid;
+ }
+
+ /// Calls dispose on all the generated controllers and focus nodes
+ void disposeForm() {
+ // The dispose function for a TextEditingController sets all listeners to null
+
+ for (var controller in _OnboardingViewTextEditingControllers.values) {
+ controller.dispose();
+ }
+ for (var focusNode in _OnboardingViewFocusNodes.values) {
+ focusNode.dispose();
+ }
+
+ _OnboardingViewTextEditingControllers.clear();
+ _OnboardingViewFocusNodes.clear();
+ }
+}
+
+extension ValueProperties on FormStateHelper {
+ bool get hasAnyValidationMessage => this
+ .fieldsValidationMessages
+ .values
+ .any((validation) => validation != null);
+
+ bool get isFormValid {
+ if (!_autoTextFieldValidation) this.validateForm();
+
+ return !hasAnyValidationMessage;
+ }
+
+ String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?;
+ String? get occupationValue =>
+ this.formValueMap[OccupationValueKey] as String?;
+ String? get learningReasonValue =>
+ this.formValueMap[LearningReasonValueKey] as String?;
+
+ set fullNameValue(String? value) {
+ this.setData(
+ this.formValueMap..addAll({FullNameValueKey: value}),
+ );
+
+ if (_OnboardingViewTextEditingControllers.containsKey(FullNameValueKey)) {
+ _OnboardingViewTextEditingControllers[FullNameValueKey]?.text =
+ value ?? '';
+ }
+ }
+
+ set occupationValue(String? value) {
+ this.setData(
+ this.formValueMap..addAll({OccupationValueKey: value}),
+ );
+
+ if (_OnboardingViewTextEditingControllers.containsKey(OccupationValueKey)) {
+ _OnboardingViewTextEditingControllers[OccupationValueKey]?.text =
+ value ?? '';
+ }
+ }
+
+ set learningReasonValue(String? value) {
+ this.setData(
+ this.formValueMap..addAll({LearningReasonValueKey: value}),
+ );
+
+ if (_OnboardingViewTextEditingControllers.containsKey(
+ LearningReasonValueKey)) {
+ _OnboardingViewTextEditingControllers[LearningReasonValueKey]?.text =
+ value ?? '';
+ }
+ }
+
+ bool get hasFullName =>
+ this.formValueMap.containsKey(FullNameValueKey) &&
+ (fullNameValue?.isNotEmpty ?? false);
+ bool get hasOccupation =>
+ this.formValueMap.containsKey(OccupationValueKey) &&
+ (occupationValue?.isNotEmpty ?? false);
+ bool get hasLearningReason =>
+ this.formValueMap.containsKey(LearningReasonValueKey) &&
+ (learningReasonValue?.isNotEmpty ?? false);
+
+ bool get hasFullNameValidationMessage =>
+ this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false;
+ bool get hasOccupationValidationMessage =>
+ this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false;
+ bool get hasLearningReasonValidationMessage =>
+ this.fieldsValidationMessages[LearningReasonValueKey]?.isNotEmpty ??
+ false;
+
+ String? get fullNameValidationMessage =>
+ this.fieldsValidationMessages[FullNameValueKey];
+ String? get occupationValidationMessage =>
+ this.fieldsValidationMessages[OccupationValueKey];
+ String? get learningReasonValidationMessage =>
+ this.fieldsValidationMessages[LearningReasonValueKey];
+}
+
+extension Methods on FormStateHelper {
+ setFullNameValidationMessage(String? validationMessage) =>
+ this.fieldsValidationMessages[FullNameValueKey] = validationMessage;
+ setOccupationValidationMessage(String? validationMessage) =>
+ this.fieldsValidationMessages[OccupationValueKey] = validationMessage;
+ setLearningReasonValidationMessage(String? validationMessage) =>
+ this.fieldsValidationMessages[LearningReasonValueKey] = validationMessage;
+
+ /// Clears text input fields on the Form
+ void clearForm() {
+ fullNameValue = '';
+ occupationValue = '';
+ learningReasonValue = '';
+ }
+
+ /// Validates text input fields on the Form
+ void validateForm() {
+ this.setValidationMessages({
+ FullNameValueKey: getValidationMessage(FullNameValueKey),
+ OccupationValueKey: getValidationMessage(OccupationValueKey),
+ LearningReasonValueKey: getValidationMessage(LearningReasonValueKey),
+ });
+ }
+}
+
+/// Returns the validation message for the given key
+String? getValidationMessage(String key) {
+ final validatorForKey = _OnboardingViewTextValidations[key];
+ if (validatorForKey == null) return null;
+
+ String? validationMessageForKey = validatorForKey(
+ _OnboardingViewTextEditingControllers[key]!.text,
+ );
+
+ return validationMessageForKey;
+}
+
+/// Updates the fieldsValidationMessages on the FormViewModel
+void updateValidationData(FormStateHelper model) =>
+ model.setValidationMessages({
+ FullNameValueKey: getValidationMessage(FullNameValueKey),
+ OccupationValueKey: getValidationMessage(OccupationValueKey),
+ LearningReasonValueKey: getValidationMessage(LearningReasonValueKey),
+ });
diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_viewmodel.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_viewmodel.dart
new file mode 100644
index 0000000..f9944c0
--- /dev/null
+++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_viewmodel.dart
@@ -0,0 +1,175 @@
+import 'package:stacked/stacked.dart';
+import 'package:stacked_services/stacked_services.dart';
+import '../../../app/app.locator.dart';
+import 'onboarding_view.form.dart';
+
+class OnboardingViewModel extends FormViewModel {
+ final _navigationService = locator();
+
+ int _currentStep = 0;
+
+ int get currentStep => _currentStep;
+
+ // Full name
+ bool _focusFullName = false;
+
+ bool get focusFullName => _focusFullName;
+
+ // Educational background
+ final List _educationalBackgrounds = [
+ 'No formal education',
+ 'Primary school',
+ 'Secondary /High school',
+ 'College / Diploma',
+ 'Bachelor’s and above'
+ ];
+
+ List get educationalBackgrounds => _educationalBackgrounds;
+
+ String? _selectedEducationalBackground;
+
+ String? get selectedEducationalBackground => _selectedEducationalBackground;
+
+ // Age group
+ final List _ageGroups = [
+ '8-14',
+ '15-18',
+ '19-26',
+ '26+',
+ ];
+
+ List get ageGroups => _ageGroups;
+
+ String? _selectedAgeGroup;
+
+ String? get selectedAgeGroup => _selectedAgeGroup;
+
+ // Occupation
+ bool _focusOccupation = false;
+
+ bool get focusOccupation => _focusOccupation;
+
+ // Learning goal
+ String? _selectedLearningGoal;
+
+ String? get selectedLearningGoal => _selectedLearningGoal;
+
+ final List