base_project

This commit is contained in:
risadmin_prod 2025-09-09 03:52:13 +00:00
commit a16ff5d0fd
1286 changed files with 77612 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,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,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,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,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,50 @@
// 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;
// final String displayKey;
// 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(
// controller: controller,
// label: label,
// hint: hint,
// items: options,
// valueKey: valueKey,
// displayKey: displayKey,
// onChanged: onChanged != null ? (_) => onChanged() : null,
// validator: validator,
// );
// }
// }

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

View File

@ -0,0 +1,103 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'base_field.dart';
import '../utils/entity_field_store.dart';
class ImageUploadField extends BaseField {
final String fieldKey;
final String label;
final String hint;
final bool isRequired;
ImageUploadField({
required this.fieldKey,
required this.label,
this.hint = 'Select images',
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 ImagePicker picker = ImagePicker();
final XFile? picked =
await picker.pickImage(source: ImageSource.gallery);
if (picked != null) {
final Uint8List bytes = await picked.readAsBytes();
final updated = List<UploadItem>.from(items)
..add(UploadItem(fileName: picked.name, bytes: bytes));
EntityFieldStore.instance.set(fieldKey, updated);
if (onChanged != null) onChanged();
}
},
icon: const Icon(Icons.image_rounded),
label: const Text('Add Image'),
),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: items
.map((u) => Stack(
alignment: Alignment.topRight,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.memory(u.bytes,
width: 72, height: 72, fit: BoxFit.cover),
),
Positioned(
right: 0,
child: InkWell(
onTap: () {
final updated = List<UploadItem>.from(items)
..remove(u);
EntityFieldStore.instance.set(fieldKey, updated);
if (onChanged != null) onChanged();
},
child: Container(
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.close_rounded,
color: Colors.white, size: 18),
),
),
),
],
))
.toList(),
),
],
);
}
}

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'base_field.dart';
import '../../../shared/widgets/inputs/modern_text_field.dart';
/// Number input field implementation
class NumberField extends BaseField {
final String fieldKey;
final String label;
final String hint;
final bool isRequired;
final double? min;
final double? max;
final int? decimalPlaces;
NumberField({
required this.fieldKey,
required this.label,
required this.hint,
this.isRequired = false,
this.min,
this.max,
this.decimalPlaces,
});
@override
String? Function(String?)? get validator => (value) {
if (isRequired && (value == null || value.isEmpty)) {
return '$label is required';
}
if (value != null && value.isNotEmpty) {
final number = double.tryParse(value);
if (number == null) {
return 'Please enter a valid number';
}
if (min != null && number < min!) {
return '$label must be at least $min';
}
if (max != null && number > max!) {
return '$label must be at most $max';
}
}
return null;
};
@override
Widget buildField({
required TextEditingController controller,
required ColorScheme colorScheme,
VoidCallback? onChanged,
}) {
return ModernTextField(
controller: controller,
hint: hint,
keyboardType: TextInputType.numberWithOptions(
decimal: decimalPlaces != null && decimalPlaces! > 0,
),
onChanged: onChanged != null ? (_) => onChanged() : null,
validator: validator,
);
}
}

View File

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'base_field.dart';
import '../../../shared/widgets/inputs/modern_text_field.dart';
/// Reusable password field supporting dynamic pairing with confirm password
/// via a shared groupId. Use two instances with the same groupId, one with
/// isConfirm=true. EntityForm will handle cross-field validation and exclude
/// confirm entries from submission.
class PasswordField extends BaseField {
final String fieldKey;
final String label;
final String hint;
final bool isRequired;
final int? maxLength;
final String? groupId; // Pair password/confirm dynamically
final bool isConfirm; // Mark this instance as a confirm field
PasswordField({
required this.fieldKey,
required this.label,
required this.hint,
this.isRequired = false,
this.maxLength,
this.groupId,
this.isConfirm = false,
});
@override
String? Function(String?)? get validator => (value) {
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';
}
// Cross-field match is handled centrally in EntityForm using groupId
return null;
};
@override
Widget buildField({
required TextEditingController controller,
required ColorScheme colorScheme,
VoidCallback? onChanged,
}) {
return ModernTextField(
controller: controller,
hint: hint,
maxLength: maxLength,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
onChanged: onChanged != null ? (_) => onChanged() : null,
validator: validator,
);
}
@override
Map<String, dynamic>? get customProperties => {
if (groupId != null) 'groupId': groupId,
'isConfirm': isConfirm,
'isPassword': true,
};
}

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'base_field.dart';
import '../../../shared/widgets/inputs/modern_text_field.dart';
/// Phone number input field implementation
class PhoneField extends BaseField {
final String fieldKey;
final String label;
final String hint;
final bool isRequired;
final String? countryCode;
PhoneField({
required this.fieldKey,
required this.label,
required this.hint,
this.isRequired = false,
this.countryCode,
});
@override
String? Function(String?)? get validator => (value) {
if (isRequired && (value == null || value.isEmpty)) {
return '$label is required';
}
if (value != null && value.isNotEmpty) {
// Phone number validation
final phoneRegex = RegExp(r'^\+?[\d\s\-\(\)]+$');
if (!phoneRegex.hasMatch(value)) {
return 'Please enter a valid phone number';
}
}
return null;
};
@override
Widget buildField({
required TextEditingController controller,
required ColorScheme colorScheme,
VoidCallback? onChanged,
}) {
return ModernTextField(
controller: controller,
hint: hint,
keyboardType: TextInputType.phone,
prefixIcon: countryCode != null ? Text(countryCode!) : null,
onChanged: onChanged != null ? (_) => onChanged() : null,
validator: validator,
);
}
}

