base_project
							
								
								
									
										45
									
								
								testflutterdd-demot1-f/authsec_flutter_new/base_project/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | ||||
| # Miscellaneous | ||||
| *.class | ||||
| *.log | ||||
| *.pyc | ||||
| *.swp | ||||
| .DS_Store | ||||
| .atom/ | ||||
| .build/ | ||||
| .buildlog/ | ||||
| .history | ||||
| .svn/ | ||||
| .swiftpm/ | ||||
| migrate_working_dir/ | ||||
| 
 | ||||
| # IntelliJ related | ||||
| *.iml | ||||
| *.ipr | ||||
| *.iws | ||||
| .idea/ | ||||
| 
 | ||||
| # The .vscode folder contains launch configuration and tasks you configure in | ||||
| # VS Code which you may wish to be included in version control, so this line | ||||
| # is commented out by default. | ||||
| #.vscode/ | ||||
| 
 | ||||
| # Flutter/Dart/Pub related | ||||
| **/doc/api/ | ||||
| **/ios/Flutter/.last_build_id | ||||
| .dart_tool/ | ||||
| .flutter-plugins | ||||
| .flutter-plugins-dependencies | ||||
| .pub-cache/ | ||||
| .pub/ | ||||
| /build/ | ||||
| 
 | ||||
| # Symbolication related | ||||
| app.*.symbols | ||||
| 
 | ||||
| # Obfuscation related | ||||
| app.*.map.json | ||||
| 
 | ||||
| # Android Studio will place build artifacts here | ||||
| /android/app/debug | ||||
| /android/app/profile | ||||
| /android/app/release | ||||
| @ -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: "17025dd88227cd9532c33fa78f5250d548d87e9a" | ||||
|   channel: "stable" | ||||
| 
 | ||||
| project_type: app | ||||
| 
 | ||||
| # Tracks metadata for the flutter migrate command | ||||
| migration: | ||||
|   platforms: | ||||
|     - platform: root | ||||
|       create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|       base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|     - platform: android | ||||
|       create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|       base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|     - platform: ios | ||||
|       create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|       base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|     - platform: linux | ||||
|       create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|       base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|     - platform: macos | ||||
|       create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|       base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|     - platform: web | ||||
|       create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|       base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|     - platform: windows | ||||
|       create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
|       base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a | ||||
| 
 | ||||
|   # 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' | ||||
							
								
								
									
										4
									
								
								testflutterdd-demot1-f/authsec_flutter_new/base_project/.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | ||||
| { | ||||
|     "java.compile.nullAnalysis.mode": "automatic", | ||||
|     "java.configuration.updateBuildConfiguration": "interactive" | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| # base_project | ||||
| 
 | ||||
| A new Flutter project. | ||||
| 
 | ||||
| ## Getting Started | ||||
| 
 | ||||
| This project is a starting point for a Flutter application. | ||||
| 
 | ||||
| A few resources to get you started if this is your first Flutter project: | ||||
| 
 | ||||
| - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) | ||||
| - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) | ||||
| 
 | ||||
| For help getting started with Flutter development, view the | ||||
| [online documentation](https://docs.flutter.dev/), which offers tutorials, | ||||
| samples, guidance on mobile development, and a full API reference. | ||||
| 
 | ||||
| 
 | ||||
| treat updating UI_DEVELOPMENT_GUIDE.md as mandatory after every change. | ||||
| @ -0,0 +1,28 @@ | ||||
| # This file configures the analyzer, which statically analyzes Dart code to | ||||
| # check for errors, warnings, and lints. | ||||
| # | ||||
| # The issues identified by the analyzer are surfaced in the UI of Dart-enabled | ||||
| # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be | ||||
| # invoked from the command line by running `flutter analyze`. | ||||
| 
 | ||||
| # The following line activates a set of recommended lints for Flutter apps, | ||||
| # packages, and plugins designed to encourage good coding practices. | ||||
| include: package:flutter_lints/flutter.yaml | ||||
| 
 | ||||
| linter: | ||||
|   # The lint rules applied to this project can be customized in the | ||||
|   # section below to disable rules from the `package:flutter_lints/flutter.yaml` | ||||
|   # included above or to enable additional rules. A list of all available lints | ||||
|   # and their documentation is published at https://dart.dev/lints. | ||||
|   # | ||||
|   # Instead of disabling a lint rule for the entire project in the | ||||
|   # section below, it can also be suppressed for a single line of code | ||||
|   # or a specific dart file by using the `// ignore: name_of_lint` and | ||||
|   # `// ignore_for_file: name_of_lint` syntax on the line or in the file | ||||
|   # producing the lint. | ||||
|   rules: | ||||
|     # avoid_print: false  # Uncomment to disable the `avoid_print` rule | ||||
|     # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule | ||||
| 
 | ||||
| # Additional information about this file can be found at | ||||
| # https://dart.dev/guides/language/analysis-options | ||||
							
								
								
									
										13
									
								
								testflutterdd-demot1-f/authsec_flutter_new/base_project/android/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | ||||
| gradle-wrapper.jar | ||||
| /.gradle | ||||
| /captures/ | ||||
| /gradlew | ||||
| /gradlew.bat | ||||
| /local.properties | ||||
| GeneratedPluginRegistrant.java | ||||
| 
 | ||||
| # Remember to never publicly share your keystore. | ||||
| # See https://flutter.dev/to/reference-keystore | ||||
| key.properties | ||||
| **/*.keystore | ||||
| **/*.jks | ||||
| @ -0,0 +1,44 @@ | ||||
| plugins { | ||||
|     id "com.android.application" | ||||
|     id "kotlin-android" | ||||
|     // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. | ||||
|     id "dev.flutter.flutter-gradle-plugin" | ||||
| } | ||||
| 
 | ||||
| android { | ||||
|     namespace = "com.example.base_project" | ||||
|     compileSdk = flutter.compileSdkVersion | ||||
|     ndkVersion = flutter.ndkVersion | ||||
| 
 | ||||
|     compileOptions { | ||||
|         sourceCompatibility = JavaVersion.VERSION_1_8 | ||||
|         targetCompatibility = JavaVersion.VERSION_1_8 | ||||
|     } | ||||
| 
 | ||||
|     kotlinOptions { | ||||
|         jvmTarget = JavaVersion.VERSION_1_8 | ||||
|     } | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). | ||||
|         applicationId = "com.example.base_project" | ||||
|         // You can update the following values to match your application needs. | ||||
|         // For more information, see: https://flutter.dev/to/review-gradle-config. | ||||
|         minSdk = 24 | ||||
|         targetSdk = flutter.targetSdkVersion | ||||
|         versionCode = flutter.versionCode | ||||
|         versionName = flutter.versionName | ||||
|     } | ||||
| 
 | ||||
