base_project

This commit is contained in:
risadmin_prod 2025-09-17 09:13:44 +00:00
commit 98adc54f46
1303 changed files with 81474 additions and 0 deletions

View 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

View File

@ -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'

View File

@ -0,0 +1,4 @@
{
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "interactive"
}

View File

@ -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.

View File

@ -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

View 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

View File

@ -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 = "../.."
}

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -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>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View 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

View File

@ -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>

View File

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

View File

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

View File

@ -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

View File

@ -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 */;
}

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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"
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -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.

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -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.
}
}

View File

@ -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();
},
),
)),
],
);
}
}

View File

@ -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)),
],
),
);
}
}

View File

@ -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();
},
)
],
);
},
);
}
}

View File

@ -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();
}
},
),
],
),
],
),
),
],
);
}
}

View File

@ -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;
}

View File

@ -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),
);
}
}

View File

@ -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)),
));
}
}

View File

@ -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,
),
),
),
],
);
}
}

View File

@ -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(),
),
),
],
),
],
);
}
}

View File

@ -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,
);
}
}

View File

@ -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(),
),
);
},
);
}
}

View File

@ -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

View File

@ -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)}';
}
}

View File

@ -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',
// /// ),
// /// ];
// /// }
// /// }
// /// ```

View File

@ -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
}
}
}

View File

@ -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 ?? '';
},
);
}
}

View File

@ -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),
);
}
}

View File

@ -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(),
),
],
);
}
}

View File

@ -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,
);
}
}

View File

@ -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();
},
);
}
}

View File

@ -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);
}
}

View File

@ -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();
},
),
)),
],
);
}
}

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