View File

@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'base_field.dart';
class RadioField extends BaseField {
final String fieldKey;
final String label;
final String hint;
final bool isRequired;
final List<String> options; // e.g., ['yes','no']
RadioField({
required this.fieldKey,
required this.label,
this.hint = '',
this.isRequired = false,
required this.options,
});
@override
String? Function(String?)? get validator => (value) {
if (isRequired && (value == null || value.isEmpty)) {
return '$label is required';
}
if (value != null && value.isNotEmpty && !options.contains(value)) {
return 'Invalid selection';
}
return null;
};
@override
Widget buildField({
required TextEditingController controller,
required ColorScheme colorScheme,
VoidCallback? onChanged,
}) {
final String current = controller.text;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
Wrap(
spacing: 12,
children: options.map((opt) {
return ChoiceChip(
label: Text(opt),
selected: current == opt,
selectedColor: colorScheme.primary.withOpacity(0.2),
onSelected: (_) {
controller.text = opt;
if (onChanged != null) onChanged();
},
);
}).toList(),
),
],
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'base_field.dart';
/// Boolean switch field implementation
class SwitchField extends BaseField {
final String fieldKey;
final String label;
final String hint;
final bool isRequired;
SwitchField({
required this.fieldKey,
required this.label,
this.hint = '',
this.isRequired = false,
});
@override
String? Function(String?)? get validator => (_) => null;
@override
Widget buildField({
required TextEditingController controller,
required ColorScheme colorScheme,
VoidCallback? onChanged,
}) {
final bool current = (controller.text.toLowerCase() == 'true');
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
label,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
Switch(
value: current,
activeColor: colorScheme.onPrimary,
activeTrackColor: colorScheme.primary,
onChanged: (value) {
controller.text = value.toString();
if (onChanged != null) onChanged();
},
),
],
);
}
}