|     buildTypes { | ||||
|         release { | ||||
|             // TODO: Add your own signing config for the release build. | ||||
|             // Signing with the debug keys for now, so `flutter run --release` works. | ||||
|             signingConfig = signingConfigs.debug | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| flutter { | ||||
|     source = "../.." | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <!-- The INTERNET permission is required for development. Specifically, | ||||
|          the Flutter tool needs it to communicate with the running application | ||||
|          to allow setting breakpoints, to provide hot reload, etc. | ||||
|     --> | ||||
|     <uses-permission android:name="android.permission.INTERNET"/> | ||||
| </manifest> | ||||
| @ -0,0 +1,49 @@ | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||
|     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | ||||
|     <application | ||||
|         android:label="base_project" | ||||
|         android:name="${applicationName}" | ||||
|         android:icon="@mipmap/ic_launcher"> | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|             android:exported="true" | ||||
|             android:launchMode="singleTop" | ||||
|             android:taskAffinity="" | ||||
|             android:theme="@style/LaunchTheme" | ||||
|             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" | ||||
|             android:hardwareAccelerated="true" | ||||
|             android:windowSoftInputMode="adjustResize"> | ||||
|             <!-- Specifies an Android theme to apply to this Activity as soon as | ||||
|                  the Android process has started. This theme is visible to the user | ||||
|                  while the Flutter UI initializes. After that, this theme continues | ||||
|                  to determine the Window background behind the Flutter UI. --> | ||||
|             <meta-data | ||||
|                 android:name="io.flutter.embedding.android.NormalTheme" | ||||
|                 android:resource="@style/NormalTheme" | ||||
|             /> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|         <!-- Don't delete the meta-data below. | ||||
|              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> | ||||
|         <meta-data | ||||
|             android:name="flutterEmbedding" | ||||
|             android:value="2" /> | ||||
|     </application> | ||||
|     <!-- Required to query activities that can process text, see: | ||||
|          https://developer.android.com/training/package-visibility and | ||||
|          https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT. | ||||
| 
 | ||||
|          In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> | ||||
|     <queries> | ||||
|         <intent> | ||||
|             <action android:name="android.intent.action.PROCESS_TEXT" /> | ||||
|             <data android:mimeType="text/plain" /> | ||||
|         </intent> | ||||
|     </queries> | ||||
| </manifest> | ||||
| @ -0,0 +1,5 @@ | ||||
| package com.example.base_project | ||||
| 
 | ||||
| import io.flutter.embedding.android.FlutterActivity | ||||
| 
 | ||||
| class MainActivity: FlutterActivity() | ||||
| @ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Modify this file to customize your launch splash screen --> | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <item android:drawable="?android:colorBackground" /> | ||||
| 
 | ||||
|     <!-- You can insert your own image assets here --> | ||||
|     <!-- <item> | ||||
|         <bitmap | ||||
|             android:gravity="center" | ||||
|             android:src="@mipmap/launch_image" /> | ||||
|     </item> --> | ||||
| </layer-list> | ||||
| @ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Modify this file to customize your launch splash screen --> | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <item android:drawable="@android:color/white" /> | ||||
| 
 | ||||
|     <!-- You can insert your own image assets here --> | ||||
|     <!-- <item> | ||||
|         <bitmap | ||||
|             android:gravity="center" | ||||
|             android:src="@mipmap/launch_image" /> | ||||
|     </item> --> | ||||
| </layer-list> | ||||
| After Width: | Height: | Size: 544 B | 
| After Width: | Height: | Size: 442 B | 
| After Width: | Height: | Size: 721 B | 
| After Width: | Height: | Size: 1.0 KiB | 
| After Width: | Height: | Size: 1.4 KiB | 
| @ -0,0 +1,18 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> | ||||
|     <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> | ||||
|         <!-- Show a splash screen on the activity. Automatically removed when | ||||
|              the Flutter engine draws its first frame --> | ||||
|         <item name="android:windowBackground">@drawable/launch_background</item> | ||||
|     </style> | ||||
|     <!-- Theme applied to the Android Window as soon as the process has started. | ||||
|          This theme determines the color of the Android Window while your | ||||
|          Flutter UI initializes, as well as behind your Flutter UI while its | ||||
|          running. | ||||
| 
 | ||||
|          This Theme is only used starting with V2 of Flutter's Android embedding. --> | ||||
|     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> | ||||
|         <item name="android:windowBackground">?android:colorBackground</item> | ||||
|     </style> | ||||
| </resources> | ||||
| @ -0,0 +1,18 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> | ||||
|     <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> | ||||
|         <!-- Show a splash screen on the activity. Automatically removed when | ||||
|              the Flutter engine draws its first frame --> | ||||
|         <item name="android:windowBackground">@drawable/launch_background</item> | ||||
|     </style> | ||||
|     <!-- Theme applied to the Android Window as soon as the process has started. | ||||
|          This theme determines the color of the Android Window while your | ||||
|          Flutter UI initializes, as well as behind your Flutter UI while its | ||||
|          running. | ||||
| 
 | ||||
|          This Theme is only used starting with V2 of Flutter's Android embedding. --> | ||||
|     <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> | ||||
|         <item name="android:windowBackground">?android:colorBackground</item> | ||||
|     </style> | ||||
| </resources> | ||||
| @ -0,0 +1,7 @@ | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <!-- The INTERNET permission is required for development. Specifically, | ||||
|          the Flutter tool needs it to communicate with the running application | ||||
|          to allow setting breakpoints, to provide hot reload, etc. | ||||
|     --> | ||||
|     <uses-permission android:name="android.permission.INTERNET"/> | ||||
| </manifest> | ||||
| @ -0,0 +1,21 @@ | ||||
| allprojects { | ||||
|     repositories { | ||||
|         google() | ||||
|         mavenCentral() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| rootProject.buildDir = "../build" | ||||
| subprojects { | ||||
|     project.buildDir = "${rootProject.buildDir}/${project.name}" | ||||
| } | ||||
| subprojects { | ||||
|     project.evaluationDependsOn(":app") | ||||
| } | ||||
| 
 | ||||
| tasks.register("clean", Delete) { | ||||
|     delete rootProject.buildDir | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1,6 @@ | ||||
| org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError | ||||
| android.useAndroidX=true | ||||
| android.enableJetifier=true | ||||
| android.nonTransitiveRClass=true | ||||
| android.defaults.buildfeatures.namespaces=false | ||||
| 
 | ||||
| @ -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.3-all.zip | ||||
| @ -0,0 +1,25 @@ | ||||
| pluginManagement { | ||||
|     def flutterSdkPath = { | ||||
|         def properties = new Properties() | ||||
|         file("local.properties").withInputStream { properties.load(it) } | ||||
|         def flutterSdkPath = properties.getProperty("flutter.sdk") | ||||
|         assert flutterSdkPath != null, "flutter.sdk not set in local.properties" | ||||
|         return flutterSdkPath | ||||
|     }() | ||||
| 
 | ||||
|     includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") | ||||
| 
 | ||||
|     repositories { | ||||
|         google() | ||||
|         mavenCentral() | ||||
|         gradlePluginPortal() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| plugins { | ||||
|     id "dev.flutter.flutter-plugin-loader" version "1.0.0" | ||||
|     id "com.android.application" version "8.2.1" apply false | ||||
|     id "org.jetbrains.kotlin.android" version "1.8.22" apply false | ||||
| } | ||||
| 
 | ||||
| include ":app" | ||||
| @ -0,0 +1,6 @@ | ||||
| <svg width="93" height="87" viewBox="0 0 93 87" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <circle cx="33.9429" cy="36.8292" r="6.82918" fill="#AF2D2D"/> | ||||
| <circle cx="79" cy="27" r="13" fill="#AF2D2D"/> | ||||
| <path fill-rule="evenodd" clip-rule="evenodd" d="M32.5511 49.4389C32.4191 48.7782 31.9357 48.2428 31.2918 48.0442C30.5686 47.8212 29.7827 48.0647 29.3124 48.6576L26.6097 52.0648C24.7585 54.3984 21.7904 55.5017 18.8235 55.2372C11.5519 54.5888 4.04597 55.1701 -3.42021 57.1407C-37.9518 66.255 -58.5567 101.637 -49.4425 136.169C-40.3282 170.7 -4.9463 191.305 29.5853 182.191C64.1169 173.077 84.7218 137.695 75.6076 103.163C70.3813 83.3617 56.5177 68.1397 39.13 60.4748C36.2342 59.1983 34.0008 56.6968 33.3809 53.5935L32.5511 49.4389Z" fill="#AF2D2D"/> | ||||
| <circle cx="21.5475" cy="4.5476" r="3.49096" transform="rotate(-22.0902 21.5475 4.5476)" fill="#AF2D2D"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 869 B | 
| @ -0,0 +1,3 @@ | ||||
| <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <path d="M25.8137 9.6379C26.6421 8.8095 26.6421 7.46638 25.8137 6.63797L25.3622 6.18649C24.5338 5.35808 23.1907 5.35808 22.3623 6.18649L16.0002 12.5486L9.63815 6.18649C8.80974 5.35809 7.46662 5.35808 6.63822 6.18649L6.18673 6.63797C5.35833 7.46638 5.35833 8.80949 6.18674 9.6379L12.5488 16L6.18674 22.362C5.35833 23.1904 5.35833 24.5336 6.18674 25.362L6.63822 25.8134C7.46662 26.6419 8.80974 26.6419 9.63814 25.8134L16.0002 19.4514L22.3623 25.8134C23.1907 26.6418 24.5338 26.6419 25.3622 25.8134L25.8137 25.362C26.6421 24.5336 26.6421 23.1904 25.8137 22.362L19.4516 16L25.8137 9.6379Z" fill="white"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 704 B | 
| @ -0,0 +1,3 @@ | ||||
| <svg width="78" height="78" viewBox="0 0 78 78" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <path fill-rule="evenodd" clip-rule="evenodd" d="M47.8055 67.6794C48.1344 67.4706 48.4933 67.3124 48.8664 67.2006C63.0117 62.9621 73.3199 49.8448 73.3199 34.32C73.3199 15.3656 57.9543 0 38.9999 0C20.0455 0 4.67993 15.3656 4.67993 34.32C4.67993 47.8227 12.4777 59.5042 23.8158 65.1068C23.8286 65.1132 23.8233 65.1326 23.8091 65.1315C23.7999 65.1308 23.793 65.1396 23.7957 65.1483L26.9037 74.9903C27.6946 77.4949 30.6437 78.5705 32.8614 77.1631L47.8055 67.6794Z" fill="#801336"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 581 B | 
| @ -0,0 +1,11 @@ | ||||
| <?xml version='1.0' encoding='iso-8859-1'?> | ||||
| <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> | ||||
| <svg fill="#000000" height="800px" width="800px" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 363.188 363.188" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 363.188 363.188"> | ||||
|   <g> | ||||
|     <path d="m111.667,132.311c-61.574,0-111.667,50.093-111.667,111.666s50.093,111.667 111.667,111.667 111.667-50.094 111.667-111.667-50.094-111.666-111.667-111.666zm0,208.333c-53.303,0-96.667-43.364-96.667-96.667 0-53.302 43.364-96.666 96.667-96.666s96.667,43.364 96.667,96.666c-0.001,53.303-43.365,96.667-96.667,96.667z"/> | ||||
|     <path d="m111.667,173.977c-4.142,0-7.5,3.357-7.5,7.5s3.358,7.5 7.5,7.5c30.327,0 55,24.673 55,55 0,4.143 3.358,7.5 7.5,7.5s7.5-3.357 7.5-7.5c0-38.598-31.402-70-70-70z"/> | ||||
|     <path d="m298.333,69.835c-35.761,0-64.855,29.094-64.855,64.855 0,35.761 29.094,64.854 64.855,64.854s64.855-29.094 64.855-64.854c-5.68434e-14-35.761-29.093-64.855-64.855-64.855zm0,114.71c-27.49,0-49.855-22.364-49.855-49.854s22.365-49.855 49.855-49.855 49.855,22.365 49.855,49.855-22.364,49.854-49.855,49.854z"/> | ||||
|     <path d="m302.012,157.925c-14.84,0-26.913-12.073-26.913-26.913 0-4.143-3.358-7.5-7.5-7.5s-7.5,3.357-7.5,7.5c0,23.111 18.802,41.913 41.913,41.913 4.142,0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/> | ||||
|     <path d="m123.358,96.568c24.544,0 44.512-19.968 44.512-44.512s-19.968-44.512-44.512-44.512-44.512,19.968-44.512,44.512 19.968,44.512 44.512,44.512zm0-74.024c16.273,3.55271e-15 29.512,13.239 29.512,29.512s-13.239,29.512-29.512,29.512-29.512-13.239-29.512-29.512 13.239-29.512 29.512-29.512z"/> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.6 KiB | 
| @ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 50 50" width="50px" height="50px"><path d="M 7.71875 6.28125 L 6.28125 7.71875 L 23.5625 25 L 6.28125 42.28125 L 7.71875 43.71875 L 25 26.4375 L 42.28125 43.71875 L 43.71875 42.28125 L 26.4375 25 L 43.71875 7.71875 L 42.28125 6.28125 L 25 23.5625 Z"/></svg> | ||||
| After Width: | Height: | Size: 311 B | 
| After Width: | Height: | Size: 4.2 KiB | 
| @ -0,0 +1,3 @@ | ||||
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <path d="M16.9999 10.9999H9.4099L12.7099 7.70994C12.8982 7.52164 13.004 7.26624 13.004 6.99994C13.004 6.73364 12.8982 6.47825 12.7099 6.28994C12.5216 6.10164 12.2662 5.99585 11.9999 5.99585C11.7336 5.99585 11.4782 6.10164 11.2899 6.28994L6.2899 11.2899C6.19886 11.385 6.12749 11.4972 6.0799 11.6199C5.97988 11.8634 5.97988 12.1365 6.0799 12.3799C6.12749 12.5027 6.19886 12.6148 6.2899 12.7099L11.2899 17.7099C11.3829 17.8037 11.4935 17.8781 11.6153 17.9288C11.7372 17.9796 11.8679 18.0057 11.9999 18.0057C12.1319 18.0057 12.2626 17.9796 12.3845 17.9288C12.5063 17.8781 12.6169 17.8037 12.7099 17.7099C12.8036 17.617 12.878 17.5064 12.9288 17.3845C12.9796 17.2627 13.0057 17.132 13.0057 16.9999C13.0057 16.8679 12.9796 16.7372 12.9288 16.6154C12.878 16.4935 12.8036 16.3829 12.7099 16.2899L9.4099 12.9999H16.9999C17.2651 12.9999 17.5195 12.8946 17.707 12.707C17.8945 12.5195 17.9999 12.2652 17.9999 11.9999C17.9999 11.7347 17.8945 11.4804 17.707 11.2928C17.5195 11.1053 17.2651 10.9999 16.9999 10.9999Z" fill="#262B35"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
| @ -0,0 +1,3 @@ | ||||
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <path d="M16.9999 10.9999H9.4099L12.7099 7.70994C12.8982 7.52164 13.004 7.26624 13.004 6.99994C13.004 6.73364 12.8982 6.47825 12.7099 6.28994C12.5216 6.10164 12.2662 5.99585 11.9999 5.99585C11.7336 5.99585 11.4782 6.10164 11.2899 6.28994L6.2899 11.2899C6.19886 11.385 6.12749 11.4972 6.0799 11.6199C5.97988 11.8634 5.97988 12.1365 6.0799 12.3799C6.12749 12.5027 6.19886 12.6148 6.2899 12.7099L11.2899 17.7099C11.3829 17.8037 11.4935 17.8781 11.6153 17.9288C11.7372 17.9796 11.8679 18.0057 11.9999 18.0057C12.1319 18.0057 12.2626 17.9796 12.3845 17.9288C12.5063 17.8781 12.6169 17.8037 12.7099 17.7099C12.8036 17.617 12.878 17.5064 12.9288 17.3845C12.9796 17.2627 13.0057 17.132 13.0057 16.9999C13.0057 16.8679 12.9796 16.7372 12.9288 16.6154C12.878 16.4935 12.8036 16.3829 12.7099 16.2899L9.4099 12.9999H16.9999C17.2651 12.9999 17.5195 12.8946 17.707 12.707C17.8945 12.5195 17.9999 12.2652 17.9999 11.9999C17.9999 11.7347 17.8945 11.4804 17.707 11.2928C17.5195 11.1053 17.2651 10.9999 16.9999 10.9999Z" fill="#262B35"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
| @ -0,0 +1,10 @@ | ||||
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <g opacity="0.5" clip-path="url(#clip0_521_2417)"> | ||||
| <path d="M13.5 0.670013C13.5 0.670013 14.24 3.32001 14.24 5.47001C14.24 7.53001 12.89 9.20001 10.83 9.20001C8.76 9.20001 7.2 7.53001 7.2 5.47001L7.23 5.11001C5.21 7.51001 4 10.62 4 14C4 18.42 7.58 22 12 22C16.42 22 20 18.42 20 14C20 8.61001 17.41 3.80001 13.5 0.670013ZM11.71 19C9.93 19 8.49 17.6 8.49 15.86C8.49 14.24 9.54 13.1 11.3 12.74C13.07 12.38 14.9 11.53 15.92 10.16C16.31 11.45 16.51 12.81 16.51 14.2C16.51 16.85 14.36 19 11.71 19Z" fill="white"/> | ||||
| </g> | ||||
| <defs> | ||||
| <clipPath id="clip0_521_2417"> | ||||
| <rect width="24" height="24" fill="white"/> | ||||
| </clipPath> | ||||
| </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 718 B | 
| @ -0,0 +1,3 @@ | ||||
| <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <path d="M16.125 9H3.875C3.64294 9 3.42038 9.07902 3.25628 9.21967C3.09219 9.36032 3 9.55109 3 9.75C3 9.94891 3.09219 10.1397 3.25628 10.2803C3.42038 10.421 3.64294 10.5 3.875 10.5H16.125C16.3571 10.5 16.5796 10.421 16.7437 10.2803C16.9078 10.1397 17 9.94891 17 9.75C17 9.55109 16.9078 9.36032 16.7437 9.21967C16.5796 9.07902 16.3571 9 16.125 9Z" fill="#75839D"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 467 B | 
| @ -0,0 +1,10 @@ | ||||
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <g opacity="0.5" clip-path="url(#clip0_521_2408)"> | ||||
| <path d="M12 8C13.1 8 14 7.1 14 6C14 4.9 13.1 4 12 4C10.9 4 10 4.9 10 6C10 7.1 10.9 8 12 8ZM12 10C10.9 10 10 10.9 10 12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12C14 10.9 13.1 10 12 10ZM12 16C10.9 16 10 16.9 10 18C10 19.1 10.9 20 12 20C13.1 20 14 19.1 14 18C14 16.9 13.1 16 12 16Z" fill="white"/> | ||||
| </g> | ||||
| <defs> | ||||
| <clipPath id="clip0_521_2408"> | ||||
| <rect width="24" height="24" fill="white"/> | ||||
| </clipPath> | ||||
| </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 559 B | 
| @ -0,0 +1,10 @@ | ||||
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <g clip-path="url(#clip0_521_2411)"> | ||||
| <path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM13 5.3L14.35 4.35C16.17 4.91 17.72 6.11 18.73 7.69L18.34 9.03L16.99 9.49L13 6.7V5.3ZM9.65 4.35L11 5.3V6.7L7.01 9.49L5.66 9.03L5.27 7.69C6.28 6.12 7.83 4.92 9.65 4.35ZM7.08 17.11L5.94 17.21C4.73 15.81 4 13.99 4 12C4 11.88 4.01 11.77 4.02 11.65L5.02 10.92L6.4 11.4L7.86 15.74L7.08 17.11ZM14.5 19.59C13.71 19.85 12.87 20 12 20C11.13 20 10.29 19.85 9.5 19.59L8.81 18.1L9.45 17H14.56L15.2 18.11L14.5 19.59ZM14.27 15H9.73L8.38 10.98L12 8.44L15.63 10.98L14.27 15ZM18.06 17.21L16.92 17.11L16.13 15.74L17.59 11.4L18.98 10.93L19.98 11.66C19.99 11.77 20 11.88 20 12C20 13.99 19.27 15.81 18.06 17.21Z" fill="white"/> | ||||
| </g> | ||||
| <defs> | ||||
| <clipPath id="clip0_521_2411"> | ||||
| <rect width="24" height="24" fill="white"/> | ||||
| </clipPath> | ||||
| </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 954 B | 
| @ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M4 22C4 17.5817 7.58172 14 12 14C16.4183 14 20 17.5817 20 22H18C18 18.6863 15.3137 16 12 16C8.68629 16 6 18.6863 6 22H4ZM12 13C8.685 13 6 10.315 6 7C6 3.685 8.685 1 12 1C15.315 1 18 3.685 18 7C18 10.315 15.315 13 12 13ZM12 11C14.21 11 16 9.21 16 7C16 4.79 14.21 3 12 3C9.79 3 8 4.79 8 7C8 9.21 9.79 11 12 11Z"></path></svg> | ||||
| After Width: | Height: | Size: 412 B | 
| @ -0,0 +1,3 @@ | ||||
| description: This file stores settings for Dart & Flutter DevTools. | ||||
| documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states | ||||
| extensions: | ||||
							
								
								
									
										34
									
								
								testflutterdd-demot1-f/authsec_flutter_new/base_project/ios/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,34 @@ | ||||
| **/dgph | ||||
| *.mode1v3 | ||||
| *.mode2v3 | ||||
| *.moved-aside | ||||
| *.pbxuser | ||||
| *.perspectivev3 | ||||
| **/*sync/ | ||||
| .sconsign.dblite | ||||
| .tags* | ||||
| **/.vagrant/ | ||||
| **/DerivedData/ | ||||
| Icon? | ||||
| **/Pods/ | ||||
| **/.symlinks/ | ||||
| profile | ||||
| xcuserdata | ||||
| **/.generated/ | ||||
| Flutter/App.framework | ||||
| Flutter/Flutter.framework | ||||
| Flutter/Flutter.podspec | ||||
| Flutter/Generated.xcconfig | ||||
| Flutter/ephemeral/ | ||||
| Flutter/app.flx | ||||
| Flutter/app.zip | ||||
| Flutter/flutter_assets/ | ||||
| Flutter/flutter_export_environment.sh | ||||
| ServiceDefinitions.json | ||||
| Runner/GeneratedPluginRegistrant.* | ||||
| 
 | ||||
| # Exceptions to above rules. | ||||
| !default.mode1v3 | ||||
| !default.mode2v3 | ||||
| !default.pbxuser | ||||
| !default.perspectivev3 | ||||
| @ -0,0 +1,26 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
|   <key>CFBundleDevelopmentRegion</key> | ||||
|   <string>en</string> | ||||
|   <key>CFBundleExecutable</key> | ||||
|   <string>App</string> | ||||
|   <key>CFBundleIdentifier</key> | ||||
|   <string>io.flutter.flutter.app</string> | ||||
|   <key>CFBundleInfoDictionaryVersion</key> | ||||
|   <string>6.0</string> | ||||
|   <key>CFBundleName</key> | ||||
|   <string>App</string> | ||||
|   <key>CFBundlePackageType</key> | ||||
|   <string>FMWK</string> | ||||
|   <key>CFBundleShortVersionString</key> | ||||
|   <string>1.0</string> | ||||
|   <key>CFBundleSignature</key> | ||||
|   <string>????</string> | ||||
|   <key>CFBundleVersion</key> | ||||
|   <string>1.0</string> | ||||
|   <key>MinimumOSVersion</key> | ||||
|   <string>12.0</string> | ||||
| </dict> | ||||
| </plist> | ||||
| @ -0,0 +1,2 @@ | ||||
| #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" | ||||
| #include "Generated.xcconfig" | ||||
| @ -0,0 +1,2 @@ | ||||
| #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" | ||||
| #include "Generated.xcconfig" | ||||
| @ -0,0 +1,41 @@ | ||||
| # Uncomment this line to define a global platform for your project | ||||
| # platform :ios, '12.0' | ||||
| 
 | ||||
| # CocoaPods analytics sends network stats synchronously affecting flutter build latency. | ||||
| ENV['COCOAPODS_DISABLE_STATS'] = 'true' | ||||
| 
 | ||||
| project 'Runner', { | ||||
|   'Debug' => :debug, | ||||
|   'Profile' => :release, | ||||
|   'Release' => :release, | ||||
| } | ||||
| 
 | ||||
| def flutter_root | ||||
|   generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) | ||||
|   unless File.exist?(generated_xcode_build_settings_path) | ||||
|     raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" | ||||
|   end | ||||
| 
 | ||||
|   File.foreach(generated_xcode_build_settings_path) do |line| | ||||
|     matches = line.match(/FLUTTER_ROOT\=(.*)/) | ||||
|     return matches[1].strip if matches | ||||
|   end | ||||
|   raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" | ||||
| end | ||||
| 
 | ||||
| require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) | ||||
| 
 | ||||
| flutter_ios_podfile_setup | ||||
| 
 | ||||
| target 'Runner' do | ||||
|   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) | ||||
|   target 'RunnerTests' do | ||||
|     inherit! :search_paths | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| post_install do |installer| | ||||
|   installer.pods_project.targets.each do |target| | ||||
|     flutter_additional_ios_build_settings(target) | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,616 @@ | ||||
| // !$*UTF8*$! | ||||
| { | ||||
| 	archiveVersion = 1; | ||||
| 	classes = { | ||||
| 	}; | ||||
| 	objectVersion = 54; | ||||
| 	objects = { | ||||
| 
 | ||||
| /* Begin PBXBuildFile section */ | ||||
| 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; | ||||
| 		331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; | ||||
| 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; | ||||
| 		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; | ||||
| 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; | ||||
| 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; | ||||
| 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; | ||||
| /* End PBXBuildFile section */ | ||||
| 
 | ||||
| /* Begin PBXContainerItemProxy section */ | ||||
| 		331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { | ||||
| 			isa = PBXContainerItemProxy; | ||||
| 			containerPortal = 97C146E61CF9000F007C117D /* Project object */; | ||||
| 			proxyType = 1; | ||||
| 			remoteGlobalIDString = 97C146ED1CF9000F007C117D; | ||||
| 			remoteInfo = Runner; | ||||
| 		}; | ||||
| /* End PBXContainerItemProxy section */ | ||||
| 
 | ||||
| /* Begin PBXCopyFilesBuildPhase section */ | ||||
| 		9705A1C41CF9048500538489 /* Embed Frameworks */ = { | ||||
| 			isa = PBXCopyFilesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			dstPath = ""; | ||||
| 			dstSubfolderSpec = 10; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			name = "Embed Frameworks"; | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| /* End PBXCopyFilesBuildPhase section */ | ||||
| 
 | ||||
| /* Begin PBXFileReference section */ | ||||
| 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; | ||||
| 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; | ||||
| 		331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; | ||||
| 		331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; | ||||
| 		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; | ||||
| 		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | ||||
| 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; | ||||
| 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; | ||||
| 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; | ||||
| 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||
| 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; | ||||
| 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; | ||||
| 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; | ||||
| 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | ||||
| /* End PBXFileReference section */ | ||||
| 
 | ||||
| /* Begin PBXFrameworksBuildPhase section */ | ||||
| 		97C146EB1CF9000F007C117D /* Frameworks */ = { | ||||
| 			isa = PBXFrameworksBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| /* End PBXFrameworksBuildPhase section */ | ||||
| 
 | ||||
| /* Begin PBXGroup section */ | ||||
| 		331C8082294A63A400263BE5 /* RunnerTests */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				331C807B294A618700263BE5 /* RunnerTests.swift */, | ||||
| 			); | ||||
| 			path = RunnerTests; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		9740EEB11CF90186004384FC /* Flutter */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, | ||||
| 				9740EEB21CF90195004384FC /* Debug.xcconfig */, | ||||
| 				7AFA3C8E1D35360C0083082E /* Release.xcconfig */, | ||||
| 				9740EEB31CF90195004384FC /* Generated.xcconfig */, | ||||
| 			); | ||||
| 			name = Flutter; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		97C146E51CF9000F007C117D = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				9740EEB11CF90186004384FC /* Flutter */, | ||||
| 				97C146F01CF9000F007C117D /* Runner */, | ||||
| 				97C146EF1CF9000F007C117D /* Products */, | ||||
| 				331C8082294A63A400263BE5 /* RunnerTests */, | ||||
| 			); | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		97C146EF1CF9000F007C117D /* Products */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				97C146EE1CF9000F007C117D /* Runner.app */, | ||||
| 				331C8081294A63A400263BE5 /* RunnerTests.xctest */, | ||||
| 			); | ||||
| 			name = Products; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		97C146F01CF9000F007C117D /* Runner */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				97C146FA1CF9000F007C117D /* Main.storyboard */, | ||||
| 				97C146FD1CF9000F007C117D /* Assets.xcassets */, | ||||
| 				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, | ||||
| 				97C147021CF9000F007C117D /* Info.plist */, | ||||
| 				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, | ||||
| 				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, | ||||
| 				74858FAE1ED2DC5600515810 /* AppDelegate.swift */, | ||||
| 				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, | ||||
| 			); | ||||
| 			path = Runner; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| /* End PBXGroup section */ | ||||
| 
 | ||||
| /* Begin PBXNativeTarget section */ | ||||
| 		331C8080294A63A400263BE5 /* RunnerTests */ = { | ||||
| 			isa = PBXNativeTarget; | ||||
| 			buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; | ||||
| 			buildPhases = ( | ||||
| 				331C807D294A63A400263BE5 /* Sources */, | ||||
| 				331C807F294A63A400263BE5 /* Resources */, | ||||
| 			); | ||||
| 			buildRules = ( | ||||
| 			); | ||||
| 			dependencies = ( | ||||
| 				331C8086294A63A400263BE5 /* PBXTargetDependency */, | ||||
| 			); | ||||
| 			name = RunnerTests; | ||||
| 			productName = RunnerTests; | ||||
| 			productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; | ||||
| 			productType = "com.apple.product-type.bundle.unit-test"; | ||||
| 		}; | ||||
| 		97C146ED1CF9000F007C117D /* Runner */ = { | ||||
| 			isa = PBXNativeTarget; | ||||
| 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; | ||||
| 			buildPhases = ( | ||||
| 				9740EEB61CF901F6004384FC /* Run Script */, | ||||
| 				97C146EA1CF9000F007C117D /* Sources */, | ||||
| 				97C146EB1CF9000F007C117D /* Frameworks */, | ||||
| 				97C146EC1CF9000F007C117D /* Resources */, | ||||
| 				9705A1C41CF9048500538489 /* Embed Frameworks */, | ||||
| 				3B06AD1E1E4923F5004D2608 /* Thin Binary */, | ||||
| 			); | ||||
| 			buildRules = ( | ||||
| 			); | ||||
| 			dependencies = ( | ||||
| 			); | ||||
| 			name = Runner; | ||||
| 			productName = Runner; | ||||
| 			productReference = 97C146EE1CF9000F007C117D /* Runner.app */; | ||||
| 			productType = "com.apple.product-type.application"; | ||||
| 		}; | ||||
| /* End PBXNativeTarget section */ | ||||
| 
 | ||||
| /* Begin PBXProject section */ | ||||
| 		97C146E61CF9000F007C117D /* Project object */ = { | ||||
| 			isa = PBXProject; | ||||
| 			attributes = { | ||||
| 				BuildIndependentTargetsInParallel = YES; | ||||
| 				LastUpgradeCheck = 1510; | ||||
| 				ORGANIZATIONNAME = ""; | ||||
| 				TargetAttributes = { | ||||
| 					331C8080294A63A400263BE5 = { | ||||
| 						CreatedOnToolsVersion = 14.0; | ||||
| 						TestTargetID = 97C146ED1CF9000F007C117D; | ||||
| 					}; | ||||
| 					97C146ED1CF9000F007C117D = { | ||||
| 						CreatedOnToolsVersion = 7.3.1; | ||||
| 						LastSwiftMigration = 1100; | ||||
| 					}; | ||||
| 				}; | ||||
| 			}; | ||||
| 			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; | ||||
| 			compatibilityVersion = "Xcode 9.3"; | ||||
| 			developmentRegion = en; | ||||
| 			hasScannedForEncodings = 0; | ||||
| 			knownRegions = ( | ||||
| 				en, | ||||
| 				Base, | ||||
| 			); | ||||
| 			mainGroup = 97C146E51CF9000F007C117D; | ||||
| 			productRefGroup = 97C146EF1CF9000F007C117D /* Products */; | ||||
| 			projectDirPath = ""; | ||||
| 			projectRoot = ""; | ||||
| 			targets = ( | ||||
| 				97C146ED1CF9000F007C117D /* Runner */, | ||||
| 				331C8080294A63A400263BE5 /* RunnerTests */, | ||||
| 			); | ||||
| 		}; | ||||
| /* End PBXProject section */ | ||||
| 
 | ||||
| /* Begin PBXResourcesBuildPhase section */ | ||||
| 		331C807F294A63A400263BE5 /* Resources */ = { | ||||
| 			isa = PBXResourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		97C146EC1CF9000F007C117D /* Resources */ = { | ||||
| 			isa = PBXResourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, | ||||
| 				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, | ||||
| 				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, | ||||
| 				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| /* End PBXResourcesBuildPhase section */ | ||||
| 
 | ||||
| /* Begin PBXShellScriptBuildPhase section */ | ||||
| 		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { | ||||
| 			isa = PBXShellScriptBuildPhase; | ||||
| 			alwaysOutOfDate = 1; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			inputPaths = ( | ||||
| 				"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", | ||||
| 			); | ||||
| 			name = "Thin Binary"; | ||||
| 			outputPaths = ( | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 			shellPath = /bin/sh; | ||||
| 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; | ||||
| 		}; | ||||
| 		9740EEB61CF901F6004384FC /* Run Script */ = { | ||||
| 			isa = PBXShellScriptBuildPhase; | ||||
| 			alwaysOutOfDate = 1; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 			); | ||||
| 			inputPaths = ( | ||||
| 			); | ||||
| 			name = "Run Script"; | ||||
| 			outputPaths = ( | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 			shellPath = /bin/sh; | ||||
| 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; | ||||
| 		}; | ||||
| /* End PBXShellScriptBuildPhase section */ | ||||
| 
 | ||||
| /* Begin PBXSourcesBuildPhase section */ | ||||
| 		331C807D294A63A400263BE5 /* Sources */ = { | ||||
| 			isa = PBXSourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 				331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| 		97C146EA1CF9000F007C117D /* Sources */ = { | ||||
| 			isa = PBXSourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, | ||||
| 				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
| /* End PBXSourcesBuildPhase section */ | ||||
| 
 | ||||
| /* Begin PBXTargetDependency section */ | ||||
| 		331C8086294A63A400263BE5 /* PBXTargetDependency */ = { | ||||
| 			isa = PBXTargetDependency; | ||||
| 			target = 97C146ED1CF9000F007C117D /* Runner */; | ||||
| 			targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; | ||||
| 		}; | ||||
| /* End PBXTargetDependency section */ | ||||
| 
 | ||||
| /* Begin PBXVariantGroup section */ | ||||
| 		97C146FA1CF9000F007C117D /* Main.storyboard */ = { | ||||
| 			isa = PBXVariantGroup; | ||||
| 			children = ( | ||||
| 				97C146FB1CF9000F007C117D /* Base */, | ||||
| 			); | ||||
| 			name = Main.storyboard; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { | ||||
| 			isa = PBXVariantGroup; | ||||
| 			children = ( | ||||
| 				97C147001CF9000F007C117D /* Base */, | ||||
| 			); | ||||
| 			name = LaunchScreen.storyboard; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| /* End PBXVariantGroup section */ | ||||
| 
 | ||||
| /* Begin XCBuildConfiguration section */ | ||||
| 		249021D3217E4FDB00AE95B9 /* Profile */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			buildSettings = { | ||||
| 				ALWAYS_SEARCH_USER_PATHS = NO; | ||||
| 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; | ||||
| 				CLANG_ANALYZER_NONNULL = YES; | ||||
| 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; | ||||
| 				CLANG_CXX_LIBRARY = "libc++"; | ||||
| 				CLANG_ENABLE_MODULES = YES; | ||||
| 				CLANG_ENABLE_OBJC_ARC = YES; | ||||
| 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; | ||||
| 				CLANG_WARN_BOOL_CONVERSION = YES; | ||||
| 				CLANG_WARN_COMMA = YES; | ||||
| 				CLANG_WARN_CONSTANT_CONVERSION = YES; | ||||
| 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; | ||||
| 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||||
| 				CLANG_WARN_EMPTY_BODY = YES; | ||||
| 				CLANG_WARN_ENUM_CONVERSION = YES; | ||||
| 				CLANG_WARN_INFINITE_RECURSION = YES; | ||||
| 				CLANG_WARN_INT_CONVERSION = YES; | ||||
| 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | ||||
| 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; | ||||
| 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | ||||
| 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||||
| 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | ||||
| 				CLANG_WARN_STRICT_PROTOTYPES = YES; | ||||
| 				CLANG_WARN_SUSPICIOUS_MOVE = YES; | ||||
| 				CLANG_WARN_UNREACHABLE_CODE = YES; | ||||
| 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||||
| 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||||
| 				COPY_PHASE_STRIP = NO; | ||||
| 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; | ||||
| 				ENABLE_NS_ASSERTIONS = NO; | ||||
| 				ENABLE_STRICT_OBJC_MSGSEND = YES; | ||||
| 				ENABLE_USER_SCRIPT_SANDBOXING = NO; | ||||
| 				GCC_C_LANGUAGE_STANDARD = gnu99; | ||||
| 				GCC_NO_COMMON_BLOCKS = YES; | ||||
| 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES; | ||||
| 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; | ||||
| 				GCC_WARN_UNDECLARED_SELECTOR = YES; | ||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 12.0; | ||||
| 				MTL_ENABLE_DEBUG_INFO = NO; | ||||
| 				SDKROOT = iphoneos; | ||||
| 				SUPPORTED_PLATFORMS = iphoneos; | ||||
| 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||
| 				VALIDATE_PRODUCT = YES; | ||||
| 			}; | ||||
| 			name = Profile; | ||||
| 		}; | ||||
| 		249021D4217E4FDB00AE95B9 /* Profile */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; | ||||
| 			buildSettings = { | ||||
| 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||||
| 				CLANG_ENABLE_MODULES = YES; | ||||
| 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | ||||
| 				ENABLE_BITCODE = NO; | ||||
| 				INFOPLIST_FILE = Runner/Info.plist; | ||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | ||||
| 					"$(inherited)", | ||||
| 					"@executable_path/Frameworks", | ||||
| 				); | ||||
| 				PRODUCT_BUNDLE_IDENTIFIER = com.example.baseProject; | ||||
| 				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.baseProject.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.baseProject.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.baseProject.RunnerTests; | ||||
| 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
| 				SWIFT_VERSION = 5.0; | ||||
| 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; | ||||
| 			}; | ||||
| 			name = Profile; | ||||
| 		}; | ||||
| 		97C147031CF9000F007C117D /* Debug */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			buildSettings = { | ||||
| 				ALWAYS_SEARCH_USER_PATHS = NO; | ||||
| 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; | ||||
| 				CLANG_ANALYZER_NONNULL = YES; | ||||
| 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; | ||||
| 				CLANG_CXX_LIBRARY = "libc++"; | ||||
| 				CLANG_ENABLE_MODULES = YES; | ||||
| 				CLANG_ENABLE_OBJC_ARC = YES; | ||||
| 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; | ||||
| 				CLANG_WARN_BOOL_CONVERSION = YES; | ||||
| 				CLANG_WARN_COMMA = YES; | ||||
| 				CLANG_WARN_CONSTANT_CONVERSION = YES; | ||||
| 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; | ||||
| 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||||
| 				CLANG_WARN_EMPTY_BODY = YES; | ||||
| 				CLANG_WARN_ENUM_CONVERSION = YES; | ||||
| 				CLANG_WARN_INFINITE_RECURSION = YES; | ||||
| 				CLANG_WARN_INT_CONVERSION = YES; | ||||
| 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | ||||
| 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; | ||||
| 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | ||||
| 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||||
| 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | ||||
| 				CLANG_WARN_STRICT_PROTOTYPES = YES; | ||||
| 				CLANG_WARN_SUSPICIOUS_MOVE = YES; | ||||
| 				CLANG_WARN_UNREACHABLE_CODE = YES; | ||||
| 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||||
| 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||||
| 				COPY_PHASE_STRIP = NO; | ||||
| 				DEBUG_INFORMATION_FORMAT = dwarf; | ||||
| 				ENABLE_STRICT_OBJC_MSGSEND = YES; | ||||
| 				ENABLE_TESTABILITY = YES; | ||||
| 				ENABLE_USER_SCRIPT_SANDBOXING = NO; | ||||
| 				GCC_C_LANGUAGE_STANDARD = gnu99; | ||||
| 				GCC_DYNAMIC_NO_PIC = NO; | ||||
| 				GCC_NO_COMMON_BLOCKS = YES; | ||||
| 				GCC_OPTIMIZATION_LEVEL = 0; | ||||
| 				GCC_PREPROCESSOR_DEFINITIONS = ( | ||||
| 					"DEBUG=1", | ||||
| 					"$(inherited)", | ||||
| 				); | ||||
| 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES; | ||||
| 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; | ||||
| 				GCC_WARN_UNDECLARED_SELECTOR = YES; | ||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 12.0; | ||||
| 				MTL_ENABLE_DEBUG_INFO = YES; | ||||
| 				ONLY_ACTIVE_ARCH = YES; | ||||
| 				SDKROOT = iphoneos; | ||||
| 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||
| 			}; | ||||
| 			name = Debug; | ||||
| 		}; | ||||
| 		97C147041CF9000F007C117D /* Release */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			buildSettings = { | ||||
| 				ALWAYS_SEARCH_USER_PATHS = NO; | ||||
| 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; | ||||
| 				CLANG_ANALYZER_NONNULL = YES; | ||||
| 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; | ||||
| 				CLANG_CXX_LIBRARY = "libc++"; | ||||
| 				CLANG_ENABLE_MODULES = YES; | ||||
| 				CLANG_ENABLE_OBJC_ARC = YES; | ||||
| 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; | ||||
| 				CLANG_WARN_BOOL_CONVERSION = YES; | ||||
| 				CLANG_WARN_COMMA = YES; | ||||
| 				CLANG_WARN_CONSTANT_CONVERSION = YES; | ||||
| 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; | ||||
| 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||||
| 				CLANG_WARN_EMPTY_BODY = YES; | ||||
| 				CLANG_WARN_ENUM_CONVERSION = YES; | ||||
| 				CLANG_WARN_INFINITE_RECURSION = YES; | ||||
| 				CLANG_WARN_INT_CONVERSION = YES; | ||||
| 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | ||||
| 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; | ||||
| 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | ||||
| 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||||
| 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | ||||
| 				CLANG_WARN_STRICT_PROTOTYPES = YES; | ||||
| 				CLANG_WARN_SUSPICIOUS_MOVE = YES; | ||||
| 				CLANG_WARN_UNREACHABLE_CODE = YES; | ||||
| 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||||
| 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||||
| 				COPY_PHASE_STRIP = NO; | ||||
| 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; | ||||
| 				ENABLE_NS_ASSERTIONS = NO; | ||||
| 				ENABLE_STRICT_OBJC_MSGSEND = YES; | ||||
| 				ENABLE_USER_SCRIPT_SANDBOXING = NO; | ||||
| 				GCC_C_LANGUAGE_STANDARD = gnu99; | ||||
| 				GCC_NO_COMMON_BLOCKS = YES; | ||||
| 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES; | ||||
| 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; | ||||
| 				GCC_WARN_UNDECLARED_SELECTOR = YES; | ||||
| 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||
| 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||
| 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||
| 				IPHONEOS_DEPLOYMENT_TARGET = 12.0; | ||||
| 				MTL_ENABLE_DEBUG_INFO = NO; | ||||
| 				SDKROOT = iphoneos; | ||||
| 				SUPPORTED_PLATFORMS = iphoneos; | ||||
| 				SWIFT_COMPILATION_MODE = wholemodule; | ||||
| 				SWIFT_OPTIMIZATION_LEVEL = "-O"; | ||||
| 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||
| 				VALIDATE_PRODUCT = YES; | ||||
| 			}; | ||||
| 			name = Release; | ||||
| 		}; | ||||
| 		97C147061CF9000F007C117D /* Debug */ = { | ||||
| 			isa = XCBuildConfiguration; | ||||
| 			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; | ||||
| 			buildSettings = { | ||||
| 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||||
| 				CLANG_ENABLE_MODULES = YES; | ||||
| 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | ||||
| 				ENABLE_BITCODE = NO; | ||||
| 				INFOPLIST_FILE = Runner/Info.plist; | ||||
| 				LD_RUNPATH_SEARCH_PATHS = ( | ||||
| 					"$(inherited)", | ||||
| 					"@executable_path/Frameworks", | ||||
| 				); | ||||
| 				PRODUCT_BUNDLE_IDENTIFIER = com.example.baseProject; | ||||
| 				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.baseProject; | ||||
| 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
| 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | ||||
| 				SWIFT_VERSION = 5.0; | ||||
| 				VERSIONING_SYSTEM = "apple-generic"; | ||||
| 			}; | ||||
| 			name = Release; | ||||
| 		}; | ||||
| /* End XCBuildConfiguration section */ | ||||
| 
 | ||||
| /* Begin XCConfigurationList section */ | ||||
| 		331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { | ||||
| 			isa = XCConfigurationList; | ||||
| 			buildConfigurations = ( | ||||
| 				331C8088294A63A400263BE5 /* Debug */, | ||||
| 				331C8089294A63A400263BE5 /* Release */, | ||||
| 				331C808A294A63A400263BE5 /* Profile */, | ||||
| 			); | ||||
| 			defaultConfigurationIsVisible = 0; | ||||
| 			defaultConfigurationName = Release; | ||||
| 		}; | ||||
| 		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { | ||||
| 			isa = XCConfigurationList; | ||||
| 			buildConfigurations = ( | ||||
| 				97C147031CF9000F007C117D /* Debug */, | ||||
| 				97C147041CF9000F007C117D /* Release */, | ||||
| 				249021D3217E4FDB00AE95B9 /* Profile */, | ||||
| 			); | ||||
| 			defaultConfigurationIsVisible = 0; | ||||
| 			defaultConfigurationName = Release; | ||||
| 		}; | ||||
| 		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { | ||||
| 			isa = XCConfigurationList; | ||||
| 			buildConfigurations = ( | ||||
| 				97C147061CF9000F007C117D /* Debug */, | ||||
| 				97C147071CF9000F007C117D /* Release */, | ||||
| 				249021D4217E4FDB00AE95B9 /* Profile */, | ||||
| 			); | ||||
| 			defaultConfigurationIsVisible = 0; | ||||
| 			defaultConfigurationName = Release; | ||||
| 		}; | ||||
| /* End XCConfigurationList section */ | ||||
| 	}; | ||||
| 	rootObject = 97C146E61CF9000F007C117D /* Project object */; | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <Workspace | ||||
|    version = "1.0"> | ||||
|    <FileRef | ||||
|       location = "self:"> | ||||
|    </FileRef> | ||||
| </Workspace> | ||||
| @ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>IDEDidComputeMac32BitWarning</key> | ||||
| 	<true/> | ||||
| </dict> | ||||
| </plist> | ||||
| @ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>PreviewsEnabled</key> | ||||
| 	<false/> | ||||
| </dict> | ||||
| </plist> | ||||
| @ -0,0 +1,101 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <Scheme | ||||
|    LastUpgradeVersion = "1510" | ||||
|    version = "1.3"> | ||||
|    <BuildAction | ||||
|       parallelizeBuildables = "YES" | ||||
|       buildImplicitDependencies = "YES"> | ||||
|       <BuildActionEntries> | ||||
|          <BuildActionEntry | ||||
|             buildForTesting = "YES" | ||||
|             buildForRunning = "YES" | ||||
|             buildForProfiling = "YES" | ||||
|             buildForArchiving = "YES" | ||||
|             buildForAnalyzing = "YES"> | ||||
|             <BuildableReference | ||||
|                BuildableIdentifier = "primary" | ||||
|                BlueprintIdentifier = "97C146ED1CF9000F007C117D" | ||||
|                BuildableName = "Runner.app" | ||||
|                BlueprintName = "Runner" | ||||
|                ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|             </BuildableReference> | ||||
|          </BuildActionEntry> | ||||
|       </BuildActionEntries> | ||||
|    </BuildAction> | ||||
|    <TestAction | ||||
|       buildConfiguration = "Debug" | ||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||
|       customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" | ||||
|       shouldUseLaunchSchemeArgsEnv = "YES"> | ||||
|       <MacroExpansion> | ||||
|          <BuildableReference | ||||
|             BuildableIdentifier = "primary" | ||||
|             BlueprintIdentifier = "97C146ED1CF9000F007C117D" | ||||
|             BuildableName = "Runner.app" | ||||
|             BlueprintName = "Runner" | ||||
|             ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|          </BuildableReference> | ||||
|       </MacroExpansion> | ||||
|       <Testables> | ||||
|          <TestableReference | ||||
|             skipped = "NO" | ||||
|             parallelizable = "YES"> | ||||
|             <BuildableReference | ||||
|                BuildableIdentifier = "primary" | ||||
|                BlueprintIdentifier = "331C8080294A63A400263BE5" | ||||
|                BuildableName = "RunnerTests.xctest" | ||||
|                BlueprintName = "RunnerTests" | ||||
|                ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|             </BuildableReference> | ||||
|          </TestableReference> | ||||
|       </Testables> | ||||
|    </TestAction> | ||||
|    <LaunchAction | ||||
|       buildConfiguration = "Debug" | ||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||
|       customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" | ||||
|       launchStyle = "0" | ||||
|       useCustomWorkingDirectory = "NO" | ||||
|       ignoresPersistentStateOnLaunch = "NO" | ||||
|       debugDocumentVersioning = "YES" | ||||
|       debugServiceExtension = "internal" | ||||
|       enableGPUValidationMode = "1" | ||||
|       allowLocationSimulation = "YES"> | ||||
|       <BuildableProductRunnable | ||||
|          runnableDebuggingMode = "0"> | ||||
|          <BuildableReference | ||||
|             BuildableIdentifier = "primary" | ||||
|             BlueprintIdentifier = "97C146ED1CF9000F007C117D" | ||||
|             BuildableName = "Runner.app" | ||||
|             BlueprintName = "Runner" | ||||
|             ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|          </BuildableReference> | ||||
|       </BuildableProductRunnable> | ||||
|    </LaunchAction> | ||||
|    <ProfileAction | ||||
|       buildConfiguration = "Profile" | ||||
|       shouldUseLaunchSchemeArgsEnv = "YES" | ||||
|       savedToolIdentifier = "" | ||||
|       useCustomWorkingDirectory = "NO" | ||||
|       debugDocumentVersioning = "YES"> | ||||
|       <BuildableProductRunnable | ||||
|          runnableDebuggingMode = "0"> | ||||
|          <BuildableReference | ||||
|             BuildableIdentifier = "primary" | ||||
|             BlueprintIdentifier = "97C146ED1CF9000F007C117D" | ||||
|             BuildableName = "Runner.app" | ||||
|             BlueprintName = "Runner" | ||||
|             ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|          </BuildableReference> | ||||
|       </BuildableProductRunnable> | ||||
|    </ProfileAction> | ||||
|    <AnalyzeAction | ||||
|       buildConfiguration = "Debug"> | ||||
|    </AnalyzeAction> | ||||
|    <ArchiveAction | ||||
|       buildConfiguration = "Release" | ||||
|       revealArchiveInOrganizer = "YES"> | ||||
|    </ArchiveAction> | ||||
| </Scheme> | ||||
| @ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <Workspace | ||||
|    version = "1.0"> | ||||
|    <FileRef | ||||
|       location = "group:Runner.xcodeproj"> | ||||
|    </FileRef> | ||||
| </Workspace> | ||||
| @ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>IDEDidComputeMac32BitWarning</key> | ||||
| 	<true/> | ||||
| </dict> | ||||
| </plist> | ||||
| @ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>PreviewsEnabled</key> | ||||
| 	<false/> | ||||
| </dict> | ||||
| </plist> | ||||
| @ -0,0 +1,13 @@ | ||||
| import Flutter | ||||
| import UIKit | ||||
| 
 | ||||
| @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) | ||||
|   } | ||||
| } | ||||
| @ -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" | ||||
|   } | ||||
| } | ||||
| After Width: | Height: | Size: 11 KiB | 
| After Width: | Height: | Size: 295 B | 
| After Width: | Height: | Size: 406 B | 
| After Width: | Height: | Size: 450 B | 
| After Width: | Height: | Size: 282 B | 
| After Width: | Height: | Size: 462 B | 
| After Width: | Height: | Size: 704 B | 
| After Width: | Height: | Size: 406 B | 
| After Width: | Height: | Size: 586 B | 
| After Width: | Height: | Size: 862 B | 
| After Width: | Height: | Size: 862 B | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 762 B | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 1.4 KiB | 
| @ -0,0 +1,23 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "filename" : "LaunchImage.png", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "filename" : "LaunchImage@2x.png", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "filename" : "LaunchImage@3x.png", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "version" : 1, | ||||
|     "author" : "xcode" | ||||
|   } | ||||
| } | ||||
| After Width: | Height: | Size: 68 B | 
| After Width: | Height: | Size: 68 B | 
| After Width: | Height: | Size: 68 B | 
| @ -0,0 +1,5 @@ | ||||
| # Launch Screen Assets | ||||
| 
 | ||||
