Initial commit
45
.gitignore
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
45
.metadata
Normal 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: "68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
- platform: android
|
||||||
|
create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
- platform: linux
|
||||||
|
create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
- platform: macos
|
||||||
|
create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
- platform: web
|
||||||
|
create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
- platform: windows
|
||||||
|
create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
|
||||||
|
|
||||||
|
# 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'
|
||||||
16
README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# frontend
|
||||||
|
|
||||||
|
Travel Expense Management
|
||||||
|
|
||||||
|
## 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.
|
||||||
28
analysis_options.yaml
Normal 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
|
||||||
13
android/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
44
android/app/build.gradle
Normal 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.travelexpense.ts.frontend"
|
||||||
|
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.travelexpense.ts.frontend"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig = signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
7
android/app/src/debug/AndroidManifest.xml
Normal 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>
|
||||||
45
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="frontend"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.travelexpense.ts.frontend
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity()
|
||||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal 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>
|
||||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal 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>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal 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>
|
||||||
18
android/app/src/main/res/values/styles.xml
Normal 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>
|
||||||
7
android/app/src/profile/AndroidManifest.xml
Normal 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>
|
||||||
18
android/build.gradle
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
3
android/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
|
||||||
25
android/settings.gradle
Normal 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.1.0" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
||||||
34
ios/.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
26
ios/Flutter/AppFrameworkInfo.plist
Normal 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>
|
||||||
2
ios/Flutter/Debug.xcconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
2
ios/Flutter/Release.xcconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
44
ios/Podfile
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# 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
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
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
|
||||||
616
ios/Runner.xcodeproj/project.pbxproj
Normal 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.travelexpense.ts.frontend;
|
||||||
|
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.travelexpense.ts.frontend.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.travelexpense.ts.frontend.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.travelexpense.ts.frontend.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.travelexpense.ts.frontend;
|
||||||
|
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.travelexpense.ts.frontend;
|
||||||
|
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 */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
98
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<?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"
|
||||||
|
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"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
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>
|
||||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
13
ios/Runner/AppDelegate.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal 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.
|
||||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal 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>
|
||||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal 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>
|
||||||
49
ios/Runner/Info.plist
Normal 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>Frontend</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>frontend</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>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
23
lib/Screens/authentication/login/login_page.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'login_widget.dart';
|
||||||
|
|
||||||
|
class LoginPage extends StatelessWidget {
|
||||||
|
const LoginPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInformation) {
|
||||||
|
bool isDesktop = sizingInformation.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
bool isTablet = sizingInformation.deviceScreenType == DeviceScreenType.tablet;
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: LoginWidget(isDesktop: isDesktop, isTablet: isTablet),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
186
lib/Screens/authentication/login/login_widget.dart
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/config/apiUrl.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class LoginWidget extends StatefulWidget {
|
||||||
|
final bool isDesktop;
|
||||||
|
final bool isTablet;
|
||||||
|
|
||||||
|
const LoginWidget({Key? key, required this.isDesktop, required this.isTablet}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LoginWidgetState createState() => _LoginWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginWidgetState extends State<LoginWidget> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final TextEditingController _emailController = TextEditingController();
|
||||||
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
|
bool _obscureText = true;
|
||||||
|
|
||||||
|
void _login(BuildContext context) async {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
const String url = '$apiUrl/auth/login';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse(url),
|
||||||
|
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
|
||||||
|
body: jsonEncode({
|
||||||
|
'email': _emailController.text.trim(),
|
||||||
|
'password': _passwordController.text.trim(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
print("data- $data");
|
||||||
|
|
||||||
|
final token = data['token']; // Assuming the token is in response
|
||||||
|
final userId = data['user_id'].toString();
|
||||||
|
|
||||||
|
// Save token to SharedPreferences
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('auth_token', token);
|
||||||
|
await prefs.setString('userId', userId);
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text("Login Successful")),
|
||||||
|
);
|
||||||
|
context.go('/home'); // Navigate to home
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Login Failed: ${jsonDecode(response.body)['message']}")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Error: $e")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
/// Layout
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
double formWidth = widget.isTablet ? 400 : 300;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
if (widget.isDesktop)
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
"Welcome Back!",
|
||||||
|
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(40),
|
||||||
|
child: _buildForm(width: formWidth), // Fixed form width
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// **Reusable Login Form**
|
||||||
|
Widget _buildForm({required double width}) {
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"Hello! Welcome Back",
|
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.blueAccent),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
/// **Email Field**
|
||||||
|
_buildLabel("EMAIL"),
|
||||||
|
TextFormField(
|
||||||
|
controller: _emailController,
|
||||||
|
decoration: _inputDecoration("Enter your email"),
|
||||||
|
validator: (value) => value == null || value.isEmpty ? 'Required Email' : null,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
/// **Password Field**
|
||||||
|
_buildLabel("PASSWORD"),
|
||||||
|
TextFormField(
|
||||||
|
controller: _passwordController,
|
||||||
|
obscureText: _obscureText,
|
||||||
|
decoration: _inputDecoration("Enter your password").copyWith(
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(_obscureText ? Icons.visibility_off : Icons.visibility, color: Colors.blueAccent),
|
||||||
|
onPressed: () => setState(() => _obscureText = !_obscureText),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) => value == null || value.isEmpty ? 'Required Password' : null,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
/// **Login Button**
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _login(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blueAccent, // Button color
|
||||||
|
foregroundColor: Colors.white, // Text color
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
),
|
||||||
|
child: const Text("Login"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **Reusable Label Widget**
|
||||||
|
Widget _buildLabel(String text) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.blueAccent),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **Reusable Input Decoration**
|
||||||
|
InputDecoration _inputDecoration(String hint) {
|
||||||
|
return InputDecoration(
|
||||||
|
labelText: hint,
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.white,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
213
lib/Screens/authentication/loginPage1.dart
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:frontend/config/apiUrl.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class LoginPage1 extends StatefulWidget {
|
||||||
|
const LoginPage1({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LoginPageState createState() => _LoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPageState extends State<LoginPage1> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: LoginWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// StatefulWidget for Body Content
|
||||||
|
class LoginWidget extends StatefulWidget{
|
||||||
|
const LoginWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LoginWidgetState createState() => _LoginWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginWidgetState extends State<LoginWidget>{
|
||||||
|
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final TextEditingController _emailController = TextEditingController();
|
||||||
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
|
bool _obscureText = true;
|
||||||
|
|
||||||
|
void _login(BuildContext context) async {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
// const String url = '$apiUrl/auth/login'; // Replace with actual API URL
|
||||||
|
const String url = 'https://apitest.tripapprovaltool.com/auth/login';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse(url),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'},
|
||||||
|
body: jsonEncode({
|
||||||
|
'email': _emailController.text.trim(),
|
||||||
|
'password': _passwordController.text.trim(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text("Login Successful")),
|
||||||
|
);
|
||||||
|
context.go('/home'); // Login Success
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Login Failed: ${jsonDecode(response.body)['message']}")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Error: $e")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context){
|
||||||
|
double myHeight = MediaQuery.of(context).size.height;
|
||||||
|
double myWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"Hello! , Welcome Back",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blueAccent
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: myHeight/10),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: const Text(
|
||||||
|
"EMAIL",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blueAccent
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height:8),
|
||||||
|
TextFormField(
|
||||||
|
controller: _emailController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Enter the email',
|
||||||
|
labelStyle: TextStyle(color: Colors.grey,),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
filled: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12), // Rounded corners// Remove the default border
|
||||||
|
borderSide: BorderSide.none
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value){
|
||||||
|
if(value == null || value.isEmpty){
|
||||||
|
return 'Required Email';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: const Text(
|
||||||
|
"PASSWORD",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blueAccent
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height:8),
|
||||||
|
TextFormField(
|
||||||
|
controller:_passwordController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
obscureText: _obscureText,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Enter the password",
|
||||||
|
labelStyle: TextStyle(color: Colors.grey,),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_obscureText ? Icons.visibility_off : Icons.visibility,
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_obscureText = !_obscureText; // Toggle state
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide.none
|
||||||
|
)
|
||||||
|
),
|
||||||
|
validator: (value){
|
||||||
|
if(value == null || value.isEmpty){
|
||||||
|
return 'Required Password';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: (){
|
||||||
|
_login(context);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blueAccent, // Change button color
|
||||||
|
foregroundColor: Colors.white, // Text color
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24,vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
child: const Text(
|
||||||
|
"Login")
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
35
lib/Screens/dashboard/home_page.dart
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
|
||||||
|
class HomePage extends StatelessWidget {
|
||||||
|
const HomePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: isDesktop ? null : const CustomAppBar(title:'Home'),
|
||||||
|
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
||||||
|
body:
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if(isDesktop)
|
||||||
|
CustomDrawer(isDesktop: true),
|
||||||
|
const Expanded(child: Center(child: Text("Home Page Content"))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
476
lib/Screens/dialog/user_selection_dialog.dart
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../data/models/searchUser.dart';
|
||||||
|
import '../../widgets/custom_text_traveller.dart';
|
||||||
|
|
||||||
|
class UserSelectionDialog extends StatefulWidget{
|
||||||
|
final String title;
|
||||||
|
final void Function(String) onSubmit;
|
||||||
|
|
||||||
|
UserSelectionDialog({Key? key, required this.title, required this.onSubmit}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_UserSelectionDialogState createState() => _UserSelectionDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserSelectionDialogState extends State<UserSelectionDialog>{
|
||||||
|
TextEditingController _controller = TextEditingController();
|
||||||
|
TextEditingController _searchController = TextEditingController();
|
||||||
|
// List<String> _filteredUsers = [];
|
||||||
|
// List<String> _users = [];
|
||||||
|
List<SearchUser> _users = [];
|
||||||
|
List<SearchUser> _filteredUsers = [];
|
||||||
|
bool _showTravellerForm = false;
|
||||||
|
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> fetchUsers() async {
|
||||||
|
final String apiUrldata = '$apiUrl/api/users';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final token = await getToken();
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrldata),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final Map<String,dynamic> responseBody = json.decode(response.body);
|
||||||
|
|
||||||
|
|
||||||
|
print("API Response: $responseBody"); // Debugging
|
||||||
|
|
||||||
|
if (responseBody.containsKey('data') && responseBody['data'] is List) {
|
||||||
|
List<dynamic> userList = responseBody['data'];
|
||||||
|
|
||||||
|
// Extract first name and last name correctly
|
||||||
|
// setState(() {
|
||||||
|
// _users = userList
|
||||||
|
// .where((user) => user['first_name'] != null && user['last_name'] != null)
|
||||||
|
// .map((user) => "${user['first_name']} ${user['last_name']}")
|
||||||
|
// .toList();
|
||||||
|
// _filteredUsers = List.from(_users);
|
||||||
|
// });
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_users = userList.map((user) => SearchUser.fromJson(user)).toList();
|
||||||
|
_filteredUsers = List.from(_users);
|
||||||
|
});
|
||||||
|
|
||||||
|
print("Users fetched: ${_users.length}");
|
||||||
|
for (var user in _users) {
|
||||||
|
print("${user.firstName} ${user.lastName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw Exception("Unexpected response format: Expected a List but got ${responseBody.runtimeType}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load users. Status Code: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error fetching users: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _filterUsers(String query) {
|
||||||
|
print("Filtering users...");
|
||||||
|
setState(() {
|
||||||
|
if (query.isEmpty) {
|
||||||
|
_filteredUsers = List.from(_users);
|
||||||
|
} else {
|
||||||
|
_filteredUsers = _users.where((user) {
|
||||||
|
List<String> searchFields = [
|
||||||
|
"${user.firstName} ${user.lastName}".toLowerCase(),
|
||||||
|
user.email.toLowerCase() ?? "",
|
||||||
|
user.userId.toLowerCase() ?? "",
|
||||||
|
user.travellerId ?? "",
|
||||||
|
user.mobileNo ?? "",
|
||||||
|
user.alternateMobileNo ?? ""
|
||||||
|
];
|
||||||
|
|
||||||
|
return searchFields.any((field) => field.contains(query.toLowerCase()));
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
print("Filtered Users:");
|
||||||
|
for (var user in _filteredUsers) {
|
||||||
|
print("${user.firstName} ${user.lastName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
fetchUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
width: 400, // Adjust width as needed
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min, // Ensures content doesn't expand unnecessarily
|
||||||
|
children: [
|
||||||
|
Text("Please Select User", style: TextStyle(fontSize: 14)),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
|
||||||
|
// Search Field
|
||||||
|
TextField(
|
||||||
|
controller: _searchController,
|
||||||
|
onChanged: (query) {
|
||||||
|
setState(() {
|
||||||
|
_showTravellerForm = false;
|
||||||
|
});
|
||||||
|
_filterUsers(query);
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search for a user",
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
prefixIcon: Icon(Icons.search),
|
||||||
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(color: Colors.blueAccent, width: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
if (widget.title == "Others") ...[
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("or create a new traveler", style: TextStyle(fontSize: 14, color: Color(0xFF575A74))),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_showTravellerForm = true;
|
||||||
|
_searchController.clear();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text("Create", style: TextStyle(fontSize: 14, color: Colors.blueAccent)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
SizedBox(height: 5),
|
||||||
|
|
||||||
|
// User List or Message
|
||||||
|
_searchController.text.isNotEmpty
|
||||||
|
? SizedBox(
|
||||||
|
height: 300, // Limit height to avoid overflow
|
||||||
|
child: _filteredUsers.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: Text(
|
||||||
|
"No users found",
|
||||||
|
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount: _filteredUsers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final user = _filteredUsers[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text("${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"),
|
||||||
|
subtitle: Text("ID: ${user.userId}"),
|
||||||
|
onTap: () {
|
||||||
|
String selectedUser = "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}";
|
||||||
|
setState(() {
|
||||||
|
_searchController.text = selectedUser;
|
||||||
|
});
|
||||||
|
print("Selected: $selectedUser, ID: ${user.userId}");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) : SizedBox.shrink(),
|
||||||
|
|
||||||
|
// Traveler Form
|
||||||
|
if (_showTravellerForm)
|
||||||
|
Container(
|
||||||
|
height: 300, // Set a defined height
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: TravelerForm(
|
||||||
|
formKey: _formKey,
|
||||||
|
firstNameController: TextEditingController(),
|
||||||
|
lastNameController: TextEditingController(),
|
||||||
|
emailController: TextEditingController(),
|
||||||
|
mobileController: TextEditingController(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.blueAccent,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: Colors.blueAccent, width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text("Cancel",),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blueAccent,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: Colors.blueAccent, width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
widget.onSubmit(_searchController.text);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text("Submit"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class TravelerForm extends StatefulWidget {
|
||||||
|
final TextEditingController firstNameController;
|
||||||
|
final TextEditingController lastNameController;
|
||||||
|
final TextEditingController emailController;
|
||||||
|
final TextEditingController mobileController;
|
||||||
|
final GlobalKey<FormState> formKey;
|
||||||
|
|
||||||
|
TravelerForm({
|
||||||
|
required this.formKey,
|
||||||
|
required this.firstNameController,
|
||||||
|
required this.lastNameController,
|
||||||
|
required this.emailController,
|
||||||
|
required this.mobileController,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TravelerFormState createState() => _TravelerFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TravelerFormState extends State<TravelerForm> {
|
||||||
|
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _validateForm() {
|
||||||
|
if (widget.formKey.currentState != null) {
|
||||||
|
return widget.formKey.currentState!.validate();
|
||||||
|
}
|
||||||
|
return false; // Return false when form is not initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
String? validateMobile(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Mobile number is required';
|
||||||
|
}
|
||||||
|
if (!RegExp(r'^[0-9]{10}$').hasMatch(value)) {
|
||||||
|
return 'Enter a valid 10-digit mobile number';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? validateEmail(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Email is required';
|
||||||
|
}
|
||||||
|
if (!RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$').hasMatch(value)) {
|
||||||
|
return 'Enter a valid email address';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSubmit(BuildContext context) {
|
||||||
|
bool isValid = _validateForm();
|
||||||
|
print("Form Validation Result: $isValid");
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
print("Validation Success");
|
||||||
|
_submitForm(context);
|
||||||
|
} else {
|
||||||
|
print("Validation Failed"); // This should now print if validation fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> _submitForm(BuildContext context) async {
|
||||||
|
Map<String, String> requestBody = {
|
||||||
|
"first_name": widget.firstNameController.text,
|
||||||
|
"last_name": widget.lastNameController.text,
|
||||||
|
"email": widget.emailController.text,
|
||||||
|
"mobile": widget.mobileController.text,
|
||||||
|
};
|
||||||
|
|
||||||
|
String apiUrlData = '$apiUrl/api/travellers/create';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final token = await getToken();
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
print("response1");
|
||||||
|
var response = await http.post(
|
||||||
|
Uri.parse(apiUrlData),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: jsonEncode(requestBody),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
|
||||||
|
|
||||||
|
Navigator.pop(context);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
"Traveller added successfully!",
|
||||||
|
style: TextStyle(color: Colors.white), // ✅ Set text color
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.green,),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Error: ${response.body}")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Failed to connect to server.")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
double widthFactor;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
if (sizingInfo.deviceScreenType == DeviceScreenType.desktop) {
|
||||||
|
widthFactor = 0.23;
|
||||||
|
} else if (sizingInfo.deviceScreenType == DeviceScreenType.tablet) {
|
||||||
|
widthFactor = 0.8;
|
||||||
|
} else {
|
||||||
|
widthFactor = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Form(
|
||||||
|
key: widget.formKey,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text("Create Traveler", style: TextStyle(color: Colors.black54)),
|
||||||
|
SizedBox(height: 7),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
child: Column(children: _buildFormFirstRow(isDesktop)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
widget.formKey.currentState?.reset();
|
||||||
|
},
|
||||||
|
child: Text("Clear", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => _onSubmit(context),
|
||||||
|
child: Text("Add", style: TextStyle(color: Colors.blueAccent)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildFormFirstRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
CustomTextField(
|
||||||
|
controller: widget.firstNameController,
|
||||||
|
labelText: "First Name",
|
||||||
|
validator: (value) => value!.isEmpty ? "Enter first name" : null,
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
CustomTextField(
|
||||||
|
controller: widget.lastNameController,
|
||||||
|
labelText: "Last Name",
|
||||||
|
validator: (value) => value!.isEmpty ? "Enter last name" : null,
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
CustomTextField(
|
||||||
|
controller: widget.emailController,
|
||||||
|
labelText: "Email",
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
validator: validateEmail,
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
CustomTextField(
|
||||||
|
controller: widget.mobileController,
|
||||||
|
labelText: "Mobile Number",
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
validator: validateMobile,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
680
lib/Screens/itnerary/accomodations.dart
Normal file
@ -0,0 +1,680 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
|
class AccomodationScreen extends StatefulWidget {
|
||||||
|
final Map<String, String> formData;
|
||||||
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
|
final Function(bool) onClose; // Callback function
|
||||||
|
|
||||||
|
AccomodationScreen({required this.formData, required this.updateFormData,
|
||||||
|
required this.onClose,});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AccomodationScreenState createState() => _AccomodationScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccomodationScreenState extends State<AccomodationScreen> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
final FocusNode _destinationFocusNode = FocusNode();
|
||||||
|
final FocusNode _hotelNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _checkInFocusNode = FocusNode();
|
||||||
|
final FocusNode _checkInTimeFocusNode = FocusNode();
|
||||||
|
final FocusNode _checkOutFocusNode = FocusNode();
|
||||||
|
final FocusNode _checkOutTimeFocusNode = FocusNode();
|
||||||
|
final FocusNode _commentsFocusNode = FocusNode();
|
||||||
|
|
||||||
|
late Map<String, TextEditingController> _controllers;
|
||||||
|
|
||||||
|
late TextEditingController _destinationController = TextEditingController();
|
||||||
|
late TextEditingController _hotelNameController = TextEditingController();
|
||||||
|
late TextEditingController _checkInController = TextEditingController();
|
||||||
|
late TextEditingController _checkInTimeController = TextEditingController();
|
||||||
|
late TextEditingController _checkOutController = TextEditingController();
|
||||||
|
late TextEditingController _checkOutTimeController = TextEditingController();
|
||||||
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
|
bool _destinationFocused = false;
|
||||||
|
bool _isHotelNameFocused = false;
|
||||||
|
bool _checkInFocus = false;
|
||||||
|
bool _checkInTimeFocus = false;
|
||||||
|
bool _checkOutFocus = false;
|
||||||
|
bool _checkOutTimeFocus = false;
|
||||||
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_destinationFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_destinationFocused = _destinationFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
|
||||||
|
_hotelNameFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_checkInFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_checkInFocus = _checkInFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_checkInTimeFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_checkInTimeFocus = _checkInTimeFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_checkOutFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_checkInFocus = _checkInFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_checkOutTimeFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_checkInTimeFocus = _checkOutTimeFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_commentsFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_commentsFocus = _commentsFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
List<String> fields = [
|
||||||
|
"_destination",
|
||||||
|
"_hotelName",
|
||||||
|
"_Check_In",
|
||||||
|
"_Check_In_Time",
|
||||||
|
"_Check_Out",
|
||||||
|
"_Check_Out_Time",
|
||||||
|
"_comments"
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
_destinationController =
|
||||||
|
TextEditingController(text: widget.formData["destination"] ?? "");
|
||||||
|
_hotelNameController =
|
||||||
|
TextEditingController(text: widget.formData["_hotelName"] ?? "");
|
||||||
|
_checkInController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_In"] ?? "");
|
||||||
|
_checkInTimeController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
|
||||||
|
_checkOutController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||||
|
_checkOutTimeController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
|
||||||
|
_commentsController =
|
||||||
|
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||||
|
|
||||||
|
// Save data when user types
|
||||||
|
_destinationController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Accommodation", "destination", _destinationController.text);
|
||||||
|
}); // Save data when user types
|
||||||
|
_hotelNameController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Accommodation", "_hotelName", _hotelNameController.text);
|
||||||
|
});
|
||||||
|
_checkInController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Accommodation", "_Check_In", _checkInController.text);
|
||||||
|
});
|
||||||
|
_checkInTimeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Accommodation", "_Check_In_Time", _checkInTimeController.text);
|
||||||
|
});
|
||||||
|
_checkOutController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Accommodation", "_Check_Out", _checkOutController.text);
|
||||||
|
});
|
||||||
|
_checkOutTimeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Accommodation", "_Check_Out_Time", _checkOutTimeController.text);
|
||||||
|
});
|
||||||
|
_commentsController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Accommodation", "_comments", _commentsController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_destinationFocusNode.dispose();
|
||||||
|
_destinationController.dispose();
|
||||||
|
_hotelNameController.dispose();
|
||||||
|
_checkInController.dispose();
|
||||||
|
_checkInTimeController.dispose();
|
||||||
|
_checkOutController.dispose();
|
||||||
|
_checkOutTimeController.dispose();
|
||||||
|
_commentsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
print("Close icon clicked");
|
||||||
|
widget.onClose(false);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Text("Accomodation Booking",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||||
|
SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(28.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAccomadtionForm(isDesktop) {
|
||||||
|
return [
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
children: _buildFirstRow(isDesktop),
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
children: _buildFirstRow(isDesktop),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 10), // Spacing between first and second row
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
children: _buildSecondRow(isDesktop),
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
children: _buildSecondRow(isDesktop),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 10),
|
||||||
|
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
children: _buildThirdRow(isDesktop),
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
children: _buildThirdRow(isDesktop),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Destination",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _destinationFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _destinationFocusNode,
|
||||||
|
controller: _destinationController,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Destination",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Hotel Name",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
controller: _hotelNameController,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Hotel Name",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
DateTime? _selectedCheckInDate;
|
||||||
|
TimeOfDay? _selectedCheckInTime;
|
||||||
|
|
||||||
|
Future<void> _selectCheckInDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _selectedCheckInDate != null && _selectedCheckInDate!.isAfter(today)
|
||||||
|
? _selectedCheckInDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckInDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckInDate = pickedDate;
|
||||||
|
_checkInController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectCheckInTime(BuildContext context) async {
|
||||||
|
TimeOfDay? pickedTime = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: _selectedCheckInTime ?? TimeOfDay.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedTime != null && pickedTime != _selectedCheckInTime) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckInTime = pickedTime;
|
||||||
|
// Formatting time to HH:mm (24-hour format)
|
||||||
|
final now = DateTime.now();
|
||||||
|
final formattedTime = DateFormat('HH:mm').format(
|
||||||
|
DateTime(now.year, now.month, now.day, pickedTime.hour,
|
||||||
|
pickedTime.minute),
|
||||||
|
);
|
||||||
|
_checkInTimeController.text = formattedTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//-------------------------------Check-In End
|
||||||
|
|
||||||
|
|
||||||
|
DateTime? _selectedCheckOutDate;
|
||||||
|
TimeOfDay? _selectedCheckOutTime;
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_checkOutController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutTime(BuildContext context) async {
|
||||||
|
TimeOfDay? pickedTime = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedTime != null && pickedTime != _selectedCheckOutTime) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutTime = pickedTime;
|
||||||
|
// Formatting time to HH:mm (24-hour format)
|
||||||
|
final now = DateTime.now();
|
||||||
|
final formattedTime = DateFormat('HH:mm').format(
|
||||||
|
DateTime(now.year, now.month, now.day, pickedTime.hour,
|
||||||
|
pickedTime.minute),
|
||||||
|
);
|
||||||
|
_checkOutTimeController.text = formattedTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Check-in*",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _checkInFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckInDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _checkInFocusNode,
|
||||||
|
controller: _checkInController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Time (Check-in)",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _checkInTimeFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckInTime(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _checkInTimeFocusNode,
|
||||||
|
controller: _checkInTimeController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Time",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon:
|
||||||
|
Icon(Icons.access_time, size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Check-out*",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _checkOutFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _checkOutFocusNode,
|
||||||
|
controller: _checkOutController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Time (Check-out)",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _checkOutTimeFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutTime(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _checkOutTimeFocusNode,
|
||||||
|
controller: _checkOutTimeController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Time",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon:
|
||||||
|
Icon(Icons.access_time, size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comments",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width: isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _commentsFocusNode,
|
||||||
|
controller: _commentsController,
|
||||||
|
maxLines: 6,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _handleAction(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
// Close Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog or screen
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Close",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement save logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Save Changes",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
715
lib/Screens/itnerary/bus.dart
Normal file
@ -0,0 +1,715 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
|
class BusScreen extends StatefulWidget {
|
||||||
|
final Map<String, String> formData;
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
|
final Function(bool) onClose;
|
||||||
|
|
||||||
|
BusScreen({required this.formData, required this.updateFormData,
|
||||||
|
required this.onClose, this.apiData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_BusScreenState createState() => _BusScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BusScreenState extends State<BusScreen> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
Map<String, String?> selectedValues = {};
|
||||||
|
|
||||||
|
final FocusNode _tripTypeFocusNode = FocusNode();
|
||||||
|
final FocusNode _hotelNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _fromFocusNode = FocusNode();
|
||||||
|
final FocusNode _toFocusNode = FocusNode();
|
||||||
|
final FocusNode _dateFocusNode = FocusNode();
|
||||||
|
final FocusNode _timeFocusNode = FocusNode();
|
||||||
|
final FocusNode _commentsFocusNode = FocusNode();
|
||||||
|
|
||||||
|
late Map<String, TextEditingController> _controllers;
|
||||||
|
|
||||||
|
late TextEditingController _tripTypeController = TextEditingController();
|
||||||
|
late TextEditingController _hotelNameController = TextEditingController();
|
||||||
|
late TextEditingController _fromController = TextEditingController();
|
||||||
|
late TextEditingController _toController = TextEditingController();
|
||||||
|
late TextEditingController _dateController = TextEditingController();
|
||||||
|
late TextEditingController _timeController = TextEditingController();
|
||||||
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
|
bool _tripTypeFocused = false;
|
||||||
|
bool _isHotelNameFocused = false;
|
||||||
|
bool _fromFocus = false;
|
||||||
|
bool _toFocus = false;
|
||||||
|
bool _dateFocus = false;
|
||||||
|
bool _timeFocus = false;
|
||||||
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_tripTypeFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_tripTypeFocused = _tripTypeFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
|
||||||
|
_hotelNameFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_fromFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_fromFocus = _fromFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_toFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_toFocus = _toFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_dateFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_dateFocus = _fromFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_timeFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_timeFocus = _timeFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_commentsFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_commentsFocus = _commentsFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_tripTypeController =
|
||||||
|
TextEditingController(text: widget.formData["tripType"] ?? "");
|
||||||
|
_hotelNameController =
|
||||||
|
TextEditingController(text: widget.formData["_hotelName"] ?? "");
|
||||||
|
_fromController =
|
||||||
|
TextEditingController(text: widget.formData["_from"] ?? "");
|
||||||
|
_toController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
|
||||||
|
_dateController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||||
|
_timeController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
|
||||||
|
_commentsController =
|
||||||
|
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||||
|
|
||||||
|
// Save data when user types
|
||||||
|
_tripTypeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_tripType", _tripTypeController.text);
|
||||||
|
}); // Save data when user types
|
||||||
|
_hotelNameController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_hotelName", _hotelNameController.text);
|
||||||
|
});
|
||||||
|
_fromController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_from", _fromController.text);
|
||||||
|
});
|
||||||
|
_toController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_Check_In_Time", _toController.text);
|
||||||
|
});
|
||||||
|
_dateController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_Check_Out", _dateController.text);
|
||||||
|
});
|
||||||
|
_timeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_Check_Out_Time", _timeController.text);
|
||||||
|
});
|
||||||
|
_commentsController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_comments", _commentsController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tripTypeFocusNode.dispose();
|
||||||
|
_tripTypeController.dispose();
|
||||||
|
_hotelNameController.dispose();
|
||||||
|
_fromController.dispose();
|
||||||
|
_toController.dispose();
|
||||||
|
_dateController.dispose();
|
||||||
|
_timeController.dispose();
|
||||||
|
_commentsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.onClose(false);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Bus Booking List",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||||
|
SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(28.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||||
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
|
return [
|
||||||
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Widget>> rowBuilders = [
|
||||||
|
// _builClassType(isDesktop),
|
||||||
|
_buildSecondRow(isDesktop)
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// ...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
|
// Iterate over rowBuilders and wrap each in a responsive container
|
||||||
|
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
|
// Actions row remains a Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Trip Type",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
|
||||||
|
isDesktop ? Row(children: _buildTripType(isDesktop)
|
||||||
|
) :
|
||||||
|
Column(
|
||||||
|
children: _buildTripType(isDesktop)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
||||||
|
|
||||||
|
|
||||||
|
widget.updateFormData("Flight", "trip_type", newValue ?? "");
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _builClassType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Class *",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: TextField(
|
||||||
|
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// labelText: "Select Class",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
|
||||||
|
|
||||||
|
DateTime? _selectedCheckOutDate;
|
||||||
|
TimeOfDay? _selectedCheckOutTime;
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutTime(BuildContext context) async {
|
||||||
|
TimeOfDay? pickedTime = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedTime != null && pickedTime != _selectedCheckOutTime) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutTime = pickedTime;
|
||||||
|
// Formatting time to HH:mm (24-hour format)
|
||||||
|
final now = DateTime.now();
|
||||||
|
final formattedTime = DateFormat('HH:mm').format(
|
||||||
|
DateTime(now.year, now.month, now.day, pickedTime.hour,
|
||||||
|
pickedTime.minute),
|
||||||
|
);
|
||||||
|
_timeController.text = formattedTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"From",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _fromFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _fromFocusNode,
|
||||||
|
controller: _fromController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "From",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"To",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _toFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _toFocusNode,
|
||||||
|
controller: _toController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "To",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _dateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Time",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _timeFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutTime(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _timeFocusNode,
|
||||||
|
controller: _timeController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Time",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon:
|
||||||
|
Icon(Icons.access_time, size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comments",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width: isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _commentsFocusNode,
|
||||||
|
controller: _commentsController,
|
||||||
|
maxLines: 6,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _handleAction(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
// Close Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog or screen
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Close",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement save logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Save Changes",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
1046
lib/Screens/itnerary/flights.dart
Normal file
1171
lib/Screens/itnerary/forex.dart
Normal file
615
lib/Screens/itnerary/insurance.dart
Normal file
@ -0,0 +1,615 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
|
class InsuranceScreen extends StatefulWidget {
|
||||||
|
final Map<String, String> formData;
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
|
final Function(bool) onClose;
|
||||||
|
|
||||||
|
InsuranceScreen({required this.formData, required this.updateFormData,
|
||||||
|
required this.onClose, required this.apiData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_InsuranceScreenState createState() => _InsuranceScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InsuranceScreenState extends State<InsuranceScreen> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
Map<String, String?> selectedValues = {};
|
||||||
|
|
||||||
|
final FocusNode _tripTypeFocusNode = FocusNode();
|
||||||
|
final FocusNode _hotelNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _fromFocusNode = FocusNode();
|
||||||
|
final FocusNode _toFocusNode = FocusNode();
|
||||||
|
final FocusNode _dateFocusNode = FocusNode();
|
||||||
|
final FocusNode _timeFocusNode = FocusNode();
|
||||||
|
final FocusNode _commentsFocusNode = FocusNode();
|
||||||
|
|
||||||
|
late Map<String, TextEditingController> _controllers;
|
||||||
|
|
||||||
|
late TextEditingController _tripTypeController = TextEditingController();
|
||||||
|
late TextEditingController _startdateController = TextEditingController();
|
||||||
|
late TextEditingController _endDateController = TextEditingController();
|
||||||
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
|
bool _isHotelNameFocused = false;
|
||||||
|
bool _dateFocus = false;
|
||||||
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
|
||||||
|
String? selectedTripType;
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// _tripTypeFocusNode.addListener(() {
|
||||||
|
// setState(() {
|
||||||
|
// _tripTypeFocused = _tripTypeFocusNode.hasFocus;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
_hotelNameFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_dateFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_dateFocus = _fromFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
_commentsFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_commentsFocus = _commentsFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_tripTypeController =
|
||||||
|
TextEditingController(text: widget.formData["tripType"] ?? "");
|
||||||
|
_startdateController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||||
|
_commentsController =
|
||||||
|
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||||
|
|
||||||
|
// Save data when user types
|
||||||
|
_tripTypeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Insurance", "_tripType", _tripTypeController.text);
|
||||||
|
}); // Save data when user types
|
||||||
|
|
||||||
|
_startdateController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Insurance", "_Check_Out", _startdateController.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
_commentsController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Insurance", "_comments", _commentsController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||||||
|
node.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
updateState(node.hasFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tripTypeFocusNode.dispose();
|
||||||
|
_tripTypeController.dispose();
|
||||||
|
_startdateController.dispose();
|
||||||
|
_commentsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.onClose(false);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Insurance Booking List",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||||
|
SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(28.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||||
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
|
return [
|
||||||
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Widget>> rowBuilders = [
|
||||||
|
// _builClassType(isDesktop),
|
||||||
|
_buildSecondRow(isDesktop)
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
|
// Iterate over rowBuilders and wrap each in a responsive container
|
||||||
|
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
|
// Actions row remains a Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Insurance Type",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
|
||||||
|
isDesktop ? Row(children: _buildTripType(isDesktop)
|
||||||
|
) :
|
||||||
|
Column(
|
||||||
|
children: _buildTripType(isDesktop)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['insurance_type_of_insurance'] ?? [];
|
||||||
|
// selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
print(selectedPurpose);
|
||||||
|
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _builClassType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Class *",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: TextField(
|
||||||
|
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// labelText: "Select Class",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
|
||||||
|
|
||||||
|
DateTime? _selectedCheckOutDate;
|
||||||
|
TimeOfDay? _selectedCheckOutTime;
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_startdateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> _selectEndCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_endDateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Start Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _startdateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"End Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectEndCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _endDateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comments",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width: isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _commentsFocusNode,
|
||||||
|
controller: _commentsController,
|
||||||
|
maxLines: 6,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _handleAction(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
// Close Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog or screen
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Close",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement save logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Save Changes",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
615
lib/Screens/itnerary/miscellaneous.dart
Normal file
@ -0,0 +1,615 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
|
class MiscellaneousScreen extends StatefulWidget {
|
||||||
|
final Map<String, String> formData;
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
|
final Function(bool) onClose;
|
||||||
|
|
||||||
|
MiscellaneousScreen({required this.formData, required this.updateFormData,
|
||||||
|
required this.onClose, required this.apiData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MiscellaneousScreenState createState() => _MiscellaneousScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
Map<String, String?> selectedValues = {};
|
||||||
|
|
||||||
|
final FocusNode _tripTypeFocusNode = FocusNode();
|
||||||
|
final FocusNode _hotelNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _fromFocusNode = FocusNode();
|
||||||
|
final FocusNode _toFocusNode = FocusNode();
|
||||||
|
final FocusNode _dateFocusNode = FocusNode();
|
||||||
|
final FocusNode _timeFocusNode = FocusNode();
|
||||||
|
final FocusNode _commentsFocusNode = FocusNode();
|
||||||
|
|
||||||
|
late Map<String, TextEditingController> _controllers;
|
||||||
|
|
||||||
|
late TextEditingController _tripTypeController = TextEditingController();
|
||||||
|
late TextEditingController _startdateController = TextEditingController();
|
||||||
|
late TextEditingController _endDateController = TextEditingController();
|
||||||
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
|
bool _isHotelNameFocused = false;
|
||||||
|
bool _dateFocus = false;
|
||||||
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
|
||||||
|
String? selectedTripType;
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// _tripTypeFocusNode.addListener(() {
|
||||||
|
// setState(() {
|
||||||
|
// _tripTypeFocused = _tripTypeFocusNode.hasFocus;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
_hotelNameFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_dateFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_dateFocus = _fromFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
_commentsFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_commentsFocus = _commentsFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_tripTypeController =
|
||||||
|
TextEditingController(text: widget.formData["tripType"] ?? "");
|
||||||
|
_startdateController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||||
|
_commentsController =
|
||||||
|
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||||
|
|
||||||
|
// Save data when user types
|
||||||
|
_tripTypeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Miscellaneous", "_tripType", _tripTypeController.text);
|
||||||
|
}); // Save data when user types
|
||||||
|
|
||||||
|
_startdateController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Miscellaneous", "_Check_Out", _startdateController.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
_commentsController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Miscellaneous", "_comments", _commentsController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||||||
|
node.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
updateState(node.hasFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tripTypeFocusNode.dispose();
|
||||||
|
_tripTypeController.dispose();
|
||||||
|
_startdateController.dispose();
|
||||||
|
_commentsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.onClose(false);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Miscellaneous Booking List",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||||
|
SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(28.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||||
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
|
return [
|
||||||
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Widget>> rowBuilders = [
|
||||||
|
// _builClassType(isDesktop),
|
||||||
|
_buildSecondRow(isDesktop)
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
|
// // Iterate over rowBuilders and wrap each in a responsive container
|
||||||
|
// ...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
|
// Actions row remains a Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Special Type",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
|
||||||
|
isDesktop ? Row(children: _buildTripType(isDesktop)
|
||||||
|
) :
|
||||||
|
Column(
|
||||||
|
children: _buildTripType(isDesktop)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['miscellaneous_special_request'] ?? [];
|
||||||
|
// selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedSpecialType = dropdownItems.isNotEmpty ? dropdownItems.first.value : "No options";
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
|
value: selectedSpecialType,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedSpecialType = newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
print(selectedSpecialType);
|
||||||
|
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _builClassType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedSpecialType = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Class *",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedSpecialType,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedSpecialType = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: TextField(
|
||||||
|
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// labelText: "Select Class",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
|
||||||
|
|
||||||
|
DateTime? _selectedCheckOutDate;
|
||||||
|
TimeOfDay? _selectedCheckOutTime;
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_startdateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> _selectEndCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_endDateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Start Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _startdateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"End Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectEndCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _endDateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comments",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width: isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _commentsFocusNode,
|
||||||
|
controller: _commentsController,
|
||||||
|
maxLines: 6,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _handleAction(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
// Close Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog or screen
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Close",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement save logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Save Changes",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
994
lib/Screens/itnerary/sample.dart
Normal file
@ -0,0 +1,994 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
|
class ForexScreen extends StatefulWidget {
|
||||||
|
final Map<String, String> formData;
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
|
final Function(bool) onClose;
|
||||||
|
|
||||||
|
ForexScreen({required this.formData, required this.updateFormData,
|
||||||
|
required this.onClose, this.apiData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ForexScreenState createState() => _ForexScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ForexScreenState extends State<ForexScreen> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
Map<String, String?> selectedValues = {};
|
||||||
|
bool isChecked = false; // State variable for checkbox
|
||||||
|
|
||||||
|
final FocusNode _tripTypeFocusNode = FocusNode();
|
||||||
|
final FocusNode _hotelNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _fromFocusNode = FocusNode();
|
||||||
|
final FocusNode _toFocusNode = FocusNode();
|
||||||
|
final FocusNode _dateFocusNode = FocusNode();
|
||||||
|
final FocusNode _timeFocusNode = FocusNode();
|
||||||
|
final FocusNode _commentsFocusNode = FocusNode();
|
||||||
|
|
||||||
|
late Map<String, TextEditingController> _controllers;
|
||||||
|
|
||||||
|
late TextEditingController _tripTypeController = TextEditingController();
|
||||||
|
late TextEditingController _hotelNameController = TextEditingController();
|
||||||
|
late TextEditingController _fromController = TextEditingController();
|
||||||
|
late TextEditingController _toController = TextEditingController();
|
||||||
|
late TextEditingController _dateController = TextEditingController();
|
||||||
|
late TextEditingController _endDateController = TextEditingController();
|
||||||
|
late TextEditingController _timeController = TextEditingController();
|
||||||
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
|
bool _tripTypeFocused = false;
|
||||||
|
bool _isHotelNameFocused = false;
|
||||||
|
bool _fromFocus = false;
|
||||||
|
bool _toFocus = false;
|
||||||
|
bool _dateFocus = false;
|
||||||
|
bool _timeFocus = false;
|
||||||
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_tripTypeFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_tripTypeFocused = _tripTypeFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
|
||||||
|
_hotelNameFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_fromFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_fromFocus = _fromFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_toFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_toFocus = _toFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_dateFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_dateFocus = _fromFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_timeFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_timeFocus = _timeFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_commentsFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_commentsFocus = _commentsFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_tripTypeController =
|
||||||
|
TextEditingController(text: widget.formData["tripType"] ?? "");
|
||||||
|
_hotelNameController =
|
||||||
|
TextEditingController(text: widget.formData["_hotelName"] ?? "");
|
||||||
|
_fromController =
|
||||||
|
TextEditingController(text: widget.formData["_from"] ?? "");
|
||||||
|
_toController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
|
||||||
|
_dateController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||||
|
_endDateController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_In"] ?? "");
|
||||||
|
_timeController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
|
||||||
|
_commentsController =
|
||||||
|
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||||
|
|
||||||
|
// Save data when user types
|
||||||
|
_tripTypeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_tripType", _tripTypeController.text);
|
||||||
|
}); // Save data when user types
|
||||||
|
_hotelNameController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_hotelName", _hotelNameController.text);
|
||||||
|
});
|
||||||
|
_fromController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_from", _fromController.text);
|
||||||
|
});
|
||||||
|
_toController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_Check_In_Time", _toController.text);
|
||||||
|
});
|
||||||
|
_dateController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_Check_Out", _dateController.text);
|
||||||
|
});
|
||||||
|
_endDateController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_Check_In", _endDateController.text);
|
||||||
|
});
|
||||||
|
_timeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_Check_Out_Time", _timeController.text);
|
||||||
|
});
|
||||||
|
_commentsController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Bus", "_comments", _commentsController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tripTypeFocusNode.dispose();
|
||||||
|
_tripTypeController.dispose();
|
||||||
|
_hotelNameController.dispose();
|
||||||
|
_fromController.dispose();
|
||||||
|
_toController.dispose();
|
||||||
|
_dateController.dispose();
|
||||||
|
_endDateController.dispose();
|
||||||
|
_timeController.dispose();
|
||||||
|
_commentsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.onClose(false);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Forex List",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||||
|
SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(28.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||||
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
|
return [
|
||||||
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Widget>> rowBuilders = [
|
||||||
|
_builClassType(isDesktop),
|
||||||
|
_buildSecondRow(isDesktop)
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
|
|
||||||
|
// Iterate over rowBuilders and wrap each in a responsive container
|
||||||
|
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildCardDetailsRow(isDesktop)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildFprexCard(isDesktop)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
|
// Actions row remains a Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
|
DateTime? _selectedCheckOutDate;
|
||||||
|
DateTime? _selectedEndDate;
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<void> _selectForexEndDate(BuildContext context) async {
|
||||||
|
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _selectedEndDate != null && _selectedEndDate!.isAfter(today)
|
||||||
|
? _selectedEndDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedEndDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedEndDate = pickedDate;
|
||||||
|
_endDateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Forex Start Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _dateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Forex End Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectForexEndDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _endDateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
||||||
|
|
||||||
|
|
||||||
|
widget.updateFormData("Flight", "trip_type", newValue ?? "");
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _builClassType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Class *",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: TextField(
|
||||||
|
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// labelText: "Select Class",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Transport",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _fromFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _fromFocusNode,
|
||||||
|
controller: _fromController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "From",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Accomodation",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _toFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _toFocusNode,
|
||||||
|
controller: _toController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "To",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Telephone",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _fromFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _fromFocusNode,
|
||||||
|
controller: _fromController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "From",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Other Expenses",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _toFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
color:Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
"Rs ",
|
||||||
|
// focusNode: _toFocusNode,
|
||||||
|
// controller: _toController,
|
||||||
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600,color: Color(0xFF575A74)),
|
||||||
|
// decoration: const InputDecoration(
|
||||||
|
// labelText: "To",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildCardDetailsRow(bool isDesktop) {
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
return [
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Card Number*",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _fromFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _fromFocusNode,
|
||||||
|
controller: _fromController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "From",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Currency*",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _toFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Card*",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _toFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _toFocusNode,
|
||||||
|
controller: _toController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "To",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Cash*",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _fromFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _fromFocusNode,
|
||||||
|
controller: _fromController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "From",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Delivery Location",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width: isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _commentsFocusNode,
|
||||||
|
controller: _commentsController,
|
||||||
|
maxLines: 3,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildFprexCard(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: isChecked,
|
||||||
|
side: BorderSide(
|
||||||
|
color: Colors.grey, // Change border color
|
||||||
|
width: 1, // Adjust thickness
|
||||||
|
),
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
setState(() {
|
||||||
|
isChecked = value!;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Check If You Don't Have a forex Account",
|
||||||
|
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _handleAction(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
// Close Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog or screen
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Close",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement save logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Save Changes",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
798
lib/Screens/itnerary/taxi.dart
Normal file
@ -0,0 +1,798 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
|
class TaxiScreen extends StatefulWidget {
|
||||||
|
final Map<String, String> formData;
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
|
final Function(bool) onClose;
|
||||||
|
|
||||||
|
TaxiScreen({required this.formData, required this.updateFormData,
|
||||||
|
required this.onClose, this.apiData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TaxiScreenState createState() => _TaxiScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TaxiScreenState extends State<TaxiScreen> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
Map<String, String?> selectedValues = {};
|
||||||
|
|
||||||
|
final FocusNode _tripTypeFocusNode = FocusNode();
|
||||||
|
final FocusNode _hotelNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _fromFocusNode = FocusNode();
|
||||||
|
final FocusNode _toFocusNode = FocusNode();
|
||||||
|
final FocusNode _dateFocusNode = FocusNode();
|
||||||
|
final FocusNode _timeFocusNode = FocusNode();
|
||||||
|
final FocusNode _commentsFocusNode = FocusNode();
|
||||||
|
|
||||||
|
late Map<String, TextEditingController> _controllers;
|
||||||
|
|
||||||
|
late TextEditingController _tripTypeController = TextEditingController();
|
||||||
|
late TextEditingController _hotelNameController = TextEditingController();
|
||||||
|
late TextEditingController _fromController = TextEditingController();
|
||||||
|
late TextEditingController _toController = TextEditingController();
|
||||||
|
late TextEditingController _dateController = TextEditingController();
|
||||||
|
late TextEditingController _timeController = TextEditingController();
|
||||||
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
|
bool _tripTypeFocused = false;
|
||||||
|
bool _isHotelNameFocused = false;
|
||||||
|
bool _fromFocus = false;
|
||||||
|
bool _toFocus = false;
|
||||||
|
bool _dateFocus = false;
|
||||||
|
bool _timeFocus = false;
|
||||||
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
|
||||||
|
_addFocusListener(_tripTypeFocusNode, (focus) => _tripTypeFocused = focus);
|
||||||
|
_addFocusListener(_hotelNameFocusNode, (focus) => _isHotelNameFocused = focus);
|
||||||
|
_addFocusListener(_fromFocusNode, (focus) => _fromFocus = focus);
|
||||||
|
_addFocusListener(_toFocusNode, (focus) => _toFocus = focus);
|
||||||
|
_addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus);
|
||||||
|
_addFocusListener(_timeFocusNode, (focus) => _timeFocus = focus);
|
||||||
|
_addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
|
||||||
|
|
||||||
|
// _commentsFocusNode.addListener(() {
|
||||||
|
// setState(() {
|
||||||
|
// _commentsFocus = _commentsFocusNode.hasFocus;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_tripTypeController =
|
||||||
|
TextEditingController(text: widget.formData["tripType"] ?? "");
|
||||||
|
_hotelNameController =
|
||||||
|
TextEditingController(text: widget.formData["_hotelName"] ?? "");
|
||||||
|
_fromController =
|
||||||
|
TextEditingController(text: widget.formData["_from"] ?? "");
|
||||||
|
_toController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
|
||||||
|
_dateController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||||
|
_timeController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
|
||||||
|
_commentsController =
|
||||||
|
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||||
|
|
||||||
|
// Save data when user types
|
||||||
|
List<TextEditingController> controllers = [
|
||||||
|
_tripTypeController, _hotelNameController, _fromController, _toController, _dateController,
|
||||||
|
_timeController, _commentsController
|
||||||
|
];
|
||||||
|
|
||||||
|
List<String> keys = [
|
||||||
|
"_tripType", "_hotelName", "_from", "_Check_In_Time", "_Check_Out",
|
||||||
|
"_Check_Out_Time", "_comments"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (int i = 0; i < controllers.length; i++) {
|
||||||
|
controllers[i].addListener(() {
|
||||||
|
widget.updateFormData("Flight", keys[i], controllers[i].text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||||||
|
node.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
updateState(node.hasFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tripTypeFocusNode.dispose();
|
||||||
|
_tripTypeController.dispose();
|
||||||
|
_hotelNameController.dispose();
|
||||||
|
_fromController.dispose();
|
||||||
|
_toController.dispose();
|
||||||
|
_dateController.dispose();
|
||||||
|
_timeController.dispose();
|
||||||
|
_commentsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.onClose(false);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Taxi Booking List",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||||
|
SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(28.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||||
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
|
return [
|
||||||
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Widget>> rowBuilders = [
|
||||||
|
// _builClassType(isDesktop),
|
||||||
|
_buildSecondRow(isDesktop)
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// Iterate over rowBuilders and wrap each in a responsive container
|
||||||
|
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
|
// Actions row remains a Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['taxt_car_type'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Taxi Required For",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
|
||||||
|
isDesktop ? Row(children: _buildTripType(isDesktop)
|
||||||
|
) :
|
||||||
|
Column(
|
||||||
|
children: _buildTripType(isDesktop)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Number of Passenger",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _fromFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _fromFocusNode,
|
||||||
|
controller: _fromController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Destination",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Car Type",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _toFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
||||||
|
|
||||||
|
|
||||||
|
widget.updateFormData("Flight", "trip_type", newValue ?? "");
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
SizedBox.shrink()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['taxi_car_required_for'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
||||||
|
|
||||||
|
|
||||||
|
widget.updateFormData("Flight", "trip_type", newValue ?? "");
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _builClassType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Class *",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: TextField(
|
||||||
|
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// labelText: "Select Class",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
|
||||||
|
|
||||||
|
DateTime? _selectedCheckOutDate;
|
||||||
|
TimeOfDay? _selectedCheckOutTime;
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutTime(BuildContext context) async {
|
||||||
|
TimeOfDay? pickedTime = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedTime != null && pickedTime != _selectedCheckOutTime) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutTime = pickedTime;
|
||||||
|
// Formatting time to HH:mm (24-hour format)
|
||||||
|
final now = DateTime.now();
|
||||||
|
final formattedTime = DateFormat('HH:mm').format(
|
||||||
|
DateTime(now.year, now.month, now.day, pickedTime.hour,
|
||||||
|
pickedTime.minute),
|
||||||
|
);
|
||||||
|
_timeController.text = formattedTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Destination",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _fromFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _fromFocusNode,
|
||||||
|
controller: _fromController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Destination",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Location of Pickup",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _toFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _toFocusNode,
|
||||||
|
controller: _toController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Location of Pickup",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _dateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Time",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _timeFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutTime(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _timeFocusNode,
|
||||||
|
controller: _timeController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Time",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon:
|
||||||
|
Icon(Icons.access_time, size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comments",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width: isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _commentsFocusNode,
|
||||||
|
controller: _commentsController,
|
||||||
|
maxLines: 6,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _handleAction(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
// Close Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog or screen
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Close",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement save logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Save Changes",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
726
lib/Screens/itnerary/train.dart
Normal file
@ -0,0 +1,726 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
|
class TrainScreen extends StatefulWidget {
|
||||||
|
final Map<String, String> formData;
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
|
final Function(bool) onClose;
|
||||||
|
|
||||||
|
TrainScreen({required this.formData, required this.updateFormData,
|
||||||
|
required this.onClose, this.apiData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_BusScreenState createState() => _BusScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BusScreenState extends State<TrainScreen> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
Map<String, String?> selectedValues = {};
|
||||||
|
|
||||||
|
final FocusNode _trainNoFocusNode = FocusNode();
|
||||||
|
final FocusNode _hotelNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _fromFocusNode = FocusNode();
|
||||||
|
final FocusNode _toFocusNode = FocusNode();
|
||||||
|
final FocusNode _dateFocusNode = FocusNode();
|
||||||
|
final FocusNode _timeFocusNode = FocusNode();
|
||||||
|
final FocusNode _commentsFocusNode = FocusNode();
|
||||||
|
|
||||||
|
late Map<String, TextEditingController> _controllers;
|
||||||
|
|
||||||
|
late TextEditingController _trainNoController = TextEditingController();
|
||||||
|
late TextEditingController _hotelNameController = TextEditingController();
|
||||||
|
late TextEditingController _fromController = TextEditingController();
|
||||||
|
late TextEditingController _toController = TextEditingController();
|
||||||
|
late TextEditingController _dateController = TextEditingController();
|
||||||
|
late TextEditingController _timeController = TextEditingController();
|
||||||
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
|
bool _trainNoFocused = false;
|
||||||
|
bool _isHotelNameFocused = false;
|
||||||
|
bool _fromFocus = false;
|
||||||
|
bool _toFocus = false;
|
||||||
|
bool _dateFocus = false;
|
||||||
|
bool _timeFocus = false;
|
||||||
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_trainNoFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_trainNoFocused = _trainNoFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_hotelNameFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_fromFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_fromFocus = _fromFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_toFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_toFocus = _toFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_dateFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_dateFocus = _fromFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_timeFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_timeFocus = _timeFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_commentsFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_commentsFocus = _commentsFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_trainNoController =
|
||||||
|
TextEditingController(text: widget.formData["trainNo"] ?? "");
|
||||||
|
_hotelNameController =
|
||||||
|
TextEditingController(text: widget.formData["_hotelName"] ?? "");
|
||||||
|
_fromController =
|
||||||
|
TextEditingController(text: widget.formData["_from"] ?? "");
|
||||||
|
_toController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
|
||||||
|
_dateController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||||
|
_timeController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
|
||||||
|
_commentsController =
|
||||||
|
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||||
|
|
||||||
|
// Save data when user types
|
||||||
|
_trainNoController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Train", "_trainNo", _trainNoController.text);
|
||||||
|
}); // Save data when user types
|
||||||
|
_hotelNameController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Train", "_hotelName", _hotelNameController.text);
|
||||||
|
});
|
||||||
|
_fromController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Train", "_from", _fromController.text);
|
||||||
|
});
|
||||||
|
_toController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Train", "_Check_In_Time", _toController.text);
|
||||||
|
});
|
||||||
|
_dateController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Train", "_Check_Out", _dateController.text);
|
||||||
|
});
|
||||||
|
_timeController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Train", "_Check_Out_Time", _timeController.text);
|
||||||
|
});
|
||||||
|
_commentsController.addListener(() {
|
||||||
|
widget.updateFormData(
|
||||||
|
"Train", "_comments", _commentsController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_trainNoFocusNode.dispose();
|
||||||
|
_trainNoController.dispose();
|
||||||
|
_hotelNameController.dispose();
|
||||||
|
_fromController.dispose();
|
||||||
|
_toController.dispose();
|
||||||
|
_dateController.dispose();
|
||||||
|
_timeController.dispose();
|
||||||
|
_commentsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.onClose(false);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Train Booking List",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||||
|
SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(28.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||||
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
|
return [
|
||||||
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Widget>> rowBuilders = [
|
||||||
|
_builClassType(isDesktop),
|
||||||
|
_buildSecondRow(isDesktop)
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
|
// Iterate over rowBuilders and wrap each in a responsive container
|
||||||
|
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
|
// Actions row remains a Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Train Number",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
|
||||||
|
isDesktop ? Row(children: _buildTripType(isDesktop)
|
||||||
|
) :
|
||||||
|
Column(
|
||||||
|
children: _buildTripType(isDesktop)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _trainNoFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _trainNoFocusNode,
|
||||||
|
controller: _trainNoController,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Train number",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: DropdownButtonFormField<String>(
|
||||||
|
// focusNode: _trainNoFocusNode, // Assign the correct focus node
|
||||||
|
// value: selectedPurpose,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(
|
||||||
|
// horizontal: 10), // Proper padding
|
||||||
|
// ),
|
||||||
|
// onChanged: purposeList.isNotEmpty
|
||||||
|
// ? (newValue) {
|
||||||
|
// setState(() {
|
||||||
|
// selectedPurpose = newValue;
|
||||||
|
// });
|
||||||
|
// print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// widget.updateFormData("Flight", "trip_type", newValue ?? "");
|
||||||
|
// }
|
||||||
|
// : null,
|
||||||
|
//
|
||||||
|
// items: dropdownItems,
|
||||||
|
// ),
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _builClassType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['train_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Class *",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: TextField(
|
||||||
|
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// labelText: "Select Class",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
|
||||||
|
|
||||||
|
DateTime? _selectedCheckOutDate;
|
||||||
|
TimeOfDay? _selectedCheckOutTime;
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutTime(BuildContext context) async {
|
||||||
|
TimeOfDay? pickedTime = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedTime != null && pickedTime != _selectedCheckOutTime) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutTime = pickedTime;
|
||||||
|
// Formatting time to HH:mm (24-hour format)
|
||||||
|
final now = DateTime.now();
|
||||||
|
final formattedTime = DateFormat('HH:mm').format(
|
||||||
|
DateTime(now.year, now.month, now.day, pickedTime.hour,
|
||||||
|
pickedTime.minute),
|
||||||
|
);
|
||||||
|
_timeController.text = formattedTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"From",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _fromFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _fromFocusNode,
|
||||||
|
controller: _fromController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "From",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"To",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _toFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _toFocusNode,
|
||||||
|
controller: _toController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "To",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _dateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Time",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _timeFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutTime(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _timeFocusNode,
|
||||||
|
controller: _timeController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Time",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon:
|
||||||
|
Icon(Icons.access_time, size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comments",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width: isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _commentsFocusNode,
|
||||||
|
controller: _commentsController,
|
||||||
|
maxLines: 6,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _handleAction(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
// Close Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog or screen
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Close",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement save logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Save Changes",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
627
lib/Screens/itnerary/visa.dart
Normal file
@ -0,0 +1,627 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
|
class VisaScreen extends StatefulWidget {
|
||||||
|
final Map<String, String> formData;
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
|
final Function(bool) onClose;
|
||||||
|
|
||||||
|
VisaScreen({required this.formData, required this.updateFormData,
|
||||||
|
required this.onClose, this.apiData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_VisaScreenState createState() => _VisaScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VisaScreenState extends State<VisaScreen> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
Map<String, String?> selectedValues = {};
|
||||||
|
|
||||||
|
final FocusNode _tripTypeFocusNode = FocusNode();
|
||||||
|
final FocusNode _hotelNameFocusNode = FocusNode();
|
||||||
|
final FocusNode _fromFocusNode = FocusNode();
|
||||||
|
final FocusNode _toFocusNode = FocusNode();
|
||||||
|
final FocusNode _dateFocusNode = FocusNode();
|
||||||
|
final FocusNode _timeFocusNode = FocusNode();
|
||||||
|
final FocusNode _commentsFocusNode = FocusNode();
|
||||||
|
|
||||||
|
late Map<String, TextEditingController> _controllers;
|
||||||
|
|
||||||
|
late TextEditingController _tripTypeController = TextEditingController();
|
||||||
|
late TextEditingController _hotelNameController = TextEditingController();
|
||||||
|
late TextEditingController _fromController = TextEditingController();
|
||||||
|
late TextEditingController _toController = TextEditingController();
|
||||||
|
late TextEditingController _dateController = TextEditingController();
|
||||||
|
late TextEditingController _timeController = TextEditingController();
|
||||||
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
|
bool _tripTypeFocused = false;
|
||||||
|
bool _isHotelNameFocused = false;
|
||||||
|
bool _fromFocus = false;
|
||||||
|
bool _toFocus = false;
|
||||||
|
bool _dateFocus = false;
|
||||||
|
bool _timeFocus = false;
|
||||||
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
|
||||||
|
_addFocusListener(_tripTypeFocusNode, (focus) => _tripTypeFocused = focus);
|
||||||
|
_addFocusListener(_hotelNameFocusNode, (focus) => _isHotelNameFocused = focus);
|
||||||
|
_addFocusListener(_fromFocusNode, (focus) => _fromFocus = focus);
|
||||||
|
_addFocusListener(_toFocusNode, (focus) => _toFocus = focus);
|
||||||
|
_addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus);
|
||||||
|
_addFocusListener(_timeFocusNode, (focus) => _timeFocus = focus);
|
||||||
|
_addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
|
||||||
|
|
||||||
|
// _commentsFocusNode.addListener(() {
|
||||||
|
// setState(() {
|
||||||
|
// _commentsFocus = _commentsFocusNode.hasFocus;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_tripTypeController =
|
||||||
|
TextEditingController(text: widget.formData["tripType"] ?? "");
|
||||||
|
_hotelNameController =
|
||||||
|
TextEditingController(text: widget.formData["_hotelName"] ?? "");
|
||||||
|
_fromController =
|
||||||
|
TextEditingController(text: widget.formData["_from"] ?? "");
|
||||||
|
_toController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
|
||||||
|
_dateController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||||
|
_timeController =
|
||||||
|
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
|
||||||
|
_commentsController =
|
||||||
|
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||||
|
|
||||||
|
// Save data when user types
|
||||||
|
List<TextEditingController> controllers = [
|
||||||
|
_tripTypeController, _hotelNameController, _fromController, _toController, _dateController,
|
||||||
|
_timeController, _commentsController
|
||||||
|
];
|
||||||
|
|
||||||
|
List<String> keys = [
|
||||||
|
"_tripType", "_hotelName", "_from", "_Check_In_Time", "_Check_Out",
|
||||||
|
"_Check_Out_Time", "_comments"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (int i = 0; i < controllers.length; i++) {
|
||||||
|
controllers[i].addListener(() {
|
||||||
|
widget.updateFormData("Flight", keys[i], controllers[i].text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||||||
|
node.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
updateState(node.hasFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tripTypeFocusNode.dispose();
|
||||||
|
_tripTypeController.dispose();
|
||||||
|
_hotelNameController.dispose();
|
||||||
|
_fromController.dispose();
|
||||||
|
_toController.dispose();
|
||||||
|
_dateController.dispose();
|
||||||
|
_timeController.dispose();
|
||||||
|
_commentsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.onClose(false);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Visa Registration",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||||
|
SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(28.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||||
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
|
return [
|
||||||
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<Widget>> rowBuilders = [
|
||||||
|
// _builClassType(isDesktop),
|
||||||
|
_buildSecondRow(isDesktop)
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
|
// Iterate over rowBuilders and wrap each in a responsive container
|
||||||
|
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||||
|
|
||||||
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
|
// Actions row remains a Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Type of Visa",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
|
||||||
|
isDesktop ? Row(children: _buildTripType(isDesktop)
|
||||||
|
) :
|
||||||
|
Column(
|
||||||
|
children: _buildTripType(isDesktop)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['visa_type_of_visa'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _tripTypeFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
||||||
|
|
||||||
|
|
||||||
|
widget.updateFormData("Flight", "trip_type", newValue ?? "");
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _builClassType(bool isDesktop){
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Country",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: TextField(
|
||||||
|
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// labelText: "Select Class",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
|
||||||
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ____________
|
||||||
|
DateTime? _selectedCheckOutDate;
|
||||||
|
TimeOfDay? _selectedCheckOutTime;
|
||||||
|
|
||||||
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
|
||||||
|
|
||||||
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||||
|
? _selectedCheckOutDate!
|
||||||
|
: today,
|
||||||
|
firstDate: today,
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCheckOutDate = pickedDate;
|
||||||
|
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Country",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isHotelNameFocused,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
|
||||||
|
// child: TextField(
|
||||||
|
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
|
||||||
|
// controller: _hotelNameController,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// labelText: "Select Class",
|
||||||
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (isDesktop)
|
||||||
|
Spacer()
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Date",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _dateFocus,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _selectCheckOutDate(context),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _dateFocusNode,
|
||||||
|
controller: _dateController,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Select Date",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today,
|
||||||
|
size: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comments",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width: isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _commentsFocusNode,
|
||||||
|
controller: _commentsController,
|
||||||
|
maxLines: 6,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _handleAction(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
// Close Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog or screen
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Close",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement save logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Save Changes",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
81
lib/Screens/itnerary_list/accomodation_list.dart
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AccomodationListWidget extends StatelessWidget {
|
||||||
|
const AccomodationListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Accomodation Booking List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
152
lib/Screens/itnerary_list/bus_list.dart
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../data/models/plan.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class BusListWidget extends StatefulWidget{
|
||||||
|
const BusListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_BusListWidgetState createState() => _BusListWidgetState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BusListWidgetState extends State<BusListWidget> {
|
||||||
|
// const BusListWidget({super.key});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
late Future<List<Plan>> futurePlans;
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState(){
|
||||||
|
super.initState();
|
||||||
|
futurePlans = fetchPlans();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch API Data
|
||||||
|
Future<List<Plan>> fetchPlans() async {
|
||||||
|
final String apiUrldata = '$apiUrl/api/plans';
|
||||||
|
|
||||||
|
final token = await getToken();
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrldata),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token', // Add token here
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = json.decode(response.body);
|
||||||
|
List<dynamic> plansJson = data['data'];
|
||||||
|
return plansJson.map((json) => Plan.fromJson(json)).toList();
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load plans');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Bus Booking List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
// color: Colors.blueGrey,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Expanded(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
83
lib/Screens/itnerary_list/flight_list.dart
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FlightListWidget extends StatelessWidget {
|
||||||
|
const FlightListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Flight Booking List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
// width: 1000,
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
151
lib/Screens/itnerary_list/forex_list.dart
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../data/models/plan.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class ForexListWidget extends StatefulWidget{
|
||||||
|
const ForexListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ForexListWidgetState createState() => _ForexListWidgetState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ForexListWidgetState extends State<ForexListWidget> {
|
||||||
|
// const BusListWidget({super.key});
|
||||||
|
|
||||||
|
|
||||||
|
late Future<List<Plan>> futurePlans;
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState(){
|
||||||
|
super.initState();
|
||||||
|
futurePlans = fetchPlans();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch API Data
|
||||||
|
Future<List<Plan>> fetchPlans() async {
|
||||||
|
final String apiUrldata = '$apiUrl/api/plans';
|
||||||
|
|
||||||
|
final token = await getToken();
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrldata),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token', // Add token here
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = json.decode(response.body);
|
||||||
|
List<dynamic> plansJson = data['data'];
|
||||||
|
return plansJson.map((json) => Plan.fromJson(json)).toList();
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load plans');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Forex List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
// color: Colors.blueGrey,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Expanded(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
84
lib/Screens/itnerary_list/insurance_list.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class InsuranceListWidget extends StatelessWidget {
|
||||||
|
const InsuranceListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Container(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Insurance Booking List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
110
lib/Screens/itnerary_list/list_Iternerary.dart
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BusListWidget extends StatelessWidget {
|
||||||
|
const BusListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _buildTable("Bus Booking List", _getBusData());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, String>> _getBusData() {
|
||||||
|
return [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TrainListWidget extends StatelessWidget {
|
||||||
|
const TrainListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _buildTable("Train Booking List", _getTrainData());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, String>> _getTrainData() {
|
||||||
|
return [
|
||||||
|
{"tripType": "Express", "class": "Sleeper", "from": "Chicago", "to": "Boston"},
|
||||||
|
{"tripType": "Local", "class": "First-Class", "from": "Houston", "to": "Dallas"},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TaxiListWidget extends StatelessWidget {
|
||||||
|
const TaxiListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _buildTable("Taxi Booking List", _getTaxiData());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, String>> _getTaxiData() {
|
||||||
|
return [
|
||||||
|
{"tripType": "City Ride", "class": "Sedan", "from": "Manhattan", "to": "Brooklyn"},
|
||||||
|
{"tripType": "Airport Transfer", "class": "SUV", "from": "JFK", "to": "Times Square"},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Reusable function for building DataTable
|
||||||
|
Widget _buildTable(String title, List<Map<String, String>> data) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
top: BorderSide(color: Colors.black12),
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal borders
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: data.map((item) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(item["tripType"]!)),
|
||||||
|
DataCell(Text(item["class"]!)),
|
||||||
|
DataCell(Text(item["from"]!)),
|
||||||
|
DataCell(Text(item["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
84
lib/Screens/itnerary_list/miscellaneous_list.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MiscellaneousListWidget extends StatelessWidget {
|
||||||
|
const MiscellaneousListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Container(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Miscellaneous Booking List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
lib/Screens/itnerary_list/taxi_list.dart
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TaxiListWidget extends StatelessWidget {
|
||||||
|
const TaxiListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Taxi Booking List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
84
lib/Screens/itnerary_list/train_list.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TrainListWidget extends StatelessWidget {
|
||||||
|
const TrainListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Container(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Train Booking List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
84
lib/Screens/itnerary_list/visa_list.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class VisaListWidget extends StatelessWidget {
|
||||||
|
const VisaListWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Container(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Visa Booking List",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
border: TableBorder(
|
||||||
|
bottom: BorderSide(color: Colors.black12),
|
||||||
|
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Trip Type')),
|
||||||
|
DataColumn(label: Text('Class')),
|
||||||
|
DataColumn(label: Text('From')),
|
||||||
|
DataColumn(label: Text('To')),
|
||||||
|
DataColumn(label: Text('Actions')),
|
||||||
|
],
|
||||||
|
rows: _buildDataRows(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataRow> _buildDataRows() {
|
||||||
|
List<Map<String, String>> data = [
|
||||||
|
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
|
||||||
|
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return data.map((bus) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(bus["tripType"]!)),
|
||||||
|
DataCell(Text(bus["class"]!)),
|
||||||
|
DataCell(Text(bus["from"]!)),
|
||||||
|
DataCell(Text(bus["to"]!)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||||
|
onPressed: () {
|
||||||
|
// View action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: Colors.green),
|
||||||
|
onPressed: () {
|
||||||
|
// Edit action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
// Delete action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
744
lib/Screens/plans/create_plans.dart
Normal file
@ -0,0 +1,744 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/Screens/plans/dynamic_itinerary_stepper.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../data/models/plan.dart';
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../dialog/user_selection_dialog.dart';
|
||||||
|
|
||||||
|
class CreatePlan extends StatefulWidget {
|
||||||
|
const CreatePlan({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CreatePlansState createState() => _CreatePlansState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreatePlansState extends State<CreatePlan> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
|
||||||
|
child: Row(children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Icon(
|
||||||
|
Icons.create_new_folder_outlined,
|
||||||
|
color: Color(0xFF84869A),
|
||||||
|
size: 23,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"New Plan",
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Container(
|
||||||
|
color: Color(0xFFE9EBF6),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
onPressed: () {
|
||||||
|
context.go('/listPlan');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(26.0),
|
||||||
|
child: CreateNewPlan(isDesktop: isDesktop),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateNewPlan extends StatefulWidget {
|
||||||
|
final bool isDesktop;
|
||||||
|
const CreateNewPlan({super.key, required this.isDesktop});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CreateNewPlansState createState() => _CreateNewPlansState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateNewPlansState extends State<CreateNewPlan> {
|
||||||
|
final FocusNode _textFieldFocusNode = FocusNode(); // Declare FocusNode
|
||||||
|
bool _isTextFieldFocused = false;
|
||||||
|
late String _selectedOption = "Option 1";
|
||||||
|
late String? _selectedIsBillable = "Billable";
|
||||||
|
Map<String, dynamic>? storedData;
|
||||||
|
|
||||||
|
Map<String, dynamic>? apiData; // Store API response here
|
||||||
|
bool isLoading = true; // Track loading state
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
fetchPlans();
|
||||||
|
|
||||||
|
_textFieldFocusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_isTextFieldFocused = _textFieldFocusNode.hasFocus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textFieldFocusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<String?> getUserId() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('userId');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> fetchPlans() async {
|
||||||
|
final String apiUrldata = '$apiUrl/api/getDropdownMaster';
|
||||||
|
|
||||||
|
final token = await getToken();
|
||||||
|
final userId = "1";
|
||||||
|
// final userId = await getUserId();
|
||||||
|
|
||||||
|
print("SUSRTRT- $userId");
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrldata),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
try {
|
||||||
|
final data = json.decode(response.body);
|
||||||
|
print(data);
|
||||||
|
|
||||||
|
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||||
|
throw Exception("Invalid response format: 'data' field is missing or not a Map");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> plansJson = data['data']; // 'data' is a Map, not a List
|
||||||
|
setState(() {
|
||||||
|
apiData = data['data']; // Store API response in state
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Error parsing response: $e');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load plans');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> getStoredData() async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
String? jsonString = prefs.getString('api_response');
|
||||||
|
|
||||||
|
if (jsonString != null) {
|
||||||
|
storedData = json.decode(jsonString);
|
||||||
|
print('Stored Data: $storedData');
|
||||||
|
} else {
|
||||||
|
storedData = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Plan This Trip For", // Your label
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
isMobile
|
||||||
|
? SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: _buildPlanTrip(isMobile),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
children: _buildPlanTrip(isMobile),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Divider(
|
||||||
|
color: Color(0xFFE6E7F5), // Change color
|
||||||
|
thickness: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Trip Title", // Your label
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: _isTextFieldFocused,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _textFieldFocusNode,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Trip Title",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Trip Type", // Your label
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
isMobile
|
||||||
|
? SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: _buildTripType(isMobile),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
children: _buildTripType(isMobile),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildCostIsBillable())
|
||||||
|
: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildCostIsBillable()),
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildNonDescriptionColumn(),
|
||||||
|
SizedBox(width: 25),
|
||||||
|
_buildDescriptionColumn(isDesktop),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildNonDescriptionColumn(),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
_buildDescriptionColumn(isDesktop),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Divider(
|
||||||
|
color: Color(0xFFE6E7F5), // Change color
|
||||||
|
thickness: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: DynamicItinerary(apiData: apiData)), // Wrap with Expanded if needed
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Extracted helper function
|
||||||
|
List<Widget> _buildCostIsBillable() {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Cost Center *", // Your label
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: false, // Dropdown doesn't use focus
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 45, // Set appropriate height
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
value: "Option 1",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: (newValue) {},
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(value: "Option 1", child: Text("Option 1")),
|
||||||
|
DropdownMenuItem(value: "Option 2", child: Text("Option 2")),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(width: 25),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Is Billable ", // Your label
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Radio<String>(
|
||||||
|
value: "Billable",
|
||||||
|
groupValue: _selectedIsBillable,
|
||||||
|
activeColor: Colors.blueAccent,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_selectedIsBillable = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text("Billable"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(width: 20), // Spacing
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Radio<String>(
|
||||||
|
value: "Non Billable",
|
||||||
|
groupValue: _selectedIsBillable,
|
||||||
|
activeColor: Colors.blueAccent,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_selectedIsBillable = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text("Non Billable"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildPlanTrip(bool isDesktop) {
|
||||||
|
List<Map<String, String>> options = [
|
||||||
|
{"title": "Self", "value": "Option 1"},
|
||||||
|
{"title": "Other Employee", "value": "Option 2"},
|
||||||
|
{"title": "Others", "value": "Option 3"},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
return options.map((option) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||||
|
child: CustomTextFieldWrapper(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
width: option["value"] == "Option 2" ? 175 : 120, // Adjust width conditionally
|
||||||
|
isFocused: _selectedOption == option["value"],
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
child: RadioListTile<String>(
|
||||||
|
activeColor: Colors.blueAccent,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
dense: true,
|
||||||
|
title: Text(option["title"]!),
|
||||||
|
value: option["value"]!,
|
||||||
|
groupValue: _selectedOption,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_selectedOption = value!;
|
||||||
|
if(value == 'Option 2' || value == 'Option 3' ){
|
||||||
|
_showInputDialog(option["title"]!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isMobile) {
|
||||||
|
return [
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
width: 120,
|
||||||
|
isFocused: _selectedOption == "Option 1",
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 35,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: RadioListTile<String>(
|
||||||
|
activeColor: Colors.blueAccent,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
dense: true,
|
||||||
|
title: Text("Domestic"),
|
||||||
|
value: "Option 1",
|
||||||
|
groupValue: _selectedOption,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_selectedOption = value!;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
width: 150,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||||||
|
isFocused: _selectedOption == "Option 2",
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: RadioListTile<String>(
|
||||||
|
activeColor: Colors.blueAccent,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
dense: true,
|
||||||
|
title: Text("International"),
|
||||||
|
value: "Option 2",
|
||||||
|
groupValue: _selectedOption,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_selectedOption = value!;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Widget _buildNonDescriptionColumn (){
|
||||||
|
|
||||||
|
// 'plan_purpose_of_travel' Starts ------------------------------------------------------
|
||||||
|
|
||||||
|
List<dynamic> purposeList = apiData?['plan_purpose_of_travel'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownItems.isEmpty) {
|
||||||
|
dropdownItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
|
// 'plan_functional_department' Starts ---------------------------------------------
|
||||||
|
|
||||||
|
List<dynamic> funcDeptList = apiData?['plan_functional_department'] ?? [];
|
||||||
|
|
||||||
|
List<DropdownMenuItem<String>> dropdownFuncDeptItems = funcDeptList
|
||||||
|
.map((item)=>DropdownMenuItem<String>(
|
||||||
|
value: item['dropdown_value'],
|
||||||
|
child: Text(item['dropdown_value']),
|
||||||
|
)).toList();
|
||||||
|
|
||||||
|
if (dropdownFuncDeptItems.isEmpty) {
|
||||||
|
dropdownFuncDeptItems.add(
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: null,
|
||||||
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default selected value
|
||||||
|
String? selectedFuncDept = dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value : null;
|
||||||
|
// 'plan_functional_department' End
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Purpose of Travel *", // Your label
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: false, // Dropdown doesn't use focus
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 45, // Set appropriate height
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
value: selectedPurpose,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedPurpose = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownItems,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Functional Department", // Your label
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: false, // Dropdown doesn't use focus
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 45, // Set appropriate height
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
value: selectedFuncDept,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10), // Proper padding
|
||||||
|
),
|
||||||
|
onChanged: purposeList.isNotEmpty
|
||||||
|
? (newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedFuncDept = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
items: dropdownFuncDeptItems,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Widget _buildDescriptionColumn(isDesktop){
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Description", // Your label
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: false, // Dropdown doesn't use focus
|
||||||
|
width: isDesktop? MediaQuery.of(context).size.width * 0.5 :
|
||||||
|
MediaQuery.of(context).size.width * 0.85 ,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _textFieldFocusNode,
|
||||||
|
maxLines: 6,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _showInputDialog(String title){
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context){
|
||||||
|
return UserSelectionDialog(
|
||||||
|
title:title,
|
||||||
|
onSubmit: (input){
|
||||||
|
print("USer entered : $input");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
266
lib/Screens/plans/dynamic_itinerary_stepper.dart
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
import 'package:easy_stepper/easy_stepper.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/Screens/itnerary_list/accomodation_list.dart';
|
||||||
|
import 'package:frontend/Screens/itnerary_list/bus_list.dart';
|
||||||
|
import 'package:frontend/Screens/itnerary_list/flight_list.dart';
|
||||||
|
import 'package:frontend/Screens/itnerary_list/taxi_list.dart';
|
||||||
|
import 'package:frontend/Screens/itnerary_list/train_list.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../itnerary/accomodations.dart';
|
||||||
|
import '../itnerary/bus.dart';
|
||||||
|
import '../itnerary/flights.dart';
|
||||||
|
import '../itnerary/forex.dart';
|
||||||
|
import '../itnerary/insurance.dart';
|
||||||
|
import '../itnerary/miscellaneous.dart';
|
||||||
|
import '../itnerary/taxi.dart';
|
||||||
|
import '../itnerary/train.dart';
|
||||||
|
import '../itnerary/visa.dart';
|
||||||
|
import '../itnerary_list/forex_list.dart';
|
||||||
|
import '../itnerary_list/insurance_list.dart';
|
||||||
|
import '../itnerary_list/miscellaneous_list.dart';
|
||||||
|
import '../itnerary_list/visa_list.dart';
|
||||||
|
|
||||||
|
class DynamicItinerary extends StatefulWidget {
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
const DynamicItinerary({super.key, required this.apiData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DynamicItineraryState createState() => _DynamicItineraryState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DynamicItineraryState extends State<DynamicItinerary> {
|
||||||
|
|
||||||
|
String selectedOption = "";
|
||||||
|
String selectedListOption = "";
|
||||||
|
|
||||||
|
bool isSelected = false;
|
||||||
|
|
||||||
|
// Store form values for each tab
|
||||||
|
final Map<String, Map<String, String>> formData = {
|
||||||
|
"Flight": {}, // Stores data for the Flight tab
|
||||||
|
"Train": {}, // Stores data for the Train tab
|
||||||
|
"Taxi": {}, // Stores data for the Car tab
|
||||||
|
"Bus": {}, // Stores data for the Bus tab
|
||||||
|
"Insurance": {}, // Stores data for the Bus tab
|
||||||
|
"Accommodation": {},// Stores data for the Accommodation tab
|
||||||
|
"Miscellaneous": {},// Stores data for the Accommodation tab
|
||||||
|
"Forex": {},
|
||||||
|
"Visa": {},// Stores data for the Accommodation tab
|
||||||
|
};
|
||||||
|
|
||||||
|
void handleClose(bool value) {
|
||||||
|
setState(() {
|
||||||
|
isSelected = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
Widget selectedWidget;
|
||||||
|
Widget selectedListWidget;
|
||||||
|
|
||||||
|
// void updateFormData(String tab, String key, String value) {
|
||||||
|
// setState(() {
|
||||||
|
// formData[tab]![key] = value; // Update the stored data
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
void updateFormData(String tab, String key, String value) {
|
||||||
|
setState(() {
|
||||||
|
if (!formData.containsKey(tab)) {
|
||||||
|
formData[tab] = {}; // Initialize if null
|
||||||
|
}
|
||||||
|
formData[tab]![key] = value; // Update the stored data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
switch (selectedListOption) {
|
||||||
|
case "Train":
|
||||||
|
selectedListWidget = TrainListWidget();
|
||||||
|
break;
|
||||||
|
case "Taxi":
|
||||||
|
selectedListWidget = TaxiListWidget();
|
||||||
|
break;
|
||||||
|
case "Bus":
|
||||||
|
selectedListWidget = BusListWidget();
|
||||||
|
break;
|
||||||
|
case "Insurance":
|
||||||
|
selectedListWidget = InsuranceListWidget();
|
||||||
|
break;
|
||||||
|
case "Visa":
|
||||||
|
selectedListWidget = VisaListWidget();
|
||||||
|
break;
|
||||||
|
case "Forex":
|
||||||
|
selectedListWidget = ForexListWidget();
|
||||||
|
break;
|
||||||
|
case "Accommodation":
|
||||||
|
selectedListWidget = AccomodationListWidget();
|
||||||
|
break;
|
||||||
|
case "Miscellaneous":
|
||||||
|
selectedListWidget = MiscellaneousListWidget();
|
||||||
|
break;
|
||||||
|
case "Flight":
|
||||||
|
default:
|
||||||
|
selectedListWidget = FlightListWidget();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (selectedOption) {
|
||||||
|
case "Train":
|
||||||
|
selectedWidget = TrainScreen(onClose: handleClose,formData: formData["Train"]!, updateFormData: updateFormData, apiData: widget.apiData,);
|
||||||
|
// selectedWidget = TrainScreen(formData: formData["Train"]!);
|
||||||
|
break;
|
||||||
|
case "Taxi":
|
||||||
|
selectedWidget = TaxiScreen(onClose: handleClose, formData: formData["Taxi"]!, updateFormData: updateFormData, apiData: widget.apiData,);
|
||||||
|
break;
|
||||||
|
case "Bus":
|
||||||
|
selectedWidget = BusScreen(onClose: handleClose, formData: formData["Bus"]!, updateFormData: updateFormData, apiData: widget.apiData,);
|
||||||
|
break;
|
||||||
|
case "Insurance":
|
||||||
|
selectedWidget = InsuranceScreen(onClose: handleClose, formData: formData["Insurance"]!, updateFormData: updateFormData, apiData: widget.apiData,);
|
||||||
|
break;
|
||||||
|
case "Visa":
|
||||||
|
selectedWidget = VisaScreen(onClose: handleClose, formData: formData["Insurance"]!, updateFormData: updateFormData, apiData: widget.apiData,);
|
||||||
|
break;
|
||||||
|
case "Miscellaneous":
|
||||||
|
selectedWidget =MiscellaneousScreen(formData: formData["Miscellaneous"]!, updateFormData: updateFormData, onClose: handleClose, apiData: widget.apiData);
|
||||||
|
break;
|
||||||
|
case "Accommodation":
|
||||||
|
selectedWidget = AccomodationScreen( onClose: handleClose,formData: formData["Accommodation"]!, updateFormData: updateFormData);
|
||||||
|
break;
|
||||||
|
case "Forex":
|
||||||
|
selectedWidget = ForexScreen( onClose: handleClose,formData: formData["Forex"]!, updateFormData: updateFormData, apiData: widget.apiData);
|
||||||
|
break;
|
||||||
|
case "Flight":
|
||||||
|
default:
|
||||||
|
selectedWidget = FlightScreen(onClose: handleClose,formData: formData["Flight"]!, updateFormData: updateFormData, apiData: widget.apiData,);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isMobile = sizingInfo.isMobile;
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text("Itinerary",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
),),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Color(0xFFF4F4FB)),
|
||||||
|
borderRadius: BorderRadius.circular(1),
|
||||||
|
color: Color(0xFFF4F4FB),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
child: isMobile ?
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: _buildOptions(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
children: _buildOptions(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Column(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 2),
|
||||||
|
isSelected ? selectedWidget : selectedListWidget,
|
||||||
|
// TrainScreen()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildOptions() {
|
||||||
|
return [
|
||||||
|
_buildOption("Flight"),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
_buildOption("Taxi"),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
_buildOption("Train"),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
_buildOption("Bus"),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
_buildOption("Accommodation"),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
_buildOption("Forex"),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
_buildOption("Insurance"),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
_buildOption("Visa"),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
_buildOption("Miscellaneous"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOption(String title) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedListOption = title;
|
||||||
|
isSelected = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74),
|
||||||
|
fontWeight:
|
||||||
|
selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
|
||||||
|
SizedBox(width: 5),
|
||||||
|
if (selectedListOption == title)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedOption = title;
|
||||||
|
isSelected = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.add_circle,
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
218
lib/Screens/plans/list_plans.dart
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:frontend/data/models/plan.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/config/apiUrl.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class ListPlans extends StatefulWidget{
|
||||||
|
const ListPlans({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ListPlansState createState() => _ListPlansState();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class _ListPlansState extends State<ListPlans>{
|
||||||
|
|
||||||
|
late Future<List<Plan>> futurePlans;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState(){
|
||||||
|
super.initState();
|
||||||
|
futurePlans = fetchPlans();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch API Data
|
||||||
|
Future<List<Plan>> fetchPlans() async {
|
||||||
|
final String apiUrldata = '$apiUrl/api/plans';
|
||||||
|
|
||||||
|
final token = await getToken();
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrldata),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token', // Add token here
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = json.decode(response.body);
|
||||||
|
List<dynamic> plansJson = data['data'];
|
||||||
|
return plansJson.map((json) => Plan.fromJson(json)).toList();
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load plans');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
|
||||||
|
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
||||||
|
body: Row(
|
||||||
|
children: [
|
||||||
|
if (isDesktop) CustomDrawer(isDesktop: true),
|
||||||
|
Expanded(child: buildTableLayout(isDesktop))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTableLayout(isDesktop) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text('Plans List',
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.keyboard_arrow_down),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
backgroundColor: Colors.blueAccent),
|
||||||
|
onPressed: () {
|
||||||
|
context.go('/createPlan');
|
||||||
|
if (!isDesktop) Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.add_circle,color: Colors.white,),
|
||||||
|
SizedBox(width: 5,),
|
||||||
|
Text('NewPlan'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
FutureBuilder<List<Plan>>(
|
||||||
|
future: futurePlans, // Use the futurePlans variable
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
return Center(child: Text("Error: ${snapshot.error}"));
|
||||||
|
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||||
|
return const Center(child: Text("No plans available"));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Plan> plans = snapshot.data!; // Extract the list of plans
|
||||||
|
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
bool isTabletOrDesktop = sizingInfo.isTablet || sizingInfo.isDesktop;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
// scrollDirection: isTabletOrDesktop ? Axis.vertical : Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
// constraints: isTabletOrDesktop
|
||||||
|
// ? const BoxConstraints(maxWidth: double.infinity)
|
||||||
|
// : BoxConstraints.tightFor(width: 600),
|
||||||
|
width: MediaQuery.of(context).size.width ,
|
||||||
|
child: DataTable(
|
||||||
|
// columnSpacing: 50.0,
|
||||||
|
dividerThickness: 0.5, // Reduce the thickness of row dividers
|
||||||
|
border: TableBorder(
|
||||||
|
horizontalInside: BorderSide(width: 0.5, color: Colors.grey.shade200), // Reduce horizontal line thickness
|
||||||
|
),
|
||||||
|
columns: const [
|
||||||
|
DataColumn(label: Text('Plan ID', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
DataColumn(label: Text('Trip Title', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
DataColumn(label: Text('Trip Type', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
DataColumn(label: Text('Cost Center', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
// DataColumn(label: Text('Functional Department', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
// DataColumn(label: Text('Purpose Of Travel', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
// DataColumn(label: Text('Description', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
//
|
||||||
|
DataColumn(label: Text('Is Billable', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
DataColumn(label: Text('Status', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
DataColumn(label: Text('Actions', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
|
||||||
|
],
|
||||||
|
rows: plans.map((plan) {
|
||||||
|
return DataRow(cells: [
|
||||||
|
DataCell(Text(plan.planId)),
|
||||||
|
// DataCell(Text(plan.tripTitle)),
|
||||||
|
DataCell(Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
plan.tripTitle,
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis, // Adds "..." if text is too long
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
|
||||||
|
|
||||||
|
DataCell(Text(plan.tripType)),
|
||||||
|
DataCell(Text(plan.costCenter)),
|
||||||
|
// DataCell(Text(plan.functionalDepartment)),
|
||||||
|
// DataCell(Text(plan.purposeOfTravel)),
|
||||||
|
// DataCell(Text(plan.description)),
|
||||||
|
//
|
||||||
|
DataCell(Text(plan.isBillable)),
|
||||||
|
DataCell(Text(plan.status)),
|
||||||
|
DataCell(
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
print("View button clicked for ${plan.tripTitle}");
|
||||||
|
},
|
||||||
|
child: const Text('View',
|
||||||
|
style: TextStyle(color: Colors.blueAccent)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
16
lib/app.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/routes/custom_router.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp.router(
|
||||||
|
title: 'TRIP MANAGEMENT',
|
||||||
|
routerConfig: router,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/config/apiUrl.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
//api url
|
||||||
|
const String apiUrl = 'http://apitest.tripapprovaltool.com';
|
||||||
|
|
||||||
|
|
||||||
40
lib/data/models/plan.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
class Plan {
|
||||||
|
final String planId;
|
||||||
|
final String tripTitle;
|
||||||
|
final String tripType;
|
||||||
|
final String status;
|
||||||
|
final String costCenter;
|
||||||
|
final String functionalDepartment;
|
||||||
|
final String purposeOfTravel;
|
||||||
|
final String soNumber;
|
||||||
|
final String description;
|
||||||
|
final String isBillable;
|
||||||
|
|
||||||
|
Plan({
|
||||||
|
required this.planId,
|
||||||
|
required this.tripTitle,
|
||||||
|
required this.tripType,
|
||||||
|
required this.status,
|
||||||
|
required this.costCenter,
|
||||||
|
required this.functionalDepartment,
|
||||||
|
required this.purposeOfTravel,
|
||||||
|
required this.soNumber,
|
||||||
|
required this.description,
|
||||||
|
required this.isBillable,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Plan.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Plan(
|
||||||
|
planId: json['plan_id'],
|
||||||
|
tripTitle: json['trip_title'],
|
||||||
|
tripType: json['trip_type_value'],
|
||||||
|
status: json['status'] == "0" ? "Inactive" : "Active",
|
||||||
|
costCenter: json['cost_center_value'] ?? '',
|
||||||
|
functionalDepartment: json['functional_department_value'] ?? '',
|
||||||
|
purposeOfTravel: json['purpose_of_travel_value'] ?? '',
|
||||||
|
soNumber: json['so_number'] ?? '',
|
||||||
|
description: json['description'] ?? '',
|
||||||
|
isBillable: json['is_billable_value'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
lib/data/models/searchUser.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
class SearchUser{
|
||||||
|
final String userId;
|
||||||
|
final String travellerId;
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String email;
|
||||||
|
final String mobileNo;
|
||||||
|
final String alternateMobileNo;
|
||||||
|
|
||||||
|
SearchUser({
|
||||||
|
required this.userId,
|
||||||
|
required this.travellerId,
|
||||||
|
required this.firstName,
|
||||||
|
required this.lastName,
|
||||||
|
required this.email,
|
||||||
|
required this.mobileNo,
|
||||||
|
required this.alternateMobileNo,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
factory SearchUser.fromJson(Map<String, dynamic> json){
|
||||||
|
return SearchUser(
|
||||||
|
userId: json['user_id'].toString(),
|
||||||
|
travellerId: json['traveller_id'].toString(),
|
||||||
|
firstName: json['first_name'].toString()?? '',
|
||||||
|
lastName: json['last_name'].toString()?? '',
|
||||||
|
email: json['email'].toString()?? '',
|
||||||
|
mobileNo: json['mobile_no'].toString()?? '',
|
||||||
|
alternateMobileNo: json['alternate_mobile_no'].toString()?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
6
lib/main.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'app.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const MyApp());
|
||||||
|
}
|
||||||
34
lib/routes/custom_appBar.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final String title;
|
||||||
|
final List<Widget>? actions; // Optional actions (icons, buttons)
|
||||||
|
final bool showBackButton;
|
||||||
|
|
||||||
|
const CustomAppBar({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
this.actions,
|
||||||
|
this.showBackButton = false, // Default: No back button
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
centerTitle: true,
|
||||||
|
backgroundColor: Colors.blueAccent, // Customize color
|
||||||
|
elevation: 4, // Shadow effect
|
||||||
|
leading: showBackButton
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => Navigator.pop(context), // Back navigation
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
actions: actions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(56); // Standard AppBar height
|
||||||
|
}
|
||||||
83
lib/routes/custom_drawer.dart
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class CustomDrawer extends StatelessWidget{
|
||||||
|
final bool isDesktop;
|
||||||
|
|
||||||
|
const CustomDrawer({super.key, required this.isDesktop});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context){
|
||||||
|
Widget drawerContent = Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 80,
|
||||||
|
child: const DrawerHeader(
|
||||||
|
decoration: BoxDecoration(color: Colors.blue),
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Text('Menu', style: TextStyle(color: Colors.white, fontSize: 20)))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildDrawerItem(context, Icons.home,'Home', '/home'),
|
||||||
|
_buildExpandableItem(context,Icons.assessment,'Plans',[
|
||||||
|
_buildSubDrawerItem(context,'My Plans','/listPlan'),
|
||||||
|
_buildSubDrawerItem(context,'PlanB','/PlanB')
|
||||||
|
]),
|
||||||
|
_buildDrawerItem(context,Icons.login,'Login','/')
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isDesktop) {
|
||||||
|
// Sidebar for Desktop (always visible)**
|
||||||
|
return Container(
|
||||||
|
width: 250, // Fixed width for sidebar
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
child: drawerContent,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Drawer for Mobile & Tablet**
|
||||||
|
return Drawer(child: ListView(padding: EdgeInsets.zero, children: [drawerContent]));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **Reusable Drawer Item**
|
||||||
|
Widget _buildDrawerItem(BuildContext context, IconData icon, String title, String route)
|
||||||
|
{
|
||||||
|
return ListTile(
|
||||||
|
leading: Icon(icon),
|
||||||
|
title: Text(title),
|
||||||
|
onTap: (){
|
||||||
|
context.go(route);
|
||||||
|
if (!isDesktop) Navigator.pop(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildExpandableItem(BuildContext context, IconData icon, String title, List<Widget>children){
|
||||||
|
return ExpansionTile(
|
||||||
|
leading: Icon(icon),
|
||||||
|
title: Text(title),
|
||||||
|
shape: const Border(), // Removes top and bottom dividers
|
||||||
|
childrenPadding: const EdgeInsets.only(left: 40), // Indent sub-items
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Widget _buildSubDrawerItem(BuildContext context, String title, String route)
|
||||||
|
{
|
||||||
|
return ListTile(
|
||||||
|
title: Text(title),
|
||||||
|
onTap: (){
|
||||||
|
context.go(route);
|
||||||
|
if (!isDesktop) Navigator.pop(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
28
lib/routes/custom_router.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
import 'package:frontend/Screens/authentication/login/login_page.dart';
|
||||||
|
import 'package:frontend/Screens/authentication/loginPage1.dart';
|
||||||
|
import 'package:frontend/Screens/dashboard/home_page.dart';
|
||||||
|
import 'package:frontend/Screens/plans/create_plans.dart';
|
||||||
|
import 'package:frontend/Screens/plans/list_plans.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
final GoRouter router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
builder: (context, state) => LoginPage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/home',
|
||||||
|
builder: (context, state) => HomePage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/listPlan',
|
||||||
|
builder: (context, state) => ListPlans(),
|
||||||
|
), GoRoute(
|
||||||
|
path: '/createPlan',
|
||||||
|
builder: (context, state) => CreatePlan(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
51
lib/widgets/custom_radio_button.dart
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CustomRadioButtonWrapper extends StatefulWidget {
|
||||||
|
final bool isFocused;
|
||||||
|
final bool isDesktop;
|
||||||
|
final List<String> options;
|
||||||
|
final String selectedValue;
|
||||||
|
final Function(String) onChanged;
|
||||||
|
|
||||||
|
const CustomRadioButtonWrapper({
|
||||||
|
Key? key,
|
||||||
|
required this.isFocused,
|
||||||
|
required this.isDesktop,
|
||||||
|
required this.options,
|
||||||
|
required this.selectedValue,
|
||||||
|
required this.onChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CustomRadioButtonWrapperState createState() => _CustomRadioButtonWrapperState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomRadioButtonWrapperState extends State<CustomRadioButtonWrapper> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Select Destination",
|
||||||
|
style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Column(
|
||||||
|
children: widget.options.map((option) {
|
||||||
|
return RadioListTile<String>(
|
||||||
|
title: Text(option, style: TextStyle(fontSize: 12)),
|
||||||
|
value: option,
|
||||||
|
groupValue: widget.selectedValue,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
widget.onChanged(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||