View File

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'base_field.dart';
import '../../../shared/widgets/inputs/modern_text_field.dart';
/// URL input field implementation using ModernTextField
class UrlField extends BaseField {
final String fieldKey;
final String label;
final String hint;
final bool isRequired;
UrlField({
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 uri = Uri.tryParse(value);
if (uri == null || !(uri.hasScheme && uri.hasAuthority)) {
return 'Please enter a valid URL';
}
}
return null;
};
@override
Widget buildField({
required TextEditingController controller,
required ColorScheme colorScheme,
VoidCallback? onChanged,
}) {
return ModernTextField(
controller: controller,
hint: hint,
keyboardType: TextInputType.url,
prefixIcon: const Icon(Icons.link_rounded),
validator: validator,
onChanged: onChanged != null ? (_) => onChanged() : null,
);
}
}

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 VideoUploadField extends BaseField {
final String fieldKey;
final String label;
final String hint;
final bool isRequired;
VideoUploadField({
required this.fieldKey,
required this.label,
this.hint = 'Select video 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.video);
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.video_library_rounded),
label: const Text('Add Video'),
),
],
),
const SizedBox(height: 8),
...items.map((u) => ListTile(
leading: const Icon(Icons.videocam_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,492 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/constants/ui_constants.dart';
import '../../../core/providers/dynamic_theme_provider.dart';
/// Reusable card component for displaying entity data
/// Uses dynamic theme and provides consistent styling across all entities
class EntityCard extends StatelessWidget {
final Map<String, dynamic> entity;
final Function(Map<String, dynamic>) onEdit;
final Function(Map<String, dynamic>) onDelete;
final Function(Map<String, dynamic>)? onTap;
final List<Map<String, dynamic>> displayFields;
const EntityCard({
super.key,
required this.entity,
required this.onEdit,
required this.onDelete,
this.onTap,
this.displayFields = const [],
});
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark,
);
final size = MediaQuery.of(context).size;
final isMobile = size.width < 600;
final isTablet = size.width >= 600 && size.width < 1024;
final double maxCardHeight = isMobile
? size.height * 0.34
: isTablet
? size.height * 0.38
: size.height * 0.42;
final double horizontalPadding = isMobile
? UIConstants.spacing16
: isTablet
? UIConstants.spacing20
: UIConstants.spacing24;
final double verticalPadding = isMobile
? UIConstants.spacing16
: isTablet
? UIConstants.spacing16
: UIConstants.spacing20;
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: 160,
maxHeight: maxCardHeight.clamp(160.0, size.height * 0.8),
),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.surface,
colorScheme.surface.withOpacity(0.95),
],
),
borderRadius: BorderRadius.circular(UIConstants.radius20),
border: Border.all(
color: colorScheme.primary.withOpacity(0.1),
width: 1.5,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.08),
blurRadius: 20,
offset: const Offset(0, 8),
spreadRadius: 2,
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap != null ? () => onTap!(entity) : null,
borderRadius: BorderRadius.circular(UIConstants.radius20),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: horizontalPadding,
vertical: verticalPadding,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with avatar and action buttons
Row(
children: [
_buildAvatar(colorScheme,
isMobile: isMobile, isTablet: isTablet),
const Spacer(),
_buildActionButtons(colorScheme),
],
),
const SizedBox(height: UIConstants.spacing16),
// Dynamic field display (scrollable to avoid overflow)
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildFieldDisplays(colorScheme),
),
),
),
const SizedBox(height: UIConstants.spacing12),
// Status indicator pinned at bottom
_buildStatusIndicator(colorScheme),
],
),
),
),
),
),
);
},
);
}
Widget _buildAvatar(ColorScheme colorScheme,
{required bool isMobile, required bool isTablet}) {
final double side = isMobile
? 44
: isTablet
? 50
: 56;
final double icon = isMobile
? 20
: isTablet
? 22
: 24;
return Container(
width: side,
height: side,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary,
colorScheme.primary.withOpacity(0.8),
],
),
borderRadius: BorderRadius.circular(UIConstants.radius12),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Icon(
Icons.person_rounded,
color: colorScheme.onPrimary,
size: icon,
),
);
}
Widget _buildActionButtons(ColorScheme colorScheme) {
return Row(
children: [
// Edit Button
Container(
decoration: BoxDecoration(
color: colorScheme.primaryContainer.withOpacity(0.3),
borderRadius: BorderRadius.circular(UIConstants.radius8),
border: Border.all(
color: colorScheme.primary.withOpacity(0.2),
width: 1,
),
),
child: IconButton(
onPressed: () => onEdit(entity),
icon: Icon(
Icons.edit_rounded,
color: colorScheme.primary,
size: 18,
),
tooltip: 'Edit',
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
),
),
const SizedBox(width: UIConstants.spacing8),
// Delete Button
Container(
decoration: BoxDecoration(
color: colorScheme.errorContainer.withOpacity(0.3),
borderRadius: BorderRadius.circular(UIConstants.radius8),
border: Border.all(
color: colorScheme.error.withOpacity(0.2),
width: 1,
),
),
child: IconButton(
onPressed: () => onDelete(entity),
icon: Icon(
Icons.delete_rounded,
color: colorScheme.error,
size: 18,
),
tooltip: 'Delete',
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
),
),
],
);
}
List<Widget> _buildFieldDisplays(ColorScheme colorScheme) {
// Dynamic field display based on entity data
final displayFields = _getDisplayFields();
return displayFields.map((field) {
final value = entity[field['key']]?.toString() ?? '';
if (value.isEmpty) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(bottom: UIConstants.spacing8),
child: _buildFieldDisplay(field, value, colorScheme),
);
}).toList();
}
Widget _buildFieldDisplay(
Map<String, dynamic> field, String value, ColorScheme colorScheme) {
switch (field['type']) {
case 'phone':
return _buildPhoneDisplay(value, colorScheme);
case 'email':
return _buildEmailDisplay(value, colorScheme);
case 'number':
return _buildNumberDisplay(field['label'], value, colorScheme);
default:
return _buildTextDisplay(field['label'], value, colorScheme);
}
}
Widget _buildPhoneDisplay(String value, ColorScheme colorScheme) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing12,
vertical: UIConstants.spacing8,
),
decoration: BoxDecoration(
color: colorScheme.primaryContainer.withOpacity(0.2),
borderRadius: BorderRadius.circular(UIConstants.radius8),
border: Border.all(
color: colorScheme.primary.withOpacity(0.1),
width: 1,
),
),
child: Row(
children: [
Icon(
Icons.phone_rounded,
size: 16,
color: colorScheme.primary,
),
const SizedBox(width: UIConstants.spacing8),
Expanded(
child: Text(
value,
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.8),
fontSize: 13,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
Widget _buildEmailDisplay(String value, ColorScheme colorScheme) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing12,
vertical: UIConstants.spacing8,
),
decoration: BoxDecoration(
color: colorScheme.secondaryContainer.withOpacity(0.2),
borderRadius: BorderRadius.circular(UIConstants.radius8),
border: Border.all(
color: colorScheme.secondary.withOpacity(0.1),
width: 1,
),
),
child: Row(
children: [
Icon(
Icons.email_rounded,
size: 16,
color: colorScheme.secondary,
),
const SizedBox(width: UIConstants.spacing8),
Expanded(
child: Text(
value,
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.8),
fontSize: 13,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
Widget _buildTextDisplay(
String label, String value, ColorScheme colorScheme) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (label.isNotEmpty) ...[
const SizedBox(height: UIConstants.spacing4),
Text(
label,
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 12,
fontWeight: FontWeight.w400,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
],
);
}
Widget _buildNumberDisplay(
String label, String value, ColorScheme colorScheme) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing12,
vertical: UIConstants.spacing8,
),
decoration: BoxDecoration(
color: colorScheme.tertiaryContainer.withOpacity(0.2),
borderRadius: BorderRadius.circular(UIConstants.radius8),
border: Border.all(
color: colorScheme.tertiary.withOpacity(0.1),
width: 1,
),
),
child: Row(
children: [
Icon(
Icons.numbers_rounded,
size: 16,
color: colorScheme.tertiary,
),
const SizedBox(width: UIConstants.spacing8),
Expanded(
child: Text(
value,
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.8),
fontSize: 13,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
Widget _buildStatusIndicator(ColorScheme colorScheme) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing8,
vertical: UIConstants.spacing4,
),
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: colorScheme.primary,
shape: BoxShape.circle,
),
),
const SizedBox(width: UIConstants.spacing6),
Text(
'Active',
style: TextStyle(
color: colorScheme.primary,
fontSize: 11,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
List<Map<String, dynamic>> _getDisplayFields() {
// Use provided displayFields or default fields
if (displayFields.isNotEmpty) {
return displayFields;
}
// Default fields - try to get meaningful fields from entity
final defaultFields = <Map<String, dynamic>>[];
// Try to find primary fields
final primaryFields = ['name', 'title', 'id'];
for (final field in primaryFields) {
if (entity.containsKey(field) && entity[field] != null) {
defaultFields.add({'key': field, 'label': '', 'type': 'text'});
break; // Only add first found primary field
}
}
// Add other non-empty fields (max 3 for card display)
int addedCount = 0;
for (final entry in entity.entries) {
if (entry.value != null &&
entry.value.toString().isNotEmpty &&
!primaryFields.contains(entry.key) &&
addedCount < 2) {
defaultFields.add({
'key': entry.key,
'label': _formatFieldLabel(entry.key),
'type': _getFieldType(entry.value)
});
addedCount++;
}
}
return defaultFields;
}
String _formatFieldLabel(String key) {
return key
.split('_')
.map((word) => word[0].toUpperCase() + word.substring(1))
.join(' ');
}
String _getFieldType(dynamic value) {
if (value is num) return 'number';
if (value.toString().contains('@')) return 'email';
if (RegExp(r'^\+?[\d\s\-\(\)]+$').hasMatch(value.toString()))
return 'phone';
return 'text';
}
}

View File

@ -0,0 +1,584 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/providers/dynamic_theme_provider.dart';
import '../../../core/constants/ui_constants.dart';
import '../../../shared/widgets/app_bar/modern_app_bar.dart';
import '../../../shared/widgets/buttons/modern_button.dart';
/// Generic details component for displaying entity information
/// This component works with any entity type and provides consistent UI/UX
class EntityDetails extends StatefulWidget {
final Map<String, dynamic> entity;
final Function(Map<String, dynamic>) onEdit;
final Function(Map<String, dynamic>) onDelete;
final String title;
final List<Map<String, dynamic>> displayFields;
final bool isLoading;
const EntityDetails({
super.key,
required this.entity,
required this.onEdit,
required this.onDelete,
required this.title,
this.displayFields = const [],
this.isLoading = false,
});
@override
State<EntityDetails> createState() => _EntityDetailsState();
}
class _EntityDetailsState extends State<EntityDetails>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_initializeAnimations();
}
void _initializeAnimations() {
_animationController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.6, curve: Curves.easeOut),
));
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _animationController,
curve: const Interval(0.2, 1.0, curve: Curves.easeOutCubic),
));
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark,
);
return Scaffold(
backgroundColor: colorScheme.surface,
appBar: ModernAppBar(
title: widget.title,
showBackButton: true,
actions: [
IconButton(
onPressed: () => widget.onEdit(widget.entity),
icon: Icon(
Icons.edit_rounded,
color: colorScheme.primary,
),
tooltip: 'Edit',
),
IconButton(
onPressed: () => _showDeleteDialog(colorScheme),
icon: Icon(
Icons.delete_rounded,
color: colorScheme.error,
),
tooltip: 'Delete',
),
],
),
body: widget.isLoading
? _buildLoadingState(colorScheme)
: _buildContent(colorScheme),
);
},
);
}
Widget _buildLoadingState(ColorScheme colorScheme) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: colorScheme.primary,
strokeWidth: 3,
),
const SizedBox(height: UIConstants.spacing16),
Text(
'Loading details...',
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
Widget _buildContent(ColorScheme colorScheme) {
return SingleChildScrollView(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.screenPaddingMedium,
tablet: UIConstants.screenPaddingLarge,
desktop: UIConstants.screenPaddingLarge,
),
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Card
_buildHeaderCard(colorScheme),
const SizedBox(height: UIConstants.spacing24),
// Details Section
_buildDetailsSection(colorScheme),
const SizedBox(height: UIConstants.spacing32),
// Action Buttons
_buildActionButtons(colorScheme),
],
),
),
);
},
),
);
}
Widget _buildHeaderCard(ColorScheme colorScheme) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary,
colorScheme.primary.withOpacity(0.8),
],
),
borderRadius: BorderRadius.circular(UIConstants.radius20),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 2,
),
],
),
child: Padding(
padding: UIConstants.cardPaddingLarge,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Avatar and Title
Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: colorScheme.onPrimary.withOpacity(0.2),
borderRadius: BorderRadius.circular(UIConstants.radius16),
),
child: Icon(
Icons.person_rounded,
color: colorScheme.onPrimary,
size: 30,
),
),
const SizedBox(width: UIConstants.spacing16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_getPrimaryFieldValue(),
style: TextStyle(
color: colorScheme.onPrimary,
fontSize: 24,
fontWeight: FontWeight.w700,
letterSpacing: 0.5,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: UIConstants.spacing4),
Text(
widget.title,
style: TextStyle(
color: colorScheme.onPrimary.withOpacity(0.8),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
const SizedBox(height: UIConstants.spacing16),
// Status Badge
Container(
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing12,
vertical: UIConstants.spacing6,
),
decoration: BoxDecoration(
color: colorScheme.onPrimary.withOpacity(0.2),
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: colorScheme.onPrimary,
shape: BoxShape.circle,
),
),
const SizedBox(width: UIConstants.spacing8),
Text(
'Active',
style: TextStyle(
color: colorScheme.onPrimary,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
),
);
}
Widget _buildDetailsSection(ColorScheme colorScheme) {
final displayFields = widget.displayFields.isNotEmpty
? widget.displayFields
: _getDefaultDisplayFields();
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.surface,
colorScheme.surface.withOpacity(0.95),
],
),
borderRadius: BorderRadius.circular(UIConstants.radius20),
border: Border.all(
color: colorScheme.primary.withOpacity(0.1),
width: 1.5,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.08),
blurRadius: 20,
offset: const Offset(0, 8),
spreadRadius: 2,
),
],
),
child: Padding(
padding: UIConstants.cardPaddingLarge,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Details',
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 20,
fontWeight: FontWeight.w700,
letterSpacing: 0.5,
),
),
const SizedBox(height: UIConstants.spacing20),
// Field Details
...displayFields
.map((field) => _buildFieldDetail(field, colorScheme)),
],
),
),
);
}
Widget _buildFieldDetail(
Map<String, dynamic> field, ColorScheme colorScheme) {
final value = widget.entity[field['key']]?.toString() ?? '';
if (value.isEmpty) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(bottom: UIConstants.spacing16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
field['label'] ?? field['key'],
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 12,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
const SizedBox(height: UIConstants.spacing4),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing16,
vertical: UIConstants.spacing12,
),
decoration: BoxDecoration(
color: _getFieldBackgroundColor(field['type'], colorScheme),
borderRadius: BorderRadius.circular(UIConstants.radius12),
border: Border.all(
color: _getFieldBorderColor(field['type'], colorScheme),
width: 1,
),
),
child: Row(
children: [
Icon(
_getFieldIcon(field['type']),
size: 18,
color: _getFieldIconColor(field['type'], colorScheme),
),
const SizedBox(width: UIConstants.spacing12),
Expanded(
child: Text(
value,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
],
),
);
}
Widget _buildActionButtons(ColorScheme colorScheme) {
return Row(
children: [
Expanded(
child: ModernButton(
text: 'Edit',
type: ModernButtonType.secondary,
size: ModernButtonSize.large,
onPressed: () => widget.onEdit(widget.entity),
icon: Icon(Icons.edit_rounded),
),
),
const SizedBox(width: UIConstants.spacing16),
Expanded(
child: ModernButton(
text: 'Delete',
type: ModernButtonType.danger,
size: ModernButtonSize.large,
onPressed: () => _showDeleteDialog(colorScheme),
icon: Icon(Icons.delete_rounded),
),
),
],
);
}
void _showDeleteDialog(ColorScheme colorScheme) {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: colorScheme.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UIConstants.radius20),
),
title: Text(
'Delete ${widget.title}',
style: TextStyle(
color: colorScheme.onSurface,
fontWeight: FontWeight.w700,
),
),
content: Text(
'Are you sure you want to delete this ${widget.title.toLowerCase()}? This action cannot be undone.',
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.8),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
'Cancel',
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontWeight: FontWeight.w600,
),
),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
widget.onDelete(widget.entity);
},
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.error,
foregroundColor: colorScheme.onError,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
),
child: const Text(
'Delete',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
],
),
);
}
String _getPrimaryFieldValue() {
// Try to get the primary field value (usually 'name' or first field)
final primaryFields = ['name', 'title', 'id'];
for (final field in primaryFields) {
final value = widget.entity[field]?.toString();
if (value != null && value.isNotEmpty) {
return value;
}
}
// If no primary field found, return first non-empty field
for (final entry in widget.entity.entries) {
if (entry.value != null && entry.value.toString().isNotEmpty) {
return entry.value.toString();
}
}
return 'Unknown';
}
List<Map<String, dynamic>> _getDefaultDisplayFields() {
return widget.entity.entries
.where(
(entry) => entry.value != null && entry.value.toString().isNotEmpty)
.map((entry) => {
'key': entry.key,
'label': _formatFieldLabel(entry.key),
'type': _getFieldType(entry.value),
})
.toList();
}
String _formatFieldLabel(String key) {
return key
.split('_')
.map((word) => word[0].toUpperCase() + word.substring(1))
.join(' ');
}
String _getFieldType(dynamic value) {
if (value is num) return 'number';
if (value.toString().contains('@')) return 'email';
if (RegExp(r'^\+?[\d\s\-\(\)]+$').hasMatch(value.toString()))
return 'phone';
return 'text';
}
Color _getFieldBackgroundColor(String type, ColorScheme colorScheme) {
switch (type) {
case 'phone':
return colorScheme.primaryContainer.withOpacity(0.2);
case 'email':
return colorScheme.secondaryContainer.withOpacity(0.2);
case 'number':
return colorScheme.tertiaryContainer.withOpacity(0.2);
default:
return colorScheme.surfaceVariant.withOpacity(0.3);
}
}
Color _getFieldBorderColor(String type, ColorScheme colorScheme) {
switch (type) {
case 'phone':
return colorScheme.primary.withOpacity(0.1);
case 'email':
return colorScheme.secondary.withOpacity(0.1);
case 'number':
return colorScheme.tertiary.withOpacity(0.1);
default:
return colorScheme.outline.withOpacity(0.2);
}
}
IconData _getFieldIcon(String type) {
switch (type) {
case 'phone':
return Icons.phone_rounded;
case 'email':
return Icons.email_rounded;
case 'number':
return Icons.numbers_rounded;
default:
return Icons.text_fields_rounded;
}
}
Color _getFieldIconColor(String type, ColorScheme colorScheme) {
switch (type) {
case 'phone':
return colorScheme.primary;
case 'email':
return colorScheme.secondary;
case 'number':
return colorScheme.tertiary;
default:
return colorScheme.onSurface.withOpacity(0.7);
}
}
}