| You can customize the launch screen with your own desired assets by replacing the image files in this directory. | ||||
| 
 | ||||
| You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. | ||||
| @ -0,0 +1,37 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> | ||||
|     <dependencies> | ||||
|         <deployment identifier="iOS"/> | ||||
|         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> | ||||
|     </dependencies> | ||||
|     <scenes> | ||||
|         <!--View Controller--> | ||||
|         <scene sceneID="EHf-IW-A2E"> | ||||
|             <objects> | ||||
|                 <viewController id="01J-lp-oVM" sceneMemberID="viewController"> | ||||
|                     <layoutGuides> | ||||
|                         <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/> | ||||
|                         <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/> | ||||
|                     </layoutGuides> | ||||
|                     <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> | ||||
|                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||
|                         <subviews> | ||||
|                             <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"> | ||||
|                             </imageView> | ||||
|                         </subviews> | ||||
|                         <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> | ||||
|                         <constraints> | ||||
|                             <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/> | ||||
|                             <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/> | ||||
|                         </constraints> | ||||
|                     </view> | ||||
|                 </viewController> | ||||
|                 <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> | ||||
|             </objects> | ||||
|             <point key="canvasLocation" x="53" y="375"/> | ||||
|         </scene> | ||||
|     </scenes> | ||||
|     <resources> | ||||
|         <image name="LaunchImage" width="168" height="185"/> | ||||
|     </resources> | ||||
| </document> | ||||
| @ -0,0 +1,26 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> | ||||
|     <dependencies> | ||||
|         <deployment identifier="iOS"/> | ||||
|         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> | ||||
|     </dependencies> | ||||
|     <scenes> | ||||
|         <!--Flutter View Controller--> | ||||
|         <scene sceneID="tne-QT-ifu"> | ||||
|             <objects> | ||||
|                 <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController"> | ||||
|                     <layoutGuides> | ||||
|                         <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> | ||||
|                         <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> | ||||
|                     </layoutGuides> | ||||
|                     <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> | ||||
|                         <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> | ||||
|                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||
|                         <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> | ||||
|                     </view> | ||||
|                 </viewController> | ||||
|                 <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> | ||||
|             </objects> | ||||
|         </scene> | ||||
|     </scenes> | ||||
| </document> | ||||
| @ -0,0 +1,49 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>CFBundleDevelopmentRegion</key> | ||||
| 	<string>$(DEVELOPMENT_LANGUAGE)</string> | ||||
| 	<key>CFBundleDisplayName</key> | ||||
| 	<string>Base Project</string> | ||||
| 	<key>CFBundleExecutable</key> | ||||
| 	<string>$(EXECUTABLE_NAME)</string> | ||||
| 	<key>CFBundleIdentifier</key> | ||||
| 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||||
| 	<key>CFBundleInfoDictionaryVersion</key> | ||||
| 	<string>6.0</string> | ||||
| 	<key>CFBundleName</key> | ||||
| 	<string>base_project</string> | ||||
| 	<key>CFBundlePackageType</key> | ||||
| 	<string>APPL</string> | ||||
| 	<key>CFBundleShortVersionString</key> | ||||
| 	<string>$(FLUTTER_BUILD_NAME)</string> | ||||
| 	<key>CFBundleSignature</key> | ||||
| 	<string>????</string> | ||||
| 	<key>CFBundleVersion</key> | ||||
| 	<string>$(FLUTTER_BUILD_NUMBER)</string> | ||||
| 	<key>LSRequiresIPhoneOS</key> | ||||
| 	<true/> | ||||
| 	<key>UILaunchStoryboardName</key> | ||||
| 	<string>LaunchScreen</string> | ||||
| 	<key>UIMainStoryboardFile</key> | ||||
| 	<string>Main</string> | ||||
| 	<key>UISupportedInterfaceOrientations</key> | ||||
| 	<array> | ||||
| 		<string>UIInterfaceOrientationPortrait</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 	</array> | ||||
| 	<key>UISupportedInterfaceOrientations~ipad</key> | ||||
| 	<array> | ||||
| 		<string>UIInterfaceOrientationPortrait</string> | ||||
| 		<string>UIInterfaceOrientationPortraitUpsideDown</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeLeft</string> | ||||
| 		<string>UIInterfaceOrientationLandscapeRight</string> | ||||
| 	</array> | ||||
| 	<key>CADisableMinimumFrameDurationOnPhone</key> | ||||
| 	<true/> | ||||
| 	<key>UIApplicationSupportsIndirectInputEvents</key> | ||||
| 	<true/> | ||||
| </dict> | ||||
| </plist> | ||||
| @ -0,0 +1 @@ | ||||
| #import "GeneratedPluginRegistrant.h" | ||||
| @ -0,0 +1,12 @@ | ||||
| import Flutter | ||||
| import UIKit | ||||
| import XCTest | ||||
| 
 | ||||
| class RunnerTests: XCTestCase { | ||||
| 
 | ||||
|   func testExample() { | ||||
|     // If you add code to the Runner application, consider adding tests here. | ||||
|     // See https://developer.apple.com/documentation/xctest for more information about using XCTest. | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,86 @@ | ||||
| import 'dart:typed_data'; | ||||
| import 'dart:io'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:file_picker/file_picker.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../utils/entity_field_store.dart'; | ||||
| 
 | ||||
| class AudioUploadField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
| 
 | ||||
|   AudioUploadField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = 'Select audio file', | ||||
|     this.isRequired = false, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (_) { | ||||
|         final items = | ||||
|             EntityFieldStore.instance.get<List<UploadItem>>(fieldKey) ?? | ||||
|                 const []; | ||||
|         if (isRequired && items.isEmpty) return '$label is required'; | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     final items = EntityFieldStore.instance.get<List<UploadItem>>(fieldKey) ?? | ||||
|         <UploadItem>[]; | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Row( | ||||
|           children: [ | ||||
|             Text(label, | ||||
|                 style: TextStyle( | ||||
|                     fontWeight: FontWeight.w600, color: colorScheme.onSurface)), | ||||
|             const Spacer(), | ||||
|             TextButton.icon( | ||||
|               onPressed: () async { | ||||
|                 final result = | ||||
|                     await FilePicker.platform.pickFiles(type: FileType.audio); | ||||
|                 if (result != null && result.files.isNotEmpty) { | ||||
|                   final f = result.files.single; | ||||
|                   final Uint8List? bytes = f.bytes ?? | ||||
|                       (f.path != null | ||||
|                           ? await File(f.path!).readAsBytes() | ||||
|                           : null); | ||||
|                   if (bytes != null) { | ||||
|                     final updated = List<UploadItem>.from(items) | ||||
|                       ..add(UploadItem(fileName: f.name, bytes: bytes)); | ||||
|                     EntityFieldStore.instance.set(fieldKey, updated); | ||||
|                     if (onChanged != null) onChanged(); | ||||
|                   } | ||||
|                 } | ||||
|               }, | ||||
|               icon: const Icon(Icons.audiotrack_rounded), | ||||
|               label: const Text('Add Audio'), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|         const SizedBox(height: 8), | ||||
|         ...items.map((u) => ListTile( | ||||
|               leading: const Icon(Icons.music_note_rounded), | ||||
|               title: Text(u.fileName, overflow: TextOverflow.ellipsis), | ||||
|               trailing: IconButton( | ||||
|                 icon: const Icon(Icons.close_rounded), | ||||
|                 onPressed: () { | ||||
|                   final updated = List<UploadItem>.from(items)..remove(u); | ||||
|                   EntityFieldStore.instance.set(fieldKey, updated); | ||||
|                   if (onChanged != null) onChanged(); | ||||
|                 }, | ||||
|               ), | ||||
|             )), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,205 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| 
 | ||||
| class AutocompleteDropdownField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final Future<List<Map<String, dynamic>>> Function() optionsLoader; | ||||
|   final String valueKey; | ||||
|   final String displayKey; | ||||
| 
 | ||||
|   AutocompleteDropdownField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     required this.optionsLoader, | ||||
|     required this.valueKey, | ||||
|     required this.displayKey, | ||||
|     this.isRequired = false, | ||||
|   })  : assert(valueKey != ''), | ||||
|         assert(displayKey != ''); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic>? get customProperties => { | ||||
|         'isAutocomplete': true, | ||||
|         'valueKey': valueKey, | ||||
|         'displayKey': displayKey, | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     // We'll lazily set the UI label into the Autocomplete's textController once options load | ||||
|     String initialLabel = ''; | ||||
| 
 | ||||
|     return FutureBuilder<List<Map<String, dynamic>>>( | ||||
|       future: optionsLoader(), | ||||
|       builder: (context, snapshot) { | ||||
|         final List<Map<String, dynamic>> options = snapshot.data ?? const []; | ||||
| 
 | ||||
|         // Resolve initial display label from stored id | ||||
|         if (snapshot.connectionState == ConnectionState.done && | ||||
|             controller.text.isNotEmpty) { | ||||
|           final match = options.firstWhere( | ||||
|             (o) => (o[valueKey]?.toString() ?? '') == controller.text, | ||||
|             orElse: () => const {}, | ||||
|           ); | ||||
|           initialLabel = | ||||
|               match.isNotEmpty ? (match[displayKey]?.toString() ?? '') : ''; | ||||
|         } | ||||
| 
 | ||||
|         final Iterable<String> displayOptions = options | ||||
|             .map((e) => e[displayKey]) | ||||
|             .where((e) => e != null) | ||||
|             .map((e) => e.toString()); | ||||
| 
 | ||||
|         return Autocomplete<String>( | ||||
|           optionsBuilder: (TextEditingValue tev) { | ||||
|             if (tev.text.isEmpty) return const Iterable<String>.empty(); | ||||
|             return displayOptions.where( | ||||
|                 (opt) => opt.toLowerCase().contains(tev.text.toLowerCase())); | ||||
|           }, | ||||
|           onSelected: (String selection) { | ||||
|             // set UI label and hidden id | ||||
|             final match = options.firstWhere( | ||||
|               (o) => (o[displayKey]?.toString() ?? '') == selection, | ||||
|               orElse: () => const {}, | ||||
|             ); | ||||
|             final idStr = | ||||
|                 match.isNotEmpty ? (match[valueKey]?.toString() ?? '') : ''; | ||||
|             controller.text = idStr; | ||||
|             onChanged?.call(); | ||||
|           }, | ||||
|           fieldViewBuilder: | ||||
|               (context, textController, focusNode, onFieldSubmitted) { | ||||
|             // Initialize UI text once options are available | ||||
|             if (initialLabel.isNotEmpty && textController.text.isEmpty) { | ||||
|               textController.text = initialLabel; | ||||
|             } | ||||
|             return ModernTextField( | ||||
|               label: label, | ||||
|               hint: hint, | ||||
|               controller: textController, | ||||
|               focusNode: focusNode, | ||||
|               validator: (v) => validator?.call(controller.text), | ||||
|               onChanged: (_) => onChanged?.call(), | ||||
|               suffixIcon: textController.text.isNotEmpty | ||||
|                   ? IconButton( | ||||
|                       icon: const Icon(Icons.clear), | ||||
|                       onPressed: () { | ||||
|                         textController.clear(); | ||||
|                         controller.clear(); | ||||
|                         onChanged?.call(); | ||||
|                       }, | ||||
|                     ) | ||||
|                   : const Icon(Icons.search), | ||||
|             ); | ||||
|           }, | ||||
|           optionsViewBuilder: (context, onSelected, optionsIt) { | ||||
|             final optionsList = optionsIt.toList(); | ||||
|             return Align( | ||||
|               alignment: Alignment.topLeft, | ||||
|               child: Material( | ||||
|                 elevation: 6, | ||||
|                 color: colorScheme.surface, | ||||
|                 shape: RoundedRectangleBorder( | ||||
|                   borderRadius: BorderRadius.circular(12), | ||||
|                   side: | ||||
|                       BorderSide(color: colorScheme.outline.withOpacity(0.15)), | ||||
|                 ), | ||||
|                 child: ConstrainedBox( | ||||
|                   constraints: | ||||
|                       const BoxConstraints(maxHeight: 280, minWidth: 240), | ||||
|                   child: optionsList.isEmpty | ||||
|                       ? Padding( | ||||
|                           padding: const EdgeInsets.all(16), | ||||
|                           child: Row( | ||||
|                             children: [ | ||||
|                               Icon(Icons.info_outline, | ||||
|                                   color: colorScheme.onSurfaceVariant), | ||||
|                               const SizedBox(width: 8), | ||||
|                               Expanded( | ||||
|                                 child: Text( | ||||
|                                   'No results', | ||||
|                                   style: TextStyle( | ||||
|                                       color: colorScheme.onSurfaceVariant), | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ], | ||||
|                           ), | ||||
|                         ) | ||||
|                       : ListView.separated( | ||||
|                           shrinkWrap: true, | ||||
|                           padding: const EdgeInsets.symmetric(vertical: 8), | ||||
|                           itemCount: optionsList.length, | ||||
|                           separatorBuilder: (_, __) => Divider( | ||||
|                               height: 1, | ||||
|                               color: colorScheme.outline.withOpacity(0.08)), | ||||
|                           itemBuilder: (context, index) { | ||||
|                             final opt = optionsList[index]; | ||||
|                             return InkWell( | ||||
|                               onTap: () => onSelected(opt), | ||||
|                               child: Padding( | ||||
|                                 padding: const EdgeInsets.symmetric( | ||||
|                                     horizontal: 12, vertical: 10), | ||||
|                                 child: _buildHighlightedText( | ||||
|                                     opt, // current query text | ||||
|                                     // Pull current query from the Autocomplete field's text | ||||
|                                     // (not stored here directly, so we simply render opt) | ||||
|                                     '', | ||||
|                                     colorScheme), | ||||
|                               ), | ||||
|                             ); | ||||
|                           }, | ||||
|                         ), | ||||
|                 ), | ||||
|               ), | ||||
|             ); | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Widget _buildHighlightedText( | ||||
|       String text, String query, ColorScheme colorScheme) { | ||||
|     if (query.isEmpty) | ||||
|       return Text(text, style: TextStyle(color: colorScheme.onSurface)); | ||||
|     final lowerText = text.toLowerCase(); | ||||
|     final lowerQuery = query.toLowerCase(); | ||||
|     final int start = lowerText.indexOf(lowerQuery); | ||||
|     if (start < 0) | ||||
|       return Text(text, style: TextStyle(color: colorScheme.onSurface)); | ||||
|     final int end = start + query.length; | ||||
|     return RichText( | ||||
|       text: TextSpan( | ||||
|         children: [ | ||||
|           TextSpan( | ||||
|               text: text.substring(0, start), | ||||
|               style: TextStyle(color: colorScheme.onSurface)), | ||||
|           TextSpan( | ||||
|               text: text.substring(start, end), | ||||
|               style: TextStyle( | ||||
|                   color: colorScheme.primary, fontWeight: FontWeight.w600)), | ||||
|           TextSpan( | ||||
|               text: text.substring(end), | ||||
|               style: TextStyle(color: colorScheme.onSurface)), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,186 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| 
 | ||||
| class AutocompleteMultiSelectField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final Future<List<String>> Function() optionsLoader; | ||||
| 
 | ||||
|   AutocompleteMultiSelectField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     required this.optionsLoader, | ||||
|     this.isRequired = false, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic>? get customProperties => const { | ||||
|         'isMultiSelect': true, | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return FutureBuilder<List<String>>( | ||||
|       future: optionsLoader(), | ||||
|       builder: (context, snapshot) { | ||||
|         final options = snapshot.data ?? const <String>[]; | ||||
|         final Set<String> selected = controller.text.isEmpty | ||||
|             ? <String>{} | ||||
|             : controller.text | ||||
|                 .split(',') | ||||
|                 .map((e) => e.trim()) | ||||
|                 .where((e) => e.isNotEmpty) | ||||
|                 .toSet(); | ||||
| 
 | ||||
|         void toggleSelection(String value) { | ||||
|           if (selected.contains(value)) { | ||||
|             selected.remove(value); | ||||
|           } else { | ||||
|             selected.add(value); | ||||
|           } | ||||
|           controller.text = selected.join(','); | ||||
|           onChanged?.call(); | ||||
|         } | ||||
| 
 | ||||
|         return Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             Autocomplete<String>( | ||||
|               optionsBuilder: (TextEditingValue tev) { | ||||
|                 if (tev.text.isEmpty) return const Iterable<String>.empty(); | ||||
|                 return options.where((opt) => | ||||
|                     opt.toLowerCase().contains(tev.text.toLowerCase())); | ||||
|               }, | ||||
|               onSelected: (String selection) => toggleSelection(selection), | ||||
|               fieldViewBuilder: | ||||
|                   (context, textController, focusNode, onFieldSubmitted) { | ||||
|                 return ModernTextField( | ||||
|                   label: label, | ||||
|                   hint: hint, | ||||
|                   controller: textController, | ||||
|                   focusNode: focusNode, | ||||
|                   onSubmitted: (_) { | ||||
|                     final v = textController.text.trim(); | ||||
|                     if (v.isNotEmpty) { | ||||
|                       toggleSelection(v); | ||||
|                       textController.clear(); | ||||
|                     } | ||||
|                   }, | ||||
|                   suffixIcon: textController.text.isNotEmpty | ||||
|                       ? IconButton( | ||||
|                           icon: const Icon(Icons.clear), | ||||
|                           onPressed: () { | ||||
|                             textController.clear(); | ||||
|                             onChanged?.call(); | ||||
|                           }, | ||||
|                         ) | ||||
|                       : const Icon(Icons.search), | ||||
|                   onChanged: (_) => onChanged?.call(), | ||||
|                 ); | ||||
|               }, | ||||
|               optionsViewBuilder: (context, onSelected, optionsIt) { | ||||
|                 final list = optionsIt.toList(); | ||||
|                 return Align( | ||||
|                   alignment: Alignment.topLeft, | ||||
|                   child: Material( | ||||
|                     elevation: 6, | ||||
|                     color: colorScheme.surface, | ||||
|                     shape: RoundedRectangleBorder( | ||||
|                       borderRadius: BorderRadius.circular(12), | ||||
|                       side: BorderSide( | ||||
|                           color: colorScheme.outline.withOpacity(0.15)), | ||||
|                     ), | ||||
|                     child: ConstrainedBox( | ||||
|                       constraints: | ||||
|                           const BoxConstraints(maxHeight: 280, minWidth: 240), | ||||
|                       child: list.isEmpty | ||||
|                           ? Padding( | ||||
|                               padding: const EdgeInsets.all(16), | ||||
|                               child: Row( | ||||
|                                 children: [ | ||||
|                                   Icon(Icons.info_outline, | ||||
|                                       color: colorScheme.onSurfaceVariant), | ||||
|                                   const SizedBox(width: 8), | ||||
|                                   Expanded( | ||||
|                                     child: Text( | ||||
|                                       'No results', | ||||
|                                       style: TextStyle( | ||||
|                                           color: colorScheme.onSurfaceVariant), | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ], | ||||
|                               ), | ||||
|                             ) | ||||
|                           : ListView.separated( | ||||
|                               shrinkWrap: true, | ||||
|                               padding: const EdgeInsets.symmetric(vertical: 8), | ||||
|                               itemCount: list.length, | ||||
|                               separatorBuilder: (_, __) => Divider( | ||||
|                                   height: 1, | ||||
|                                   color: colorScheme.outline.withOpacity(0.08)), | ||||
|                               itemBuilder: (context, index) { | ||||
|                                 final opt = list[index]; | ||||
|                                 return ListTile( | ||||
|                                   title: Text(opt), | ||||
|                                   onTap: () { | ||||
|                                     onSelected(opt); | ||||
|                                   }, | ||||
|                                 ); | ||||
|                               }, | ||||
|                             ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|             const SizedBox(height: 8), | ||||
|             Wrap( | ||||
|               spacing: 8, | ||||
|               children: selected | ||||
|                   .map((value) => Chip( | ||||
|                         label: Text(value), | ||||
|                         onDeleted: () => toggleSelection(value), | ||||
|                       )) | ||||
|                   .toList(), | ||||
|             ), | ||||
|             if (snapshot.connectionState == ConnectionState.waiting) | ||||
|               const Padding( | ||||
|                 padding: EdgeInsets.only(top: 8.0), | ||||
|                 child: LinearProgressIndicator(minHeight: 2), | ||||
|               ), | ||||
|             if (validator != null) | ||||
|               Builder( | ||||
|                 builder: (context) { | ||||
|                   final error = validator!(controller.text); | ||||
|                   return error != null | ||||
|                       ? Padding( | ||||
|                           padding: const EdgeInsets.only(top: 6), | ||||
|                           child: Text(error, | ||||
|                               style: TextStyle( | ||||
|                                   color: colorScheme.error, fontSize: 12)), | ||||
|                         ) | ||||
|                       : const SizedBox.shrink(); | ||||
|                 }, | ||||
|               ) | ||||
|           ], | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,98 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:barcode_widget/barcode_widget.dart'; | ||||
| import 'base_field.dart'; | ||||
| 
 | ||||
| class BarcodeField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final Future<String?> Function()? scanner; // returns scanned text | ||||
| 
 | ||||
|   BarcodeField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.isRequired = false, | ||||
|     this.scanner, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (v) { | ||||
|         if (isRequired && (v == null || v.trim().isEmpty)) return 'Required'; | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Text(label, | ||||
|             style: TextStyle( | ||||
|                 color: colorScheme.onSurface, fontWeight: FontWeight.w600)), | ||||
|         const SizedBox(height: 8), | ||||
|         Container( | ||||
|           width: double.infinity, | ||||
|           padding: const EdgeInsets.all(12), | ||||
|           decoration: BoxDecoration( | ||||
|             color: colorScheme.surface, | ||||
|             borderRadius: BorderRadius.circular(12), | ||||
|             border: | ||||
|                 Border.all(color: colorScheme.outlineVariant.withOpacity(0.3)), | ||||
|           ), | ||||
|           child: Column( | ||||
|             children: [ | ||||
|               if (controller.text.trim().isNotEmpty) ...[ | ||||
|                 BarcodeWidget( | ||||
|                   barcode: Barcode.code128(), | ||||
|                   data: controller.text.trim(), | ||||
|                   width: 220, | ||||
|                   height: 80, | ||||
|                 ), | ||||
|                 const SizedBox(height: 12), | ||||
|               ], | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   Expanded( | ||||
|                     child: TextFormField( | ||||
|                       controller: controller, | ||||
|                       decoration: InputDecoration( | ||||
|                         labelText: | ||||
|                             hint.isEmpty ? 'Enter or scan barcode' : hint, | ||||
|                         border: OutlineInputBorder( | ||||
|                             borderRadius: BorderRadius.circular(10)), | ||||
|                         filled: true, | ||||
|                       ), | ||||
|                       validator: validator, | ||||
|                       onChanged: (_) => onChanged?.call(), | ||||
|                     ), | ||||
|                   ), | ||||
|                   const SizedBox(width: 8), | ||||
|                   IconButton( | ||||
|                     tooltip: 'Scan Barcode', | ||||
|                     icon: Icon(Icons.document_scanner_rounded, | ||||
|                         color: colorScheme.primary), | ||||
|                     onPressed: scanner == null | ||||
|                         ? null | ||||
|                         : () async { | ||||
|                             final val = await scanner!.call(); | ||||
|                             if (val != null) { | ||||
|                               controller.text = val; | ||||
|                               onChanged?.call(); | ||||
|                             } | ||||
|                           }, | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| /// Base interface for all field types in the Entity system | ||||
| /// This allows UI to be independent of field types and enables reusability | ||||
| abstract class BaseField { | ||||
|   /// Unique identifier for the field (e.g., 'name', 'phone') | ||||
|   String get fieldKey; | ||||
|    | ||||
|   /// Display label for the field (e.g., 'Name', 'Phone Number') | ||||
|   String get label; | ||||
|    | ||||
|   /// Placeholder text for the field | ||||
|   String get hint; | ||||
|    | ||||
|   /// Whether the field is required for validation | ||||
|   bool get isRequired; | ||||
|    | ||||
|   /// Validation function for the field | ||||
|   String? Function(String?)? get validator; | ||||
|    | ||||
|   /// Main method - each field type provides its own implementation | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }); | ||||
|    | ||||
|   /// Optional: Field-specific styling properties | ||||
|   Map<String, dynamic>? get customProperties => null; | ||||
|    | ||||
|   /// Optional: Field-specific validation rules | ||||
|   String? Function(String?)? get customValidator => null; | ||||
| } | ||||
| @ -0,0 +1,158 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../ui/entity_form.dart'; | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| 
 | ||||
| /// Calculated field | ||||
| /// - Select multiple source fields from current form | ||||
| /// - Apply operation: add, subtract, multiply, divide, concat | ||||
| /// - Displays result (read-only) and stores it in controller | ||||
| class CalculatedField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final List<String> sourceKeys; // keys of other fields in same form | ||||
|   final String operation; // add|sub|mul|div|concat | ||||
| 
 | ||||
|   CalculatedField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.isRequired = false, | ||||
|     required this.sourceKeys, | ||||
|     required this.operation, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic>? get customProperties => const { | ||||
|         'isCalculated': true, | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return _CalculatedWidget( | ||||
|       label: label, | ||||
|       hint: hint, | ||||
|       controller: controller, | ||||
|       colorScheme: colorScheme, | ||||
|       sourceKeys: sourceKeys, | ||||
|       operation: operation, | ||||
|       validate: validator, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _CalculatedWidget extends StatefulWidget { | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final TextEditingController controller; | ||||
|   final ColorScheme colorScheme; | ||||
|   final List<String> sourceKeys; | ||||
|   final String operation; | ||||
|   final String? Function(String?)? validate; | ||||
| 
 | ||||
|   const _CalculatedWidget({ | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     required this.controller, | ||||
|     required this.colorScheme, | ||||
|     required this.sourceKeys, | ||||
|     required this.operation, | ||||
|     required this.validate, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<_CalculatedWidget> createState() => _CalculatedWidgetState(); | ||||
| } | ||||
| 
 | ||||
| class _CalculatedWidgetState extends State<_CalculatedWidget> { | ||||
|   List<TextEditingController> _sources = const []; | ||||
| 
 | ||||
|   @override | ||||
|   void didChangeDependencies() { | ||||
|     super.didChangeDependencies(); | ||||
|     final scope = EntityFormScope.of(context); | ||||
|     if (scope != null) { | ||||
|       _sources = widget.sourceKeys | ||||
|           .map((k) => scope.controllers[k]) | ||||
|           .whereType<TextEditingController>() | ||||
|           .toList(); | ||||
|       for (final c in _sources) { | ||||
|         c.addListener(_recompute); | ||||
|       } | ||||
|       _recompute(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     for (final c in _sources) { | ||||
|       c.removeListener(_recompute); | ||||
|     } | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   void _recompute() { | ||||
|     String result = ''; | ||||
|     if (widget.operation == 'Concatination') { | ||||
|       result = _sources | ||||
|           .map((c) => c.text.trim()) | ||||
|           .where((s) => s.isNotEmpty) | ||||
|           .join(' '); | ||||
|     } else { | ||||
|       final nums = | ||||
|           _sources.map((c) => double.tryParse(c.text.trim()) ?? 0.0).toList(); | ||||
|       if (nums.isEmpty) { | ||||
|         result = ''; | ||||
|       } else { | ||||
|         double acc = nums.first; | ||||
|         for (int i = 1; i < nums.length; i++) { | ||||
|           switch (widget.operation) { | ||||
|             case 'Addition': | ||||
|               acc += nums[i]; | ||||
|               break; | ||||
|             case 'Subtraction': | ||||
|               acc -= nums[i]; | ||||
|               break; | ||||
|             case 'Multiplication': | ||||
|               acc *= nums[i]; | ||||
|               break; | ||||
|             case 'Division': | ||||
|               if (nums[i] != 0) acc /= nums[i]; | ||||
|               break; | ||||
|           } | ||||
|         } | ||||
|         result = acc.toString(); | ||||
|       } | ||||
|     } | ||||
|     if (widget.controller.text != result) { | ||||
|       widget.controller.text = result; | ||||
|       setState(() {}); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return ModernTextField( | ||||
|       label: widget.label, | ||||
|       hint: widget.hint, | ||||
|       controller: widget.controller, | ||||
|       readOnly: true, | ||||
|       validator: widget.validate, | ||||
|       suffixIcon: const Icon(Icons.functions), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,162 @@ | ||||
| import 'dart:math'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| import 'base_field.dart'; | ||||
| 
 | ||||
| /// Generic Captcha field: shows generated code and asks user to type it | ||||
| class CaptchaField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final int length; | ||||
| 
 | ||||
|   CaptchaField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = 'Enter CAPTCHA', | ||||
|     this.isRequired = true, | ||||
|     this.length = 6, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return _CaptchaFieldWidget( | ||||
|       key: ValueKey(fieldKey), | ||||
|       label: label, | ||||
|       hint: hint, | ||||
|       length: length, | ||||
|       colorScheme: colorScheme, | ||||
|       controller: controller, | ||||
|       baseValidator: validator, | ||||
|       onChanged: onChanged, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String _generateCaptcha(int length) { | ||||
|     final random = Random(); | ||||
|     const chars = | ||||
|         'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'; | ||||
|     return String.fromCharCodes(Iterable.generate( | ||||
|       length, | ||||
|       (_) => chars.codeUnitAt(random.nextInt(chars.length)), | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _CaptchaFieldWidget extends StatefulWidget { | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final int length; | ||||
|   final ColorScheme colorScheme; | ||||
|   final TextEditingController controller; | ||||
|   final String? Function(String?)? baseValidator; | ||||
|   final VoidCallback? onChanged; | ||||
| 
 | ||||
|   const _CaptchaFieldWidget({ | ||||
|     super.key, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     required this.length, | ||||
|     required this.colorScheme, | ||||
|     required this.controller, | ||||
|     required this.baseValidator, | ||||
|     required this.onChanged, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<_CaptchaFieldWidget> createState() => _CaptchaFieldWidgetState(); | ||||
| } | ||||
| 
 | ||||
| class _CaptchaFieldWidgetState extends State<_CaptchaFieldWidget> { | ||||
|   late String _captcha; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _captcha = _generateCaptcha(widget.length); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Container( | ||||
|           padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), | ||||
|           decoration: BoxDecoration( | ||||
|             color: widget.colorScheme.primaryContainer.withOpacity(0.2), | ||||
|             borderRadius: BorderRadius.circular(12), | ||||
|             border: | ||||
|                 Border.all(color: widget.colorScheme.primary.withOpacity(0.2)), | ||||
|           ), | ||||
|           child: Row( | ||||
|             mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|             children: [ | ||||
|               Text( | ||||
|                 _captcha, | ||||
|                 style: TextStyle( | ||||
|                   color: widget.colorScheme.primary, | ||||
|                   fontSize: 20, | ||||
|                   fontWeight: FontWeight.w700, | ||||
|                   letterSpacing: 3, | ||||
|                 ), | ||||
|               ), | ||||
|               IconButton( | ||||
|                 tooltip: 'Refresh', | ||||
|                 onPressed: () { | ||||
|                   setState(() { | ||||
|                     _captcha = _generateCaptcha(widget.length); | ||||
|                   }); | ||||
|                   if (widget.onChanged != null) widget.onChanged!(); | ||||
|                 }, | ||||
|                 icon: Icon(Icons.refresh_rounded, | ||||
|                     color: widget.colorScheme.primary), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|         const SizedBox(height: 12), | ||||
|         ModernTextField( | ||||
|           controller: widget.controller, | ||||
|           hint: widget.hint, | ||||
|           validator: (value) { | ||||
|             final base = widget.baseValidator; | ||||
|             final err = base != null ? base(value) : null; | ||||
|             if (err != null) return err; | ||||
|             if ((value ?? '').trim() != _captcha) { | ||||
|               return 'CAPTCHA does not match'; | ||||
|             } | ||||
|             return null; | ||||
|           }, | ||||
|           onChanged: | ||||
|               widget.onChanged != null ? (_) => widget.onChanged!() : null, | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String _generateCaptcha(int length) { | ||||
|     final random = Random(); | ||||
|     const chars = | ||||
|         'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'; | ||||
|     return String.fromCharCodes(Iterable.generate( | ||||
|       length, | ||||
|       (_) => chars.codeUnitAt(random.nextInt(chars.length)), | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| 
 | ||||
| class CheckboxField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
| 
 | ||||
|   CheckboxField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.isRequired = false, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     final bool checked = controller.text.toLowerCase() == 'true'; | ||||
|     return Row( | ||||
|       children: [ | ||||
|         Checkbox( | ||||
|           value: checked, | ||||
|           onChanged: (value) { | ||||
|             controller.text = (value ?? false).toString(); | ||||
|             if (onChanged != null) onChanged(); | ||||
|           }, | ||||
|         ), | ||||
|         const SizedBox(width: 8), | ||||
|         Expanded( | ||||
|           child: Text( | ||||
|             label, | ||||
|             style: TextStyle( | ||||
|               color: colorScheme.onSurface, | ||||
|               fontWeight: FontWeight.w500, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,187 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'base_field.dart'; | ||||
| 
 | ||||
| class CurrencyField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final String currencySymbol; | ||||
|   final int decimalDigits; | ||||
|   final List<Map<String, String>> | ||||
|       currencyOptions; // [{code: 'INR', symbol: 'â¹', name: 'India (INR)'}] | ||||
| 
 | ||||
|   CurrencyField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.isRequired = false, | ||||
|     this.currencySymbol = 'â¹', | ||||
|     this.decimalDigits = 2, | ||||
|     List<Map<String, String>>? currencyOptions, | ||||
|   }) : currencyOptions = currencyOptions ?? | ||||
|             const [ | ||||
|               {'code': 'INR', 'symbol': 'â¹', 'name': 'India (INR)'}, | ||||
|               {'code': 'USD', 'symbol': '\$', 'name': 'USA (USD)'}, | ||||
|               {'code': 'EUR', 'symbol': '€', 'name': 'Euro (EUR)'}, | ||||
|               {'code': 'GBP', 'symbol': '£', 'name': 'UK (GBP)'}, | ||||
|               {'code': 'AUD', 'symbol': 'A\$', 'name': 'Australia (AUD)'}, | ||||
|               {'code': 'CAD', 'symbol': 'C\$', 'name': 'Canada (CAD)'}, | ||||
|               {'code': 'CHF', 'symbol': 'CHF', 'name': 'Switzerland (CHF)'}, | ||||
|               {'code': 'CNY', 'symbol': 'Â¥', 'name': 'China (CNY)'}, | ||||
|               {'code': 'HKD', 'symbol': 'HK\$', 'name': 'Hong Kong (HKD)'}, | ||||
|               {'code': 'NZD', 'symbol': 'NZ\$', 'name': 'New Zealand (NZD)'}, | ||||
|               {'code': 'SGD', 'symbol': 'S\$', 'name': 'Singapore (SGD)'}, | ||||
|               {'code': 'ZAR', 'symbol': 'R', 'name': 'South Africa (ZAR)'}, | ||||
|               {'code': 'SEK', 'symbol': 'kr', 'name': 'Sweden (SEK)'}, | ||||
|               {'code': 'NOK', 'symbol': 'kr', 'name': 'Norway (NOK)'}, | ||||
|               {'code': 'MXN', 'symbol': '\$', 'name': 'Mexico (MXN)'}, | ||||
|               {'code': 'BRL', 'symbol': 'R\$', 'name': 'Brazil (BRL)'}, | ||||
|               {'code': 'RUB', 'symbol': '₽', 'name': 'Russia (RUB)'}, | ||||
|               {'code': 'KRW', 'symbol': '₩', 'name': 'South Korea (KRW)'}, | ||||
|               {'code': 'TRY', 'symbol': '₺', 'name': 'Turkey (TRY)'}, | ||||
|               {'code': 'JPY', 'symbol': '¥', 'name': 'Japan (JPY)'}, | ||||
|             ]; | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         final v = value?.trim() ?? ''; | ||||
|         if (isRequired && v.isEmpty) return 'Required'; | ||||
|         if (v.isNotEmpty && double.tryParse(v.replaceAll(',', '')) == null) { | ||||
|           return 'Invalid amount'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return _CurrencyFieldWidget( | ||||
|       controller: controller, | ||||
|       colorScheme: colorScheme, | ||||
|       label: label, | ||||
|       hint: hint.isEmpty ? '0.${'0' * decimalDigits}' : hint, | ||||
|       isRequired: isRequired, | ||||
|       decimalDigits: decimalDigits, | ||||
|       defaultSymbol: currencySymbol, | ||||
|       options: currencyOptions, | ||||
|       onChanged: onChanged, | ||||
|       validator: validator, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _CurrencyFieldWidget extends StatefulWidget { | ||||
|   final TextEditingController controller; | ||||
|   final ColorScheme colorScheme; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final int decimalDigits; | ||||
|   final String defaultSymbol; | ||||
|   final List<Map<String, String>> options; | ||||
|   final VoidCallback? onChanged; | ||||
|   final String? Function(String?)? validator; | ||||
| 
 | ||||
|   const _CurrencyFieldWidget({ | ||||
|     required this.controller, | ||||
|     required this.colorScheme, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     required this.isRequired, | ||||
|     required this.decimalDigits, | ||||
|     required this.defaultSymbol, | ||||
|     required this.options, | ||||
|     this.onChanged, | ||||
|     this.validator, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<_CurrencyFieldWidget> createState() => _CurrencyFieldWidgetState(); | ||||
| } | ||||
| 
 | ||||
| class _CurrencyFieldWidgetState extends State<_CurrencyFieldWidget> { | ||||
|   late String _selectedSymbol; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _selectedSymbol = widget.defaultSymbol; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final cs = widget.colorScheme; | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Text(widget.label, | ||||
|             style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600)), | ||||
|         const SizedBox(height: 8), | ||||
|         Row( | ||||
|           children: [ | ||||
|             Container( | ||||
|               padding: const EdgeInsets.symmetric(horizontal: 8), | ||||
|               decoration: BoxDecoration( | ||||
|                 color: cs.surface, | ||||
|                 borderRadius: BorderRadius.circular(10), | ||||
|                 border: Border.all(color: cs.outlineVariant.withOpacity(0.4)), | ||||
|               ), | ||||
|               child: DropdownButtonHideUnderline( | ||||
|                 child: DropdownButton<String>( | ||||
|                   value: _selectedSymbol, | ||||
|                   items: widget.options | ||||
|                       .map((o) => DropdownMenuItem<String>( | ||||
|                             value: o['symbol'], | ||||
|                             child: Row( | ||||
|                               children: [ | ||||
|                                 Text(o['symbol'] ?? ''), | ||||
|                                 const SizedBox(width: 8), | ||||
|                                 Text( | ||||
|                                   o['code'] ?? '', | ||||
|                                   style: TextStyle( | ||||
|                                       color: cs.onSurface.withOpacity(0.7), | ||||
|                                       fontSize: 12), | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ), | ||||
|                           )) | ||||
|                       .toList(), | ||||
|                   onChanged: (val) { | ||||
|                     if (val == null) return; | ||||
|                     setState(() => _selectedSymbol = val); | ||||
|                     widget.onChanged?.call(); | ||||
|                   }, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             const SizedBox(width: 8), | ||||
|             Expanded( | ||||
|               child: TextFormField( | ||||
|                 controller: widget.controller, | ||||
|                 keyboardType: | ||||
|                     const TextInputType.numberWithOptions(decimal: true), | ||||
|                 inputFormatters: [ | ||||
|                   FilteringTextInputFormatter.allow(RegExp(r'[0-9.,]')), | ||||
|                 ], | ||||
|                 decoration: InputDecoration( | ||||
|                   hintText: widget.hint, | ||||
|                   prefixText: '$_selectedSymbol ', | ||||
|                   border: OutlineInputBorder( | ||||
|                       borderRadius: BorderRadius.circular(12)), | ||||
|                   filled: true, | ||||
|                 ), | ||||
|                 validator: widget.validator, | ||||
|                 onChanged: (_) => widget.onChanged?.call(), | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,60 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| 
 | ||||
| /// Custom text input field implementation (preferred over legacy TextField) | ||||
| class CustomTextField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final int? maxLength; | ||||
|   final TextInputType? keyboardType; | ||||
|   final Map<String, dynamic>? customProperties; | ||||
|   final String? Function(String?)? customValidator; | ||||
| 
 | ||||
|   CustomTextField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     this.isRequired = false, | ||||
|     this.maxLength, | ||||
|     this.keyboardType, | ||||
|     this.customProperties, | ||||
|     this.customValidator, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         // Custom validator (e.g., range checks) | ||||
|         if (customValidator != null) { | ||||
|           final err = customValidator!(value); | ||||
|           if (err != null) return err; | ||||
|         } | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         if (maxLength != null && value != null && value.length > maxLength!) { | ||||
|           return '$label must be less than $maxLength characters'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     final bool isPassword = customProperties?['isPassword'] == true; | ||||
|     return ModernTextField( | ||||
|       controller: controller, | ||||
|       hint: hint, | ||||
|       maxLength: maxLength, | ||||
|       keyboardType: keyboardType, | ||||
|       obscureText: isPassword, | ||||
|       onChanged: onChanged != null ? (_) => onChanged() : null, | ||||
|       validator: validator, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,132 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| 
 | ||||
| /// Read-only Data Grid field | ||||
| /// - Fetches tabular data from an async loader | ||||
| /// - Renders a DataTable | ||||
| /// - Excluded from form submission | ||||
| class DataGridField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final Future<List<Map<String, dynamic>>> Function() dataLoader; | ||||
| 
 | ||||
|   DataGridField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.isRequired = false, | ||||
|     required this.dataLoader, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => null; | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic>? get customProperties => const { | ||||
|         'excludeFromSubmit': true, | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return _DataGridFieldWidget( | ||||
|       colorScheme: colorScheme, | ||||
|       dataLoader: dataLoader, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _DataGridFieldWidget extends StatefulWidget { | ||||
|   final ColorScheme colorScheme; | ||||
|   final Future<List<Map<String, dynamic>>> Function() dataLoader; | ||||
| 
 | ||||
|   const _DataGridFieldWidget({ | ||||
|     required this.colorScheme, | ||||
|     required this.dataLoader, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<_DataGridFieldWidget> createState() => _DataGridFieldWidgetState(); | ||||
| } | ||||
| 
 | ||||
| class _DataGridFieldWidgetState extends State<_DataGridFieldWidget> { | ||||
|   late Future<List<Map<String, dynamic>>> _future; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _future = widget.dataLoader(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final colorScheme = widget.colorScheme; | ||||
|     return FutureBuilder<List<Map<String, dynamic>>>( | ||||
|       future: _future, | ||||
|       builder: (context, snapshot) { | ||||
|         if (snapshot.connectionState == ConnectionState.waiting) { | ||||
|           return const LinearProgressIndicator(minHeight: 2); | ||||
|         } | ||||
|         if (snapshot.hasError) { | ||||
|           return Container( | ||||
|             padding: const EdgeInsets.all(12), | ||||
|             decoration: BoxDecoration( | ||||
|               color: colorScheme.errorContainer.withOpacity(0.2), | ||||
|               borderRadius: BorderRadius.circular(12), | ||||
|               border: Border.all(color: colorScheme.error.withOpacity(0.3)), | ||||
|             ), | ||||
|             child: Row( | ||||
|               children: [ | ||||
|                 Icon(Icons.error_outline, color: colorScheme.error), | ||||
|                 const SizedBox(width: 8), | ||||
|                 Expanded( | ||||
|                   child: Text( | ||||
|                     snapshot.error.toString(), | ||||
|                     style: TextStyle(color: colorScheme.error), | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ); | ||||
|         } | ||||
|         final data = snapshot.data ?? const <Map<String, dynamic>>[]; | ||||
|         if (data.isEmpty) { | ||||
|           return Text('No data available', | ||||
|               style: TextStyle(color: colorScheme.onSurfaceVariant)); | ||||
|         } | ||||
|         final columns = data.first.keys.toList(); | ||||
|         return SingleChildScrollView( | ||||
|           scrollDirection: Axis.horizontal, | ||||
|           child: DataTable( | ||||
|             columnSpacing: 24, | ||||
|             headingRowColor: MaterialStateColor.resolveWith( | ||||
|                 (_) => colorScheme.primaryContainer.withOpacity(0.3)), | ||||
|             dataRowColor: | ||||
|                 MaterialStateColor.resolveWith((_) => colorScheme.surface), | ||||
|             dividerThickness: 0.5, | ||||
|             columns: columns | ||||
|                 .map((k) => DataColumn( | ||||
|                       label: Text(k, | ||||
|                           style: const TextStyle(fontWeight: FontWeight.w600)), | ||||
|                     )) | ||||
|                 .toList(), | ||||
|             rows: data | ||||
|                 .map( | ||||
|                   (row) => DataRow( | ||||
|                     cells: columns | ||||
|                         .map((k) => DataCell(Text(row[k]?.toString() ?? ''))) | ||||
|                         .toList(), | ||||
|                   ), | ||||
|                 ) | ||||
|                 .toList(), | ||||
|           ), | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,76 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| import 'base_field.dart'; | ||||
| 
 | ||||
| /// Date input field implementation with native date picker | ||||
| class DateField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final DateTime? initialDate; | ||||
|   final DateTime firstDate; | ||||
|   final DateTime lastDate; | ||||
| 
 | ||||
|   DateField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     this.isRequired = false, | ||||
|     this.initialDate, | ||||
|     DateTime? firstDate, | ||||
|     DateTime? lastDate, | ||||
|   })  : firstDate = firstDate ?? DateTime(2000), | ||||
|         lastDate = lastDate ?? DateTime(2101); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return Builder( | ||||
|       builder: (context) { | ||||
|         return ModernTextField( | ||||
|           controller: controller, | ||||
|           hint: hint.isNotEmpty ? hint : label, | ||||
|           readOnly: true, | ||||
|           validator: validator, | ||||
|           suffixIcon: const Icon(Icons.calendar_today_rounded), | ||||
|           onTap: () async { | ||||
|             FocusScope.of(context).unfocus(); | ||||
|             final now = DateTime.now(); | ||||
|             final picked = await showDatePicker( | ||||
|               context: context, | ||||
|               initialDate: initialDate ?? now, | ||||
|               firstDate: firstDate, | ||||
|               lastDate: lastDate, | ||||
|               helpText: label, | ||||
|             ); | ||||
|             if (picked != null) { | ||||
|               final String value = _formatDate(picked); | ||||
|               controller.text = value; | ||||
|               if (onChanged != null) onChanged(); | ||||
|             } | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String _formatDate(DateTime date) { | ||||
|     String two(int n) => n < 10 ? '0$n' : '$n'; | ||||
|     return '${date.year}-${two(date.month)}-${two(date.day)}'; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // No global navigator key required; Builder provides local context | ||||
| @ -0,0 +1,86 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| 
 | ||||
| /// DateTime input field implementation with native date & time pickers | ||||
| class DateTimeField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final DateTime? initialDateTime; | ||||
|   final DateTime firstDate; | ||||
|   final DateTime lastDate; | ||||
| 
 | ||||
|   DateTimeField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     this.isRequired = false, | ||||
|     this.initialDateTime, | ||||
|     DateTime? firstDate, | ||||
|     DateTime? lastDate, | ||||
|   })  : firstDate = firstDate ?? DateTime(2000), | ||||
|         lastDate = lastDate ?? DateTime(2101); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return Builder( | ||||
|       builder: (context) { | ||||
|         return ModernTextField( | ||||
|           controller: controller, | ||||
|           hint: hint.isNotEmpty ? hint : label, | ||||
|           readOnly: true, | ||||
|           validator: validator, | ||||
|           suffixIcon: const Icon(Icons.access_time_rounded), | ||||
|           onTap: () async { | ||||
|             FocusScope.of(context).unfocus(); | ||||
|             final now = DateTime.now(); | ||||
|             final datePicked = await showDatePicker( | ||||
|               context: context, | ||||
|               initialDate: (initialDateTime ?? now), | ||||
|               firstDate: firstDate, | ||||
|               lastDate: lastDate, | ||||
|               helpText: label, | ||||
|             ); | ||||
|             if (datePicked == null) return; | ||||
|             final timePicked = await showTimePicker( | ||||
|               context: context, | ||||
|               initialTime: TimeOfDay.fromDateTime(initialDateTime ?? now), | ||||
|               helpText: label, | ||||
|             ); | ||||
|             if (timePicked == null) return; | ||||
|             final combined = DateTime( | ||||
|               datePicked.year, | ||||
|               datePicked.month, | ||||
|               datePicked.day, | ||||
|               timePicked.hour, | ||||
|               timePicked.minute, | ||||
|             ); | ||||
|             controller.text = _formatDateTime(combined); | ||||
|             if (onChanged != null) onChanged(); | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String _formatDateTime(DateTime dt) { | ||||
|     String two(int n) => n < 10 ? '0$n' : '$n'; | ||||
|     return '${dt.year}-${two(dt.month)}-${two(dt.day)} ${two(dt.hour)}:${two(dt.minute)}'; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,159 @@ | ||||
| // import 'package:flutter/material.dart'; | ||||
| // import 'base_field.dart'; | ||||
| // import 'dependent_dropdown_field.dart'; | ||||
| // import 'dropdown_field.dart'; | ||||
| 
 | ||||
| // /// Example usage of DependentDropdownField | ||||
| // /// This demonstrates how to create cascading dropdowns with API calls | ||||
| // class DependentDropdownExample { | ||||
| //   /// Example field definitions for a location form with country -> state -> district | ||||
| //   static List<BaseField> getLocationFields() { | ||||
| //     return [ | ||||
| //       // Parent dropdown - Country selection | ||||
| //       DropdownField( | ||||
| //         fieldKey: 'country', | ||||
| //         label: 'Country', | ||||
| //         hint: 'Select country', | ||||
| //         options: const [ | ||||
| //           {'id': 'india', 'name': 'India'}, | ||||
| //           {'id': 'usa', 'name': 'United States'}, | ||||
| //           {'id': 'canada', 'name': 'Canada'}, | ||||
| //         ], | ||||
| //         valueKey: 'id', | ||||
| //         displayKey: 'name', | ||||
| //       ), | ||||
| 
 | ||||
| //       // Dependent dropdown - State selection (depends on country) | ||||
| //       DependentDropdownField( | ||||
| //         fieldKey: 'state', | ||||
| //         label: 'State', | ||||
| //         hint: 'Select state', | ||||
| //         dependentFieldKey: 'country', | ||||
| //         apiEndpoint: '/State_ListFilter1/State_ListFilter11', | ||||
| //         valueKey: 'state_name', | ||||
| //         displayKey: 'state_name', | ||||
| //         dependentValueKey: | ||||
| //             'name', // This is the field from country that gets passed to API | ||||
| //       ), | ||||
| 
 | ||||
| //       // Dependent dropdown - District selection (depends on state) | ||||
| //       DependentDropdownField( | ||||
| //         fieldKey: 'district', | ||||
| //         label: 'District', | ||||
| //         hint: 'Select district', | ||||
| //         dependentFieldKey: 'state', | ||||
| //         apiEndpoint: '/District_ListFilter1/District_ListFilter11', | ||||
| //         valueKey: 'district_name', | ||||
| //         displayKey: 'district_name', | ||||
| //         dependentValueKey: | ||||
| //             'state_name', // This is the field from state that gets passed to API | ||||
| //       ), | ||||
| //     ]; | ||||
| //   } | ||||
| 
 | ||||
| //   /// Example field definitions for a product form with category -> subcategory -> product | ||||
| //   static List<BaseField> getProductFields() { | ||||
| //     return [ | ||||
| //       // Parent dropdown - Category selection | ||||
| //       DropdownField( | ||||
| //         fieldKey: 'category', | ||||
| //         label: 'Category', | ||||
| //         hint: 'Select category', | ||||
| //         options: const [ | ||||
| //           {'id': 'electronics', 'name': 'Electronics'}, | ||||
| //           {'id': 'clothing', 'name': 'Clothing'}, | ||||
| //           {'id': 'books', 'name': 'Books'}, | ||||
| //         ], | ||||
| //         valueKey: 'id', | ||||
| //         displayKey: 'name', | ||||
| //       ), | ||||
| 
 | ||||
| //       // Dependent dropdown - Subcategory selection (depends on category) | ||||
| //       DependentDropdownField( | ||||
| //         fieldKey: 'subcategory', | ||||
| //         label: 'Subcategory', | ||||
| //         hint: 'Select subcategory', | ||||
| //         dependentFieldKey: 'category', | ||||
| //         apiEndpoint: '/Subcategory_ListFilter1/Subcategory_ListFilter11', | ||||
| //         valueKey: 'subcategory_id', | ||||
| //         displayKey: 'subcategory_name', | ||||
| //         dependentValueKey: 'name', | ||||
| //       ), | ||||
| 
 | ||||
| //       // Dependent dropdown - Product selection (depends on subcategory) | ||||
| //       DependentDropdownField( | ||||
| //         fieldKey: 'product', | ||||
| //         label: 'Product', | ||||
| //         hint: 'Select product', | ||||
| //         dependentFieldKey: 'subcategory', | ||||
| //         apiEndpoint: '/Product_ListFilter1/Product_ListFilter11', | ||||
| //         valueKey: 'product_id', | ||||
| //         displayKey: 'product_name', | ||||
| //         dependentValueKey: 'subcategory_name', | ||||
| //       ), | ||||
| //     ]; | ||||
| //   } | ||||
| // } | ||||
| 
 | ||||
| // /// How the DependentDropdownField works: | ||||
| // ///  | ||||
| // /// 1. **Parent Field Selection**: When user selects a value in the parent field (e.g., country) | ||||
| // /// 2. **API Call Triggered**: The dependent field automatically detects the change | ||||
| // /// 3. **API Call Made**: Calls the specified API endpoint with the parent field value | ||||
| // /// 4. **Options Loaded**: The API response is parsed and options are populated | ||||
| // /// 5. **UI Updated**: The dropdown shows the new options | ||||
| // /// 6. **Cascading Effect**: If there are more dependent fields, they get cleared and wait for new selection | ||||
| // ///  | ||||
| // /// **API Endpoint Format**: | ||||
| // /// - Base URL: `ApiConstants.baseUrl` | ||||
| // /// - Endpoint: `/State_ListFilter1/State_ListFilter11/{dependentValue}` | ||||
| // /// - Example: `https://api.example.com/State_ListFilter1/State_ListFilter11/india` | ||||
| // ///  | ||||
| // /// **API Response Format**: | ||||
| // /// ```json | ||||
| // /// [ | ||||
| // ///   { | ||||
| // ///     "state_name": "Maharashtra", | ||||
| // ///     "state_id": "1" | ||||
| // ///   }, | ||||
| // ///   { | ||||
| // ///     "state_name": "Karnataka",  | ||||
| // ///     "state_id": "2" | ||||
| // ///   } | ||||
| // /// ] | ||||
| // /// ``` | ||||
| // ///  | ||||
| // /// **Key Features**: | ||||
| // /// - â
 **Automatic API Calls**: No manual API handling needed | ||||
| // /// - â
 **Loading States**: Shows loading indicator while fetching data | ||||
| // /// - â
 **Error Handling**: Displays error messages if API fails | ||||
| // /// - â
 **Cascading Clear**: Dependent fields clear when parent changes | ||||
| // /// - â
 **Form Integration**: Works seamlessly with EntityForm | ||||
| // /// - â
 **Theme Support**: Uses DynamicThemeProvider for consistent styling | ||||
| // /// - â
 **Validation**: Built-in required field validation | ||||
| // /// - â
 **Responsive**: Works on all screen sizes | ||||
| // ///  | ||||
| // /// **Usage in Entity**: | ||||
| // /// ```dart | ||||
| // /// // In your entity fields file | ||||
| // /// class MyEntityFields { | ||||
| // ///   static List<BaseField> getFields() { | ||||
| // ///     return [ | ||||
| // ///       DropdownField( | ||||
| // ///         fieldKey: 'country', | ||||
| // ///         label: 'Country', | ||||
| // ///         options: countryOptions, | ||||
| // ///       ), | ||||
| // ///       DependentDropdownField( | ||||
| // ///         fieldKey: 'state', | ||||
| // ///         label: 'State', | ||||
| // ///         dependentFieldKey: 'country', | ||||
| // ///         apiEndpoint: '/State_ListFilter1/State_ListFilter11', | ||||
| // ///         valueKey: 'state_name', | ||||
| // ///         displayKey: 'state_name', | ||||
| // ///         dependentValueKey: 'name', | ||||
| // ///       ), | ||||
| // ///     ]; | ||||
| // ///   } | ||||
| // /// } | ||||
| // /// ``` | ||||
| @ -0,0 +1,252 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'base_field.dart'; | ||||
| import 'dynamic_dropdown_field.dart'; | ||||
| import '../ui/entity_form.dart'; | ||||
| 
 | ||||
| /// Dependent dropdown field that loads options based on another field's value | ||||
| /// This field automatically makes API calls when the dependent field changes | ||||
| class DependentDropdownField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final String dependentFieldKey; // The field this dropdown depends on | ||||
|   final Future<List<Map<String, dynamic>>> Function(String parentValue) | ||||
|       optionsLoader; // Loader using parent value | ||||
|   final String valueKey; // Field name for value in API response | ||||
|   final String displayKey; // Field name for display text in API response | ||||
|   final Map<String, dynamic>? customProperties; | ||||
| 
 | ||||
|   DependentDropdownField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     this.isRequired = false, | ||||
|     required this.dependentFieldKey, | ||||
|     required this.optionsLoader, | ||||
|     this.valueKey = 'id', | ||||
|     this.displayKey = 'name', | ||||
|     this.customProperties, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return _DependentDropdownWidget( | ||||
|       fieldKey: fieldKey, | ||||
|       label: label, | ||||
|       hint: hint, | ||||
|       isRequired: isRequired, | ||||
|       dependentFieldKey: dependentFieldKey, | ||||
|       optionsLoader: optionsLoader, | ||||
|       valueKey: valueKey, | ||||
|       displayKey: displayKey, | ||||
|       controller: controller, | ||||
|       colorScheme: colorScheme, | ||||
|       onChanged: onChanged, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _DependentDropdownWidget extends StatefulWidget { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final String dependentFieldKey; | ||||
|   final Future<List<Map<String, dynamic>>> Function(String parentValue) | ||||
|       optionsLoader; | ||||
|   final String valueKey; | ||||
|   final String displayKey; | ||||
|   final TextEditingController controller; | ||||
|   final ColorScheme colorScheme; | ||||
|   final VoidCallback? onChanged; | ||||
| 
 | ||||
|   const _DependentDropdownWidget({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     required this.isRequired, | ||||
|     required this.dependentFieldKey, | ||||
|     required this.optionsLoader, | ||||
|     required this.valueKey, | ||||
|     required this.displayKey, | ||||
|     required this.controller, | ||||
|     required this.colorScheme, | ||||
|     this.onChanged, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<_DependentDropdownWidget> createState() => | ||||
|       _DependentDropdownWidgetState(); | ||||
| } | ||||
| 
 | ||||
| class _DependentDropdownWidgetState extends State<_DependentDropdownWidget> { | ||||
|   String? _lastDependentValue; | ||||
|   Key _reloadKey = const ValueKey('init'); | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     // Defer inherited widget access until after first frame | ||||
|     WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|       if (mounted) { | ||||
|         _checkDependentFieldChange(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   void _loadInitialOptions() async {} | ||||
| 
 | ||||
|   @override | ||||
|   void didChangeDependencies() { | ||||
|     super.didChangeDependencies(); | ||||
|     _checkDependentFieldChange(); | ||||
|   } | ||||
| 
 | ||||
|   void _checkDependentFieldChange() { | ||||
|     final formScope = EntityFormScope.of(context); | ||||
|     if (formScope != null) { | ||||
|       final dependentController = | ||||
|           formScope.controllers[widget.dependentFieldKey]; | ||||
|       if (dependentController != null) { | ||||
|         final currentValue = dependentController.text; | ||||
|         if (currentValue != _lastDependentValue) { | ||||
|           _lastDependentValue = currentValue; | ||||
|           // Defer mutations to next frame to avoid setState during build | ||||
|           WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|             if (!mounted) return; | ||||
|             // Clear current selection when parent changes | ||||
|             widget.controller.clear(); | ||||
|             // Force re-init of inner DynamicDropdown by changing key | ||||
|             setState(() { | ||||
|               _reloadKey = ValueKey( | ||||
|                   'dep-${widget.fieldKey}-${_lastDependentValue ?? 'empty'}-${DateTime.now().millisecondsSinceEpoch}'); | ||||
|             }); | ||||
|             // Notify parent about change | ||||
|             widget.onChanged?.call(); | ||||
|           }); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     Future<List<Map<String, dynamic>>> loadOptions() async { | ||||
|       // If no dependent value selected, return empty | ||||
|       final formScope = EntityFormScope.of(context); | ||||
|       final depVal = | ||||
|           formScope?.controllers[widget.dependentFieldKey]?.text ?? ''; | ||||
|       if (depVal.isEmpty) return const []; | ||||
| 
 | ||||
|       try { | ||||
|         final response = await widget.optionsLoader(depVal); | ||||
|         return response | ||||
|             .map((item) => { | ||||
|                   widget.valueKey: item[widget.valueKey]?.toString() ?? '', | ||||
|                   widget.displayKey: item[widget.displayKey]?.toString() ?? '', | ||||
|                 }) | ||||
|             .toList(); | ||||
|       } catch (e) { | ||||
|         // Return empty list on error to prevent showing old data | ||||
|         return const []; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     final innerField = DynamicDropdownField( | ||||
|       fieldKey: widget.fieldKey, | ||||
|       label: widget.label, | ||||
|       hint: widget.hint, | ||||
|       isRequired: widget.isRequired, | ||||
|       optionsLoader: loadOptions, | ||||
|       valueKey: widget.valueKey, | ||||
|       displayKey: widget.displayKey, | ||||
|     ); | ||||
| 
 | ||||
|     final child = innerField.buildField( | ||||
|       controller: widget.controller, | ||||
|       colorScheme: widget.colorScheme, | ||||
|       onChanged: widget.onChanged, | ||||
|     ); | ||||
| 
 | ||||
|     // Read current parent value for UI guard/notice | ||||
|     final formScope = EntityFormScope.of(context); | ||||
|     final depVal = formScope?.controllers[widget.dependentFieldKey]?.text ?? ''; | ||||
| 
 | ||||
|     final keyedChild = KeyedSubtree(key: _reloadKey, child: child); | ||||
| 
 | ||||
|     final content = Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         // If parent is empty, block interactions and show subtle disabled state | ||||
|         if (depVal.isEmpty) | ||||
|           Opacity( | ||||
|             opacity: 0.6, | ||||
|             child: AbsorbPointer(child: keyedChild), | ||||
|           ) | ||||
|         else | ||||
|           keyedChild, | ||||
|         if (depVal.isEmpty) | ||||
|           Padding( | ||||
|             padding: const EdgeInsets.only(top: 6.0), | ||||
|             child: Text( | ||||
|               'Please select ${widget.dependentFieldKey} first', | ||||
|               style: TextStyle( | ||||
|                 color: widget.colorScheme.onSurface.withOpacity(0.7), | ||||
|                 fontSize: 12, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|       ], | ||||
|     ); | ||||
| 
 | ||||
|     return content; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Helper class to manage dependent dropdown relationships | ||||
| /// This should be used in EntityForm to handle field dependencies | ||||
| class DependentDropdownManager { | ||||
|   static final Map<String, List<DependentDropdownField>> _dependencies = {}; | ||||
| 
 | ||||
|   /// Register a dependent dropdown field | ||||
|   static void registerDependency( | ||||
|       String dependentFieldKey, DependentDropdownField field) { | ||||
|     if (!_dependencies.containsKey(dependentFieldKey)) { | ||||
|       _dependencies[dependentFieldKey] = []; | ||||
|     } | ||||
|     _dependencies[dependentFieldKey]!.add(field); | ||||
|   } | ||||
| 
 | ||||
|   /// Get all dependent fields for a given field | ||||
|   static List<DependentDropdownField> getDependentFields(String fieldKey) { | ||||
|     return _dependencies[fieldKey] ?? []; | ||||
|   } | ||||
| 
 | ||||
|   /// Clear all dependencies (useful for form reset) | ||||
|   static void clearDependencies() { | ||||
|     _dependencies.clear(); | ||||
|   } | ||||
| 
 | ||||
|   /// Notify dependent fields when a field value changes | ||||
|   static void notifyDependentFields(String fieldKey, String value) { | ||||
|     final dependentFields = getDependentFields(fieldKey); | ||||
|     for (final field in dependentFields) { | ||||
|       // This would need to be implemented with proper context | ||||
|       // The actual implementation would be in EntityForm | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../../../Reuseable/reusable_dropdown_field.dart'; | ||||
| 
 | ||||
| /// Dropdown selection field implementation | ||||
| class DropdownField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final List<Map<String, dynamic>> options; | ||||
|   final String valueKey; // id field in option map | ||||
|   final String displayKey; // display field in option map | ||||
| 
 | ||||
|   DropdownField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     this.isRequired = false, | ||||
|     required this.options, | ||||
|     this.valueKey = 'id', | ||||
|     this.displayKey = 'name', | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return ReusableDropdownField( | ||||
|       label: label, | ||||
|       options: options, | ||||
|       valueField: valueKey, // id | ||||
|       uiField: displayKey, // label | ||||
|       value: controller.text.isNotEmpty ? controller.text : null, | ||||
|       onChanged: (val) { | ||||
|         controller.text = val ?? ''; | ||||
|         if (onChanged != null) onChanged(); | ||||
|       }, | ||||
|       onSaved: (val) { | ||||
|         controller.text = val ?? ''; | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,223 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| 
 | ||||
| /// Dynamic single-select dropdown (no Autocomplete) | ||||
| /// - Opens a modal list with search | ||||
| /// - Stores selected id in controller, displays label | ||||
| class DynamicDropdownField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final Future<List<Map<String, dynamic>>> Function() optionsLoader; | ||||
|   final String valueKey; | ||||
|   final String displayKey; | ||||
| 
 | ||||
|   DynamicDropdownField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.isRequired = false, | ||||
|     required this.optionsLoader, | ||||
|     required this.valueKey, | ||||
|     required this.displayKey, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return _DynamicDropdownWidget( | ||||
|       label: label, | ||||
|       hint: hint, | ||||
|       controller: controller, | ||||
|       colorScheme: colorScheme, | ||||
|       optionsLoader: optionsLoader, | ||||
|       valueKey: valueKey, | ||||
|       displayKey: displayKey, | ||||
|       validate: validator, | ||||
|       onChanged: onChanged, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _DynamicDropdownWidget extends StatefulWidget { | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final TextEditingController controller; | ||||
|   final ColorScheme colorScheme; | ||||
|   final Future<List<Map<String, dynamic>>> Function() optionsLoader; | ||||
|   final String valueKey; | ||||
|   final String displayKey; | ||||
|   final String? Function(String?)? validate; | ||||
|   final VoidCallback? onChanged; | ||||
| 
 | ||||
|   const _DynamicDropdownWidget({ | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     required this.controller, | ||||
|     required this.colorScheme, | ||||
|     required this.optionsLoader, | ||||
|     required this.valueKey, | ||||
|     required this.displayKey, | ||||
|     required this.validate, | ||||
|     required this.onChanged, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<_DynamicDropdownWidget> createState() => _DynamicDropdownWidgetState(); | ||||
| } | ||||
| 
 | ||||
| class _DynamicDropdownWidgetState extends State<_DynamicDropdownWidget> { | ||||
|   List<Map<String, dynamic>> _options = const []; | ||||
|   String _uiLabel = ''; | ||||
|   bool _loading = true; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _load(); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _load() async { | ||||
|     try { | ||||
|       final opts = await widget.optionsLoader(); | ||||
|       setState(() { | ||||
|         _options = opts; | ||||
|         _loading = false; | ||||
|       }); | ||||
|       if (widget.controller.text.isNotEmpty) { | ||||
|         final match = _options.firstWhere( | ||||
|           (o) => | ||||
|               (o[widget.valueKey]?.toString() ?? '') == widget.controller.text, | ||||
|           orElse: () => const {}, | ||||
|         ); | ||||
|         setState(() { | ||||
|           _uiLabel = match.isNotEmpty | ||||
|               ? (match[widget.displayKey]?.toString() ?? '') | ||||
|               : ''; | ||||
|         }); | ||||
|       } | ||||
|     } catch (_) { | ||||
|       setState(() => _loading = false); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _openPicker() async { | ||||
|     final ColorScheme cs = widget.colorScheme; | ||||
|     String query = ''; | ||||
|     await showModalBottomSheet( | ||||
|       context: context, | ||||
|       isScrollControlled: true, | ||||
|       backgroundColor: cs.surface, | ||||
|       shape: const RoundedRectangleBorder( | ||||
|         borderRadius: BorderRadius.vertical(top: Radius.circular(16)), | ||||
|       ), | ||||
|       builder: (context) { | ||||
|         List<Map<String, dynamic>> filtered = _options; | ||||
|         return StatefulBuilder( | ||||
|           builder: (context, setSheetState) { | ||||
|             void applyFilter(String q) { | ||||
|               query = q; | ||||
|               setSheetState(() { | ||||
|                 filtered = _options | ||||
|                     .where((o) => (o[widget.displayKey]?.toString() ?? '') | ||||
|                         .toLowerCase() | ||||
|                         .contains(q.toLowerCase())) | ||||
|                     .toList(); | ||||
|               }); | ||||
|             } | ||||
| 
 | ||||
|             return SafeArea( | ||||
|               child: Padding( | ||||
|                 padding: EdgeInsets.only( | ||||
|                   bottom: MediaQuery.of(context).viewInsets.bottom, | ||||
|                   left: 16, | ||||
|                   right: 16, | ||||
|                   top: 12, | ||||
|                 ), | ||||
|                 child: Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   children: [ | ||||
|                     TextField( | ||||
|                       decoration: InputDecoration( | ||||
|                         hintText: 'Search ${widget.label.toLowerCase()}', | ||||
|                         prefixIcon: const Icon(Icons.search), | ||||
|                         border: const OutlineInputBorder(), | ||||
|                       ), | ||||
|                       onChanged: applyFilter, | ||||
|                     ), | ||||
|                     const SizedBox(height: 12), | ||||
|                     Flexible( | ||||
|                       child: filtered.isEmpty | ||||
|                           ? Center( | ||||
|                               child: Text('No results', | ||||
|                                   style: TextStyle(color: cs.onSurfaceVariant)), | ||||
|                             ) | ||||
|                           : ListView.separated( | ||||
|                               shrinkWrap: true, | ||||
|                               itemCount: filtered.length, | ||||
|                               separatorBuilder: (_, __) => Divider( | ||||
|                                   height: 1, | ||||
|                                   color: cs.outline.withOpacity(0.08)), | ||||
|                               itemBuilder: (context, index) { | ||||
|                                 final o = filtered[index]; | ||||
|                                 final id = o[widget.valueKey]?.toString() ?? ''; | ||||
|                                 final name = | ||||
|                                     o[widget.displayKey]?.toString() ?? ''; | ||||
|                                 final isSelected = widget.controller.text == id; | ||||
|                                 return ListTile( | ||||
|                                   title: Text(name), | ||||
|                                   trailing: isSelected | ||||
|                                       ? Icon(Icons.check, color: cs.primary) | ||||
|                                       : null, | ||||
|                                   onTap: () { | ||||
|                                     setState(() { | ||||
|                                       widget.controller.text = id; | ||||
|                                       _uiLabel = name; | ||||
|                                     }); | ||||
|                                     widget.onChanged?.call(); | ||||
|                                     Navigator.of(context).pop(); | ||||
|                                   }, | ||||
|                                 ); | ||||
|                               }, | ||||
|                             ), | ||||
|                     ), | ||||
|                     const SizedBox(height: 8), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ); | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (_loading) { | ||||
|       return const LinearProgressIndicator(minHeight: 2); | ||||
|     } | ||||
|     return ModernTextField( | ||||
|       label: widget.label, | ||||
|       hint: widget.hint, | ||||
|       readOnly: true, | ||||
|       controller: TextEditingController(text: _uiLabel), | ||||
|       validator: (v) => widget.validate?.call(widget.controller.text), | ||||
|       onTap: _openPicker, | ||||
|       suffixIcon: const Icon(Icons.arrow_drop_down), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,253 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| 
 | ||||
| /// Dynamic multi-select dropdown (no Autocomplete) | ||||
| /// - Modal list with search and checkboxes | ||||
| /// - Stores comma-separated selected labels in controller | ||||
| class DynamicMultiSelectDropdownField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final Future<List<String>> Function() optionsLoader; | ||||
| 
 | ||||
|   DynamicMultiSelectDropdownField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.isRequired = false, | ||||
|     required this.optionsLoader, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|         if (isRequired && (value == null || value.isEmpty)) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return _DynamicMultiSelectWidget( | ||||
|       label: label, | ||||
|       hint: hint, | ||||
|       controller: controller, | ||||
|       colorScheme: colorScheme, | ||||
|       optionsLoader: optionsLoader, | ||||
|       validate: validator, | ||||
|       onChanged: onChanged, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _DynamicMultiSelectWidget extends StatefulWidget { | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final TextEditingController controller; | ||||
|   final ColorScheme colorScheme; | ||||
|   final Future<List<String>> Function() optionsLoader; | ||||
|   final String? Function(String?)? validate; | ||||
|   final VoidCallback? onChanged; | ||||
| 
 | ||||
|   const _DynamicMultiSelectWidget({ | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     required this.controller, | ||||
|     required this.colorScheme, | ||||
|     required this.optionsLoader, | ||||
|     required this.validate, | ||||
|     required this.onChanged, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<_DynamicMultiSelectWidget> createState() => | ||||
|       _DynamicMultiSelectWidgetState(); | ||||
| } | ||||
| 
 | ||||
| class _DynamicMultiSelectWidgetState extends State<_DynamicMultiSelectWidget> { | ||||
|   List<String> _options = const []; | ||||
|   late final Set<String> _selected; | ||||
|   bool _loading = true; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _selected = widget.controller.text.isEmpty | ||||
|         ? <String>{} | ||||
|         : widget.controller.text | ||||
|             .split(',') | ||||
|             .map((e) => e.trim()) | ||||
|             .where((e) => e.isNotEmpty) | ||||
|             .toSet(); | ||||
|     _load(); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _load() async { | ||||
|     try { | ||||
|       final opts = await widget.optionsLoader(); | ||||
|       setState(() { | ||||
|         _options = opts; | ||||
|         _loading = false; | ||||
|       }); | ||||
|     } catch (_) { | ||||
|       setState(() => _loading = false); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _openPicker() async { | ||||
|     final ColorScheme cs = widget.colorScheme; | ||||
|     String query = ''; | ||||
|     await showModalBottomSheet( | ||||
|       context: context, | ||||
|       isScrollControlled: true, | ||||
|       backgroundColor: cs.surface, | ||||
|       shape: const RoundedRectangleBorder( | ||||
|         borderRadius: BorderRadius.vertical(top: Radius.circular(16)), | ||||
|       ), | ||||
|       builder: (context) { | ||||
|         List<String> filtered = _options; | ||||
|         return StatefulBuilder( | ||||
|           builder: (context, setSheetState) { | ||||
|             void applyFilter(String q) { | ||||
|               query = q; | ||||
|               setSheetState(() { | ||||
|                 filtered = _options | ||||
|                     .where((o) => o.toLowerCase().contains(q.toLowerCase())) | ||||
|                     .toList(); | ||||
|               }); | ||||
|             } | ||||
| 
 | ||||
|             void toggle(String value) { | ||||
|               setSheetState(() { | ||||
|                 if (_selected.contains(value)) { | ||||
|                   _selected.remove(value); | ||||
|                 } else { | ||||
|                   _selected.add(value); | ||||
|                 } | ||||
|               }); | ||||
|             } | ||||
| 
 | ||||
|             return SafeArea( | ||||
|               child: Padding( | ||||
|                 padding: EdgeInsets.only( | ||||
|                   bottom: MediaQuery.of(context).viewInsets.bottom, | ||||
|                   left: 16, | ||||
|                   right: 16, | ||||
|                   top: 12, | ||||
|                 ), | ||||
|                 child: Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   children: [ | ||||
|                     TextField( | ||||
|                       decoration: InputDecoration( | ||||
|                         hintText: 'Search ${widget.label.toLowerCase()}', | ||||
|                         prefixIcon: const Icon(Icons.search), | ||||
|                         border: const OutlineInputBorder(), | ||||
|                       ), | ||||
|                       onChanged: applyFilter, | ||||
|                     ), | ||||
|                     const SizedBox(height: 12), | ||||
|                     Flexible( | ||||
|                       child: filtered.isEmpty | ||||
|                           ? Center( | ||||
|                               child: Text('No results', | ||||
|                                   style: TextStyle(color: cs.onSurfaceVariant)), | ||||
|                             ) | ||||
|                           : ListView.separated( | ||||
|                               shrinkWrap: true, | ||||
|                               itemCount: filtered.length, | ||||
|                               separatorBuilder: (_, __) => Divider( | ||||
|                                   height: 1, | ||||
|                                   color: cs.outline.withOpacity(0.08)), | ||||
|                               itemBuilder: (context, index) { | ||||
|                                 final name = filtered[index]; | ||||
|                                 final isSelected = _selected.contains(name); | ||||
|                                 return CheckboxListTile( | ||||
|                                   title: Text(name), | ||||
|                                   value: isSelected, | ||||
|                                   onChanged: (_) => toggle(name), | ||||
|                                 ); | ||||
|                               }, | ||||
|                             ), | ||||
|                     ), | ||||
|                     const SizedBox(height: 8), | ||||
|                     Row( | ||||
|                       children: [ | ||||
|                         TextButton( | ||||
|                           onPressed: () { | ||||
|                             setState(() { | ||||
|                               widget.controller.text = ''; | ||||
|                             }); | ||||
|                             _selected.clear(); | ||||
|                             widget.onChanged?.call(); | ||||
|                             Navigator.of(context).pop(); | ||||
|                           }, | ||||
|                           child: const Text('Clear'), | ||||
|                         ), | ||||
|                         const Spacer(), | ||||
|                         ElevatedButton( | ||||
|                           onPressed: () { | ||||
|                             setState(() { | ||||
|                               widget.controller.text = _selected.join(','); | ||||
|                             }); | ||||
|                             widget.onChanged?.call(); | ||||
|                             Navigator.of(context).pop(); | ||||
|                           }, | ||||
|                           child: const Text('Done'), | ||||
|                         ), | ||||
|                       ], | ||||
|                     ), | ||||
|                     const SizedBox(height: 8), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ); | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (_loading) { | ||||
|       return const LinearProgressIndicator(minHeight: 2); | ||||
|     } | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         ModernTextField( | ||||
|           label: widget.label, | ||||
|           hint: widget.hint, | ||||
|           readOnly: true, | ||||
|           controller: TextEditingController(text: _selected.join(', ')), | ||||
|           validator: widget.validate, | ||||
|           onTap: _openPicker, | ||||
|           suffixIcon: const Icon(Icons.arrow_drop_down), | ||||
|         ), | ||||
|         const SizedBox(height: 8), | ||||
|         Wrap( | ||||
|           spacing: 8, | ||||
|           children: _selected | ||||
|               .map((v) => Chip( | ||||
|                     label: Text(v), | ||||
|                     onDeleted: () { | ||||
|                       setState(() { | ||||
|                         _selected.remove(v); | ||||
|                         widget.controller.text = _selected.join(','); | ||||
|                       }); | ||||
|                       widget.onChanged?.call(); | ||||
|                     }, | ||||
|                   )) | ||||
|               .toList(), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,48 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../../../shared/widgets/inputs/modern_text_field.dart'; | ||||
| 
 | ||||
| /// Email input field implementation | ||||
| class EmailField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|    | ||||
|   EmailField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     required this.hint, | ||||
|     this.isRequired = false, | ||||
|   }); | ||||
|    | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (value) { | ||||
|     if (isRequired && (value == null || value.isEmpty)) { | ||||
|       return '$label is required'; | ||||
|     } | ||||
|     if (value != null && value.isNotEmpty) { | ||||
|       final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); | ||||
|       if (!emailRegex.hasMatch(value)) { | ||||
|         return 'Please enter a valid email address'; | ||||
|       } | ||||
|     } | ||||
|     return null; | ||||
|   }; | ||||
|    | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return ModernTextField( | ||||
|       controller: controller, | ||||
|       hint: hint, | ||||
|       keyboardType: TextInputType.emailAddress, | ||||
|       prefixIcon: const Icon(Icons.email_outlined), | ||||
|       onChanged: onChanged != null ? (_) => onChanged() : null, | ||||
|       validator: validator, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,57 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import '../ui/entity_screens.dart'; | ||||
| import '../utils/entity_field_store.dart'; | ||||
| 
 | ||||
| typedef UploadHandler = Future<dynamic> Function( | ||||
|     String entityId, String entityName, String fileName, dynamic bytes); | ||||
| typedef CreateAndReturnId = Future<int> Function(Map<String, dynamic> data); | ||||
| 
 | ||||
| /// Universal wrapper: Create entity first, then upload files collected by shared upload fields | ||||
| class EntityCreateWithUploads extends StatelessWidget { | ||||
|   final String title; | ||||
|   final List fields; // List<BaseField> | ||||
|   final CreateAndReturnId createAndReturnId; | ||||
|   final Map<String, UploadHandler> | ||||
|       uploadHandlersByFieldKey; // fieldKey -> uploader | ||||
|   final String entityName; // e.g., 'Adv1' | ||||
|   final bool isLoading; | ||||
|   final String? errorMessage; | ||||
| 
 | ||||
|   const EntityCreateWithUploads({ | ||||
|     super.key, | ||||
|     required this.title, | ||||
|     required this.fields, | ||||
|     required this.createAndReturnId, | ||||
|     required this.uploadHandlersByFieldKey, | ||||
|     required this.entityName, | ||||
|     this.isLoading = false, | ||||
|     this.errorMessage, | ||||
|   }); | ||||
| 
 | ||||
|   Future<void> _uploadAll(int id) async { | ||||
|     final store = EntityFieldStore.instance; | ||||
|     for (final entry in uploadHandlersByFieldKey.entries) { | ||||
|       final String key = entry.key; | ||||
|       final handler = entry.value; | ||||
|       final items = store.get<List<UploadItem>>(key) ?? const []; | ||||
|       for (final item in items) { | ||||
|         await handler(id.toString(), entityName, item.fileName, item.bytes); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return EntityCreateScreen( | ||||
|       title: title, | ||||
|       fields: fields.cast(), | ||||
|       isLoading: isLoading, | ||||
|       errorMessage: errorMessage, | ||||
|       onSubmit: (data) async { | ||||
|         final id = await createAndReturnId(data); | ||||
|         await _uploadAll(id); | ||||
|         EntityFieldStore.instance.clear(); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,319 @@ | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../ui/entity_form.dart'; | ||||
| 
 | ||||
| import 'custom_text_field.dart'; | ||||
| import 'number_field.dart' as shared_number; | ||||
| import 'date_field.dart' as shared_date; | ||||
| import 'currency_field.dart'; | ||||
| import 'qr_code_field.dart'; | ||||
| import 'barcode_field.dart'; | ||||
| 
 | ||||
| /// FieldGroupField | ||||
| /// - Visual group that renders multiple sub-inputs inside a styled container | ||||
| /// - Uses EntityForm's composite submit capability via assignByJsonPaths + paths | ||||
| /// - The group controller stores a JSON string of path->value; on submit, EntityForm | ||||
| ///   assigns each value into the request body by its json path. | ||||
| class FieldGroupField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
|   final List<GroupSubField>? subFields; | ||||
|   // Schema-driven: field name -> type (e.g., 'text', 'number', 'date', 'currency', 'qrcode', 'barcode') | ||||
|   final String? groupPrefix; // e.g., 'primary' | ||||
|   final Map<String, String>? schema; | ||||
| 
 | ||||
|   FieldGroupField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.isRequired = false, | ||||
|     this.subFields, | ||||
|     this.groupPrefix, | ||||
|     this.schema, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (v) { | ||||
|         if (!isRequired) return null; | ||||
|         final fields = _effectiveSubFields; | ||||
|         if (fields.any((sf) => sf.isRequired)) { | ||||
|           final scope = _scope; | ||||
|           if (scope == null) return null; | ||||
|           for (final sf in fields) { | ||||
|             if (!sf.isRequired) continue; | ||||
|             final c = scope.controllers[sf.path]; | ||||
|             if (c == null || c.text.trim().isEmpty) { | ||||
|               return 'Please complete required fields'; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   static EntityFormScope? get _scope => _EntityFormScopeAccessor.scope; | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic>? get customProperties => { | ||||
|         'excludeFromSubmit': true, | ||||
|         'assignByJsonPaths': true, | ||||
|         'paths': _effectiveSubFields.map((e) => e.path).toList(), | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     return _EntityFormScopeAccessor( | ||||
|       builder: (scope) { | ||||
|         final fields = _effectiveSubFields; | ||||
|         return Container( | ||||
|           decoration: BoxDecoration( | ||||
|             color: colorScheme.surface, | ||||
|             borderRadius: BorderRadius.circular(16), | ||||
|             border: | ||||
|                 Border.all(color: colorScheme.outlineVariant.withOpacity(0.4)), | ||||
|             boxShadow: [ | ||||
|               BoxShadow( | ||||
|                 color: colorScheme.shadow.withOpacity(0.04), | ||||
|                 blurRadius: 10, | ||||
|                 offset: const Offset(0, 6), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           child: Padding( | ||||
|             padding: const EdgeInsets.all(16), | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Text( | ||||
|                   label, | ||||
|                   style: TextStyle( | ||||
|                     color: colorScheme.onSurface, | ||||
|                     fontWeight: FontWeight.w700, | ||||
|                     fontSize: 16, | ||||
|                   ), | ||||
|                 ), | ||||
|                 if (hint.isNotEmpty) ...[ | ||||
|                   const SizedBox(height: 4), | ||||
|                   Text( | ||||
|                     hint, | ||||
|                     style: TextStyle( | ||||
|                       color: colorScheme.onSurfaceVariant, | ||||
|                       fontSize: 12, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|                 const SizedBox(height: 12), | ||||
|                 ...fields.map((sf) => _buildSubField( | ||||
|                     scope, sf, colorScheme, controller, onChanged)), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Widget _buildSubField( | ||||
|     EntityFormScope? scope, | ||||
|     GroupSubField sf, | ||||
|     ColorScheme colorScheme, | ||||
|     TextEditingController groupController, | ||||
|     VoidCallback? onChanged, | ||||
|   ) { | ||||
|     final existing = scope?.controllers[sf.path]; | ||||
|     final TextEditingController controller = | ||||
|         existing ?? TextEditingController(); | ||||
|     // Ensure controller is registered in scope for inner fields | ||||
|     if (scope != null && existing == null) { | ||||
|       scope.controllers[sf.path] = controller; | ||||
|     } | ||||
|     // Prefill subfield from group's encoded initial JSON (built by EntityForm) | ||||
|     if (controller.text.isEmpty && groupController.text.isNotEmpty) { | ||||
|       try { | ||||
|         final decoded = const JsonDecoder().convert(groupController.text); | ||||
|         if (decoded is Map<String, dynamic>) { | ||||
|           final dynamic v = decoded[sf.path]; | ||||
|           if (v != null && v.toString().isNotEmpty) { | ||||
|             controller.text = v.toString(); | ||||
|           } | ||||
|         } | ||||
|       } catch (_) { | ||||
|         // Ignore decode errors; groupController may not be JSON yet | ||||
|       } | ||||
|     } | ||||
|     controller.addListener(() { | ||||
|       // Encode all subfield values into the group controller for submit mapping | ||||
|       final Map<String, dynamic> map = {}; | ||||
|       for (final s in _effectiveSubFields) { | ||||
|         final c = scope?.controllers[s.path]; | ||||
|         if (c != null && c.text.isNotEmpty) { | ||||
|           map[s.path] = c.text.trim(); | ||||
|         } | ||||
|       } | ||||
|       groupController.text = _encode(map); | ||||
|       onChanged?.call(); | ||||
|       scope?.notifyParent(); | ||||
|     }); | ||||
| 
 | ||||
|     // Build appropriate shared field by type, but render it inline | ||||
|     final BaseField field = _toSharedField(sf); | ||||
|     return Padding( | ||||
|       padding: const EdgeInsets.only(bottom: 12), | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           if (sf.label.isNotEmpty) | ||||
|             Padding( | ||||
|               padding: const EdgeInsets.only(bottom: 6), | ||||
|               child: Text( | ||||
|                 sf.label, | ||||
|                 style: TextStyle( | ||||
|                   color: colorScheme.onSurface, | ||||
|                   fontWeight: FontWeight.w600, | ||||
|                   fontSize: 13, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           field.buildField( | ||||
|             controller: controller, | ||||
|             colorScheme: colorScheme, | ||||
|             onChanged: onChanged, | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String _encode(Map<String, dynamic> map) => const JsonEncoder().convert(map); | ||||
| 
 | ||||
|   List<GroupSubField> get _effectiveSubFields { | ||||
|     if (subFields != null && subFields!.isNotEmpty) return subFields!; | ||||
|     final List<GroupSubField> out = []; | ||||
|     final prefix = groupPrefix ?? ''; | ||||
|     final schemaMap = schema ?? const {}; | ||||
|     schemaMap.forEach((name, type) { | ||||
|       final path = prefix.isEmpty ? name : '$prefix.$name'; | ||||
|       out.add(GroupSubField( | ||||
|         path: path, | ||||
|         label: _capitalize(name.replaceAll('_', ' ')), | ||||
|         hint: '', | ||||
|         keyboardType: _keyboardFromType(type), | ||||
|         isRequired: false, | ||||
|         type: type, | ||||
|       )); | ||||
|     }); | ||||
|     return out; | ||||
|   } | ||||
| 
 | ||||
|   TextInputType _keyboardFromType(String type) { | ||||
|     switch (type.toLowerCase()) { | ||||
|       case 'number': | ||||
|       case 'currency': | ||||
|         return TextInputType.number; | ||||
|       default: | ||||
|         return TextInputType.text; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   String _capitalize(String s) { | ||||
|     if (s.isEmpty) return s; | ||||
|     return s[0].toUpperCase() + s.substring(1); | ||||
|   } | ||||
| 
 | ||||
|   BaseField _toSharedField(GroupSubField sf) { | ||||
|     final t = (sf.type ?? 'text').toLowerCase(); | ||||
|     switch (t) { | ||||
|       case 'number': | ||||
|         return shared_number.NumberField( | ||||
|           fieldKey: sf.path, | ||||
|           label: sf.label, | ||||
|           hint: sf.hint, | ||||
|           isRequired: sf.isRequired, | ||||
|         ); | ||||
|       case 'date': | ||||
|         return shared_date.DateField( | ||||
|           fieldKey: sf.path, | ||||
|           label: sf.label, | ||||
|           hint: sf.hint, | ||||
|           isRequired: sf.isRequired, | ||||
|         ); | ||||
|       case 'currency': | ||||
|         return CurrencyField( | ||||
|           fieldKey: sf.path, | ||||
|           label: sf.label, | ||||
|           hint: sf.hint, | ||||
|           isRequired: sf.isRequired, | ||||
|         ); | ||||
|       case 'qrcode': | ||||
|       case 'qr': | ||||
|         return QRCodeField( | ||||
|           fieldKey: sf.path, | ||||
|           label: sf.label, | ||||
|           hint: sf.hint, | ||||
|           isRequired: sf.isRequired, | ||||
|         ); | ||||
|       case 'barcode': | ||||
|         return BarcodeField( | ||||
|           fieldKey: sf.path, | ||||
|           label: sf.label, | ||||
|           hint: sf.hint, | ||||
|           isRequired: sf.isRequired, | ||||
|         ); | ||||
|       case 'text': | ||||
|       default: | ||||
|         return CustomTextField( | ||||
|           fieldKey: sf.path, | ||||
|           label: sf.label, | ||||
|           hint: sf.hint, | ||||
|           isRequired: sf.isRequired, | ||||
|         ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class GroupSubField { | ||||
|   final String path; // JSON path for EntityForm to assign during submit | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final TextInputType keyboardType; | ||||
|   final bool isRequired; | ||||
|   final String? type; // schema type identifier | ||||
| 
 | ||||
|   GroupSubField({ | ||||
|     required this.path, | ||||
|     required this.label, | ||||
|     this.hint = '', | ||||
|     this.keyboardType = TextInputType.text, | ||||
|     this.isRequired = false, | ||||
|     this.type, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /// Helper to access EntityFormScope in a builder without changing base components | ||||
| class _EntityFormScopeAccessor extends StatefulWidget { | ||||
|   final Widget Function(EntityFormScope? scope) builder; | ||||
|   const _EntityFormScopeAccessor({required this.builder}); | ||||
| 
 | ||||
|   static EntityFormScope? get scope => _lastScope; | ||||
|   static EntityFormScope? _lastScope; | ||||
| 
 | ||||
|   @override | ||||
|   State<_EntityFormScopeAccessor> createState() => | ||||
|       _EntityFormScopeAccessorState(); | ||||
| } | ||||
| 
 | ||||
| class _EntityFormScopeAccessorState extends State<_EntityFormScopeAccessor> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final scope = EntityFormScope.of(context); | ||||
|     _EntityFormScopeAccessor._lastScope = scope; | ||||
|     return widget.builder(scope); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,85 @@ | ||||
| import 'dart:typed_data'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:file_picker/file_picker.dart'; | ||||
| import 'base_field.dart'; | ||||
| import '../utils/entity_field_store.dart'; | ||||
| 
 | ||||
| /// Multi-file upload field; stores files in EntityFieldStore under fieldKey | ||||
| class FileUploadField extends BaseField { | ||||
|   final String fieldKey; | ||||
|   final String label; | ||||
|   final String hint; | ||||
|   final bool isRequired; | ||||
| 
 | ||||
|   FileUploadField({ | ||||
|     required this.fieldKey, | ||||
|     required this.label, | ||||
|     this.hint = 'Select files', | ||||
|     this.isRequired = false, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String? Function(String?)? get validator => (_) { | ||||
|         final items = | ||||
|             EntityFieldStore.instance.get<List<UploadItem>>(fieldKey) ?? | ||||
|                 const []; | ||||
|         if (isRequired && items.isEmpty) { | ||||
|           return '$label is required'; | ||||
|         } | ||||
|         return null; | ||||
|       }; | ||||
| 
 | ||||
|   @override | ||||
|   Widget buildField({ | ||||
|     required TextEditingController controller, | ||||
|     required ColorScheme colorScheme, | ||||
|     VoidCallback? onChanged, | ||||
|   }) { | ||||
|     final items = EntityFieldStore.instance.get<List<UploadItem>>(fieldKey) ?? | ||||
|         <UploadItem>[]; | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Row( | ||||
|           children: [ | ||||
|             Text(label, | ||||
|                 style: TextStyle( | ||||
|                     fontWeight: FontWeight.w600, color: colorScheme.onSurface)), | ||||
|             const Spacer(), | ||||
|             TextButton.icon( | ||||
|               onPressed: () async { | ||||
|                 final result = | ||||
|                     await FilePicker.platform.pickFiles(allowMultiple: true); | ||||
|                 if (result != null) { | ||||
|                   final List<UploadItem> updated = List<UploadItem>.from(items); | ||||
|                   for (final f in result.files) { | ||||
|                     final Uint8List? bytes = f.bytes; | ||||
|                     if (bytes == null) continue; | ||||
|                     updated.add(UploadItem(fileName: f.name, bytes: bytes)); | ||||
|                   } | ||||
|                   EntityFieldStore.instance.set(fieldKey, updated); | ||||
|                   if (onChanged != null) onChanged(); | ||||
|                 } | ||||
|               }, | ||||
|               icon: const Icon(Icons.attach_file_rounded), | ||||
|               label: const Text('Add Files'), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|         const SizedBox(height: 8), | ||||
|         ...items.map((u) => ListTile( | ||||
|               leading: const Icon(Icons.insert_drive_file_rounded), | ||||
|               title: Text(u.fileName, overflow: TextOverflow.ellipsis), | ||||
|               trailing: IconButton( | ||||
|                 icon: const Icon(Icons.close_rounded), | ||||
|                 onPressed: () { | ||||
|                   final updated = List<UploadItem>.from(items)..remove(u); | ||||
|                   EntityFieldStore.instance.set(fieldKey, updated); | ||||
|                   if (onChanged != null) onChanged(); | ||||
|                 }, | ||||
|               ), | ||||
|             )), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||