View File

@ -0,0 +1,146 @@
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../fields/base_field.dart';
import '../../../shared/widgets/buttons/modern_button.dart';
import '../../../core/constants/ui_constants.dart';
/// Reusable form component that dynamically renders fields based on field definitions
/// This allows UI to be independent of field types and enables reusability
class EntityForm extends StatefulWidget {
final List<BaseField> fields;
final Map<String, dynamic>? initialData;
final Function(Map<String, dynamic>) onSubmit;
final String submitButtonText;
final bool isLoading;
const EntityForm({
super.key,
required this.fields,
this.initialData,
required this.onSubmit,
this.submitButtonText = 'Submit',
this.isLoading = false,
});
@override
State<EntityForm> createState() => _EntityFormState();
}
class _EntityFormState extends State<EntityForm> {
final _formKey = GlobalKey<FormState>();
final Map<String, TextEditingController> _controllers = {};
final Map<String, BaseField> _fieldByKey = {};
@override
void initState() {
super.initState();
_initializeControllers();
}
void _initializeControllers() {
for (final field in widget.fields) {
_controllers[field.fieldKey] = TextEditingController(
text: widget.initialData?[field.fieldKey]?.toString() ?? '',
);
_fieldByKey[field.fieldKey] = field;
}
}
@override
void dispose() {
for (final controller in _controllers.values) {
controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark,
);
return Form(
key: _formKey,
child: Column(
children: [
// Dynamic field rendering
...widget.fields.map((field) => Padding(
padding:
const EdgeInsets.only(bottom: UIConstants.spacing16),
child: field.buildField(
controller: _controllers[field.fieldKey]!,
colorScheme: colorScheme,
onChanged: () => setState(() {}),
),
)),
const SizedBox(height: UIConstants.spacing24),
// Submit button
ModernButton(
text: widget.submitButtonText,
type: ModernButtonType.primary,
size: ModernButtonSize.large,
isLoading: widget.isLoading,
onPressed: widget.isLoading ? null : _handleSubmit,
),
],
),
);
},
);
}
void _handleSubmit() {
if (_formKey.currentState!.validate()) {
// Dynamic cross-field match for any password-confirm group
final Map<String, String> passwordByGroup = {};
final Map<String, String> confirmByGroup = {};
for (final entry in _controllers.entries) {
final key = entry.key;
final field = _fieldByKey[key];
final props = field?.customProperties ?? const {};
final isPassword = props['isPassword'] == true;
if (!isPassword) continue;
final String? groupId = props['groupId'];
if (groupId == null) continue;
final bool isConfirm = props['isConfirm'] == true;
if (isConfirm) {
confirmByGroup[groupId] = entry.value.text;
} else {
passwordByGroup[groupId] = entry.value.text;
}
}
for (final gid in confirmByGroup.keys) {
final confirm = confirmByGroup[gid] ?? '';
final pass = passwordByGroup[gid] ?? '';
if (confirm.isNotEmpty && confirm != pass) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Passwords do not match')),
);
return;
}
}
final formData = <String, dynamic>{};
for (final entry in _controllers.entries) {
final key = entry.key;
final field = _fieldByKey[key];
final value = entry.value.text.trim();
// Skip confirm entries for any password group
final props = field?.customProperties ?? const {};
final bool isPassword = props['isPassword'] == true;
final bool isConfirm = props['isConfirm'] == true;
if (isPassword && isConfirm) continue;
formData[key] = value;
}
widget.onSubmit(formData);
}
}
}

View File

@ -0,0 +1,476 @@
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/constants/ui_constants.dart';
import '../../../shared/widgets/app_bar/modern_app_bar.dart';
import 'entity_card.dart';
/// Generic list component for displaying entities with search, pagination, and refresh
/// This component works with any entity type and provides consistent UI/UX
class EntityList extends StatefulWidget {
final List<Map<String, dynamic>> entities;
final bool isLoading;
final String? errorMessage;
final bool hasMoreData;
final String searchQuery;
final Function(String) onSearchChanged;
final Function(Map<String, dynamic>) onEdit;
final Function(Map<String, dynamic>) onDelete;
final Function(Map<String, dynamic>)? onTap;
final Function() onRefresh;
final Function() onLoadMore;
final String title;
final Function()? onAddNew;
final List<Map<String, dynamic>> displayFields;
const EntityList({
super.key,
required this.entities,
required this.isLoading,
this.errorMessage,
required this.hasMoreData,
required this.searchQuery,
required this.onSearchChanged,
required this.onEdit,
required this.onDelete,
this.onTap,
required this.onRefresh,
required this.onLoadMore,
required this.title,
this.onAddNew,
this.displayFields = const [],
});
@override
State<EntityList> createState() => _EntityListState();
}
class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
late AnimationController _animationController;
final TextEditingController _searchController = TextEditingController();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_initializeAnimations();
_searchController.text = widget.searchQuery;
_scrollController.addListener(_scrollListener);
}
void _initializeAnimations() {
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_animationController.forward();
}
void _scrollListener() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
if (widget.hasMoreData && !widget.isLoading) {
widget.onLoadMore();
}
}
}
@override
void dispose() {
_animationController.dispose();
_searchController.dispose();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark,
);
return Scaffold(
backgroundColor: colorScheme.surface,
appBar: ModernAppBar(
title: widget.title,
showBackButton: true,
actions: [
if (widget.onAddNew != null)
IconButton(
onPressed: widget.onAddNew,
icon: Icon(
Icons.add_rounded,
color: colorScheme.primary,
),
tooltip: 'Add New',
),
],
),
body: Column(
children: [
// Search Bar
_buildSearchBar(colorScheme),
// Content
Expanded(
child: _buildContent(colorScheme),
),
],
),
);
},
);
}
Widget _buildSearchBar(ColorScheme colorScheme) {
return Container(
margin: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.screenPaddingMedium,
tablet: UIConstants.screenPaddingLarge,
desktop: UIConstants.screenPaddingLarge,
),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.surface,
colorScheme.surface.withOpacity(0.95),
],
),
borderRadius: BorderRadius.circular(UIConstants.radius16),
border: Border.all(
color: colorScheme.primary.withOpacity(0.1),
width: 1.5,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.08),
blurRadius: 15,
offset: const Offset(0, 5),
spreadRadius: 1,
),
],
),
child: Row(
children: [
Expanded(
child: TextField(
controller: _searchController,
onChanged: widget.onSearchChanged,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
decoration: InputDecoration(
hintText: 'Search ${widget.title.toLowerCase()}...',
hintStyle: TextStyle(
color: colorScheme.onSurface.withOpacity(0.6),
fontSize: 16,
fontWeight: FontWeight.w400,
),
prefixIcon: Icon(
Icons.search_rounded,
color: colorScheme.primary,
size: 22,
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
onPressed: () {
_searchController.clear();
widget.onSearchChanged('');
},
icon: Icon(
Icons.clear_rounded,
color: colorScheme.onSurface.withOpacity(0.6),
size: 20,
),
)
: null,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing16,
vertical: UIConstants.spacing12,
),
),
),
),
IconButton(
onPressed: widget.onRefresh,
icon: Icon(
Icons.refresh_rounded,
color: colorScheme.onSurface.withOpacity(0.8),
size: 22,
),
tooltip: 'Refresh',
),
],
),
);
}
Widget _buildContent(ColorScheme colorScheme) {
if (widget.isLoading && widget.entities.isEmpty) {
return _buildLoadingState(colorScheme);
}
if (widget.errorMessage != null && widget.entities.isEmpty) {
return _buildErrorState(colorScheme);
}
if (widget.entities.isEmpty) {
return _buildEmptyState(colorScheme);
}
return _buildEntityGrid(colorScheme);
}
Widget _buildLoadingState(ColorScheme colorScheme) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: colorScheme.primary,
strokeWidth: 3,
),
const SizedBox(height: UIConstants.spacing16),
Text(
'Loading ${widget.title.toLowerCase()}...',
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
Widget _buildErrorState(ColorScheme colorScheme) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: colorScheme.errorContainer.withOpacity(0.2),
borderRadius: BorderRadius.circular(UIConstants.radius20),
),
child: Icon(
Icons.error_outline_rounded,
color: colorScheme.error,
size: 40,
),
),
const SizedBox(height: UIConstants.spacing16),
Text(
'Error Loading Data',
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing8),
Text(
widget.errorMessage ?? 'Something went wrong',
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
const SizedBox(height: UIConstants.spacing24),
ElevatedButton.icon(
onPressed: widget.onRefresh,
icon: Icon(
Icons.refresh_rounded,
color: colorScheme.onPrimary,
),
label: Text(
'Retry',
style: TextStyle(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 4,
shadowColor: colorScheme.primary.withOpacity(0.3),
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing24,
vertical: UIConstants.spacing12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
),
),
],
),
);
}
Widget _buildEmptyState(ColorScheme colorScheme) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: colorScheme.primaryContainer.withOpacity(0.2),
borderRadius: BorderRadius.circular(UIConstants.radius20),
),
child: Icon(
Icons.inbox_outlined,
color: colorScheme.primary,
size: 40,
),
),
const SizedBox(height: UIConstants.spacing16),
Text(
'No ${widget.title.toLowerCase()} found',
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing8),
Text(
widget.searchQuery.isNotEmpty
? 'Try adjusting your search terms'
: 'Start by adding your first ${widget.title.toLowerCase()}',
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
if (widget.onAddNew != null) ...[
const SizedBox(height: UIConstants.spacing24),
ElevatedButton.icon(
onPressed: widget.onAddNew,
icon: Icon(
Icons.add_rounded,
color: colorScheme.onPrimary,
),
label: Text(
'Add ${widget.title}',
style: TextStyle(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 4,
shadowColor: colorScheme.primary.withOpacity(0.3),
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing24,
vertical: UIConstants.spacing12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
),
),
],
],
),
);
}
Widget _buildEntityGrid(ColorScheme colorScheme) {
return RefreshIndicator(
onRefresh: () async => widget.onRefresh(),
color: colorScheme.primary,
child: GridView.builder(
controller: _scrollController,
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.screenPaddingMedium,
tablet: UIConstants.screenPaddingLarge,
desktop: UIConstants.screenPaddingLarge,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: UIConstants.getResponsiveInt(
context,
mobile: 1,
tablet: 2,
desktop: 3,
),
childAspectRatio: 0.85,
crossAxisSpacing: UIConstants.spacing16,
mainAxisSpacing: UIConstants.spacing16,
),
itemCount: widget.entities.length + (widget.isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index < widget.entities.length) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return FadeTransition(
opacity: _animationController,
child: Transform.translate(
offset: Offset(0, 20 * (1 - _animationController.value)),
child: EntityCard(
entity: widget.entities[index],
onEdit: widget.onEdit,
onDelete: widget.onDelete,
onTap: widget.onTap,
displayFields: widget.displayFields,
),
),
);
},
);
} else {
return _buildLoadingCard(colorScheme);
}
},
),
);
}
Widget _buildLoadingCard(ColorScheme colorScheme) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.surface,
colorScheme.surface.withOpacity(0.95),
],
),
borderRadius: BorderRadius.circular(UIConstants.radius20),
border: Border.all(
color: colorScheme.primary.withOpacity(0.1),
width: 1.5,
),
),
child: Center(
child: CircularProgressIndicator(
color: colorScheme.primary,
strokeWidth: 2,
),
),
);
}
}

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