commit 1bf3e41abe7c740ec4c86b47e75a6065888989ce Author: jahongireshonqulov Date: Sat Oct 18 09:40:06 2025 +0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79c113f --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.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 diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..82b7d8f --- /dev/null +++ b/.metadata @@ -0,0 +1,33 @@ +# 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: "c23637390482d4cf9598c3ce3f2be31aa7332daf" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + - platform: android + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + - platform: ios + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c05e71 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +alias: ctrans +pasword: ctrans_123 \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..9c99bf7 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,31 @@ +# 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 + prefer_const_constructors: true + prefer_const_declarations: true + prefer_const_literals_to_create_immutables: true + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..62d8714 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,74 @@ +import java.util.Properties +import java.io.FileInputStream + + +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") + id("com.google.gms.google-services") +} +val keystoreProperties = Properties() +val keystorePropertiesFile = rootProject.file("key.properties") +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) + android { + namespace = "com.cargocalculater.cargocalculaterapp" + compileSdk = 36 + ndkVersion = "27.0.12077973" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + isCoreLibraryDesugaringEnabled = true + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.cargocalculater.cargocalculaterapp" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 24 + targetSdk = 35 + versionCode = 8 + versionName = "1.0.3" + } + + signingConfigs { + create("release") { + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + storeFile = keystoreProperties["storeFile"]?.let { file(it) } + storePassword = keystoreProperties["storePassword"] as String + } + } + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("release") + ndk { + abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") + } + } + } + } + + flutter { + source = "../.." + } + dependencies { + // firebase done + implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") + implementation("com.google.firebase:firebase-crashlytics") + } +} \ No newline at end of file diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..bc83bca --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "955070107009", + "project_id": "cargocalculator-46c5e", + "storage_bucket": "cargocalculator-46c5e.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:955070107009:android:ba6bbebd9d165416a5ac80", + "android_client_info": { + "package_name": "com.cargocalculater.cargocalculaterapp" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyBGs-BkAh9CGvP1pq3FQUddYukO1YtztFU" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..502c19b --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..74c3697 Binary files /dev/null and b/android/app/src/main/ic_launcher-playstore.png differ diff --git a/android/app/src/main/kotlin/com/cargocalculater/cargocalculaterapp/MainActivity.kt b/android/app/src/main/kotlin/com/cargocalculater/cargocalculaterapp/MainActivity.kt new file mode 100644 index 0000000..dbae18d --- /dev/null +++ b/android/app/src/main/kotlin/com/cargocalculater/cargocalculaterapp/MainActivity.kt @@ -0,0 +1,5 @@ +package com.cargocalculater.cargocalculaterapp + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-hdpi/android12splash.png b/android/app/src/main/res/drawable-hdpi/android12splash.png new file mode 100644 index 0000000..8956026 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-hdpi/splash.png b/android/app/src/main/res/drawable-hdpi/splash.png new file mode 100644 index 0000000..8956026 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-mdpi/android12splash.png b/android/app/src/main/res/drawable-mdpi/android12splash.png new file mode 100644 index 0000000..9b7155c Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-mdpi/splash.png b/android/app/src/main/res/drawable-mdpi/splash.png new file mode 100644 index 0000000..9b7155c Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/android/app/src/main/res/drawable-night-hdpi/android12splash.png new file mode 100644 index 0000000..8956026 Binary files /dev/null and b/android/app/src/main/res/drawable-night-hdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/android/app/src/main/res/drawable-night-mdpi/android12splash.png new file mode 100644 index 0000000..9b7155c Binary files /dev/null and b/android/app/src/main/res/drawable-night-mdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-v21/background.png b/android/app/src/main/res/drawable-night-v21/background.png new file mode 100644 index 0000000..8e21404 Binary files /dev/null and b/android/app/src/main/res/drawable-night-v21/background.png differ diff --git a/android/app/src/main/res/drawable-night-v21/launch_background.xml b/android/app/src/main/res/drawable-night-v21/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-night-v21/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xhdpi/android12splash.png new file mode 100644 index 0000000..9cc0c07 Binary files /dev/null and b/android/app/src/main/res/drawable-night-xhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png new file mode 100644 index 0000000..ed17d7f Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png new file mode 100644 index 0000000..79008e4 Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night/background.png b/android/app/src/main/res/drawable-night/background.png new file mode 100644 index 0000000..8e21404 Binary files /dev/null and b/android/app/src/main/res/drawable-night/background.png differ diff --git a/android/app/src/main/res/drawable-night/launch_background.xml b/android/app/src/main/res/drawable-night/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-night/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-v21/background.png b/android/app/src/main/res/drawable-v21/background.png new file mode 100644 index 0000000..8e21404 Binary files /dev/null and b/android/app/src/main/res/drawable-v21/background.png differ diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-xhdpi/android12splash.png b/android/app/src/main/res/drawable-xhdpi/android12splash.png new file mode 100644 index 0000000..9cc0c07 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/splash.png b/android/app/src/main/res/drawable-xhdpi/splash.png new file mode 100644 index 0000000..9cc0c07 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/android/app/src/main/res/drawable-xxhdpi/android12splash.png new file mode 100644 index 0000000..ed17d7f Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/splash.png b/android/app/src/main/res/drawable-xxhdpi/splash.png new file mode 100644 index 0000000..ed17d7f Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/android/app/src/main/res/drawable-xxxhdpi/android12splash.png new file mode 100644 index 0000000..79008e4 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/splash.png b/android/app/src/main/res/drawable-xxxhdpi/splash.png new file mode 100644 index 0000000..79008e4 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png new file mode 100644 index 0000000..8e21404 Binary files /dev/null and b/android/app/src/main/res/drawable/background.png differ diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..0d014e8 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..b23867a Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..bb85655 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..c038100 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..baf64f8 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..96d5a85 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..f5347c8 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..e66f50d Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..708c736 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..d30a2cc Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..c3c0d61 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62fa774 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..64d0799 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..3a21fcb Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..d95f5ac Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml new file mode 100644 index 0000000..735e8a4 --- /dev/null +++ b/android/app/src/main/res/values-night-v31/styles.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..dbc9ea9 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml new file mode 100644 index 0000000..e437c38 --- /dev/null +++ b/android/app/src/main/res/values-v31/styles.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..19317b5 --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #033776 + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..0d1fa8f --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..afa1e8e --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..0cb79f2 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "2.1.20" apply false + id("com.google.gms.google-services") version "4.4.2" apply false +} + +include(":app") diff --git a/assets/fonts/Inter-Bold.ttf b/assets/fonts/Inter-Bold.ttf new file mode 100644 index 0000000..9fb9b75 Binary files /dev/null and b/assets/fonts/Inter-Bold.ttf differ diff --git a/assets/fonts/Inter-Medium.ttf b/assets/fonts/Inter-Medium.ttf new file mode 100644 index 0000000..458cd06 Binary files /dev/null and b/assets/fonts/Inter-Medium.ttf differ diff --git a/assets/fonts/Inter-Regular.ttf b/assets/fonts/Inter-Regular.ttf new file mode 100644 index 0000000..b7aaca8 Binary files /dev/null and b/assets/fonts/Inter-Regular.ttf differ diff --git a/assets/fonts/Inter-SemiBold.ttf b/assets/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000..47f8ab1 Binary files /dev/null and b/assets/fonts/Inter-SemiBold.ttf differ diff --git a/assets/png/ic_china.jpg b/assets/png/ic_china.jpg new file mode 100644 index 0000000..c45afce Binary files /dev/null and b/assets/png/ic_china.jpg differ diff --git a/assets/png/ic_china.png b/assets/png/ic_china.png new file mode 100644 index 0000000..6571a28 Binary files /dev/null and b/assets/png/ic_china.png differ diff --git a/assets/png/ic_logo.png b/assets/png/ic_logo.png new file mode 100644 index 0000000..60a1b90 Binary files /dev/null and b/assets/png/ic_logo.png differ diff --git a/assets/png/ic_ru.png b/assets/png/ic_ru.png new file mode 100644 index 0000000..f8821cd Binary files /dev/null and b/assets/png/ic_ru.png differ diff --git a/assets/png/ic_uz.png b/assets/png/ic_uz.png new file mode 100644 index 0000000..4d03d3f Binary files /dev/null and b/assets/png/ic_uz.png differ diff --git a/assets/svg/ic_add_user.svg b/assets/svg/ic_add_user.svg new file mode 100644 index 0000000..3a2f0c8 --- /dev/null +++ b/assets/svg/ic_add_user.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_box.svg b/assets/svg/ic_box.svg new file mode 100644 index 0000000..6d86204 --- /dev/null +++ b/assets/svg/ic_box.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/svg/ic_box_gray.svg b/assets/svg/ic_box_gray.svg new file mode 100644 index 0000000..01a134c --- /dev/null +++ b/assets/svg/ic_box_gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_building.svg b/assets/svg/ic_building.svg new file mode 100644 index 0000000..bf65b4e --- /dev/null +++ b/assets/svg/ic_building.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_calculator.svg b/assets/svg/ic_calculator.svg new file mode 100644 index 0000000..eb69ee1 --- /dev/null +++ b/assets/svg/ic_calculator.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/svg/ic_calculator_1.svg b/assets/svg/ic_calculator_1.svg new file mode 100644 index 0000000..b8da3e2 --- /dev/null +++ b/assets/svg/ic_calculator_1.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_calendar.svg b/assets/svg/ic_calendar.svg new file mode 100644 index 0000000..2ec4076 --- /dev/null +++ b/assets/svg/ic_calendar.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/svg/ic_check_box.svg b/assets/svg/ic_check_box.svg new file mode 100644 index 0000000..ea062b5 --- /dev/null +++ b/assets/svg/ic_check_box.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_chek.svg b/assets/svg/ic_chek.svg new file mode 100644 index 0000000..45bcc80 --- /dev/null +++ b/assets/svg/ic_chek.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_done.svg b/assets/svg/ic_done.svg new file mode 100644 index 0000000..15b8ba7 --- /dev/null +++ b/assets/svg/ic_done.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_edit.svg b/assets/svg/ic_edit.svg new file mode 100644 index 0000000..4d06951 --- /dev/null +++ b/assets/svg/ic_edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_empty_box.svg b/assets/svg/ic_empty_box.svg new file mode 100644 index 0000000..caef692 --- /dev/null +++ b/assets/svg/ic_empty_box.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/ic_list_1.svg b/assets/svg/ic_list_1.svg new file mode 100644 index 0000000..f85f5a0 --- /dev/null +++ b/assets/svg/ic_list_1.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_log_out.svg b/assets/svg/ic_log_out.svg new file mode 100644 index 0000000..73bf0c5 --- /dev/null +++ b/assets/svg/ic_log_out.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_logo_auth.svg b/assets/svg/ic_logo_auth.svg new file mode 100644 index 0000000..e38811d --- /dev/null +++ b/assets/svg/ic_logo_auth.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/ic_logo_lang.svg b/assets/svg/ic_logo_lang.svg new file mode 100644 index 0000000..a945ca5 --- /dev/null +++ b/assets/svg/ic_logo_lang.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/svg/ic_mail.svg b/assets/svg/ic_mail.svg new file mode 100644 index 0000000..102c4b7 --- /dev/null +++ b/assets/svg/ic_mail.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/ic_notification.svg b/assets/svg/ic_notification.svg new file mode 100644 index 0000000..99228e5 --- /dev/null +++ b/assets/svg/ic_notification.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_phone.svg b/assets/svg/ic_phone.svg new file mode 100644 index 0000000..2ebc393 --- /dev/null +++ b/assets/svg/ic_phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_profile.svg b/assets/svg/ic_profile.svg new file mode 100644 index 0000000..c75281d --- /dev/null +++ b/assets/svg/ic_profile.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/svg/ic_required.svg b/assets/svg/ic_required.svg new file mode 100644 index 0000000..415f40e --- /dev/null +++ b/assets/svg/ic_required.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/svg/ic_star_selected.svg b/assets/svg/ic_star_selected.svg new file mode 100644 index 0000000..f36a961 --- /dev/null +++ b/assets/svg/ic_star_selected.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_star_unselected.svg b/assets/svg/ic_star_unselected.svg new file mode 100644 index 0000000..4148d43 --- /dev/null +++ b/assets/svg/ic_star_unselected.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_sun.svg b/assets/svg/ic_sun.svg new file mode 100644 index 0000000..7be924d --- /dev/null +++ b/assets/svg/ic_sun.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_trash.svg b/assets/svg/ic_trash.svg new file mode 100644 index 0000000..8f69257 --- /dev/null +++ b/assets/svg/ic_trash.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/svg/ic_user.svg b/assets/svg/ic_user.svg new file mode 100644 index 0000000..daa3b05 --- /dev/null +++ b/assets/svg/ic_user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/ic_warning.svg b/assets/svg/ic_warning.svg new file mode 100644 index 0000000..5fc7b21 --- /dev/null +++ b/assets/svg/ic_warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ic_world.svg b/assets/svg/ic_world.svg new file mode 100644 index 0000000..dd53725 --- /dev/null +++ b/assets/svg/ic_world.svg @@ -0,0 +1,3 @@ + + + diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..3821316 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,35 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Podfile.lock +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..1dc6cf7 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..6649374 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '15.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! + + 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 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d6ab21b --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,769 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 08AA52AC2E20C7A6002761DA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 08AA52AB2E20C7A6002761DA /* GoogleService-Info.plist */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 29A5AEAF4C25E3D10E4E2EAE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 590561AFFA62840BE471FA6C /* Pods_Runner.framework */; }; + 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 */; }; + 87E868C2FBE0C304E9919659 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A86449E2E481DAF240A4C43 /* Pods_RunnerTests.framework */; }; + 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 */ + 08AA52AB2E20C7A6002761DA /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 08AA52AD2E20CB62002761DA /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3924EB4EFB20EBBD2B4ABC /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 590561AFFA62840BE471FA6C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 67B97F508EEB1822F385B2CA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9A86449E2E481DAF240A4C43 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BEB2D4DE482F97AD394FA281 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + C1ED55DE2008E052467D8EEE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + D615C076D73411EF04937461 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + E10A6C805F387C34EE3BB8DE /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 38773EE4B183EF6E545B4EEC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 87E868C2FBE0C304E9919659 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 29A5AEAF4C25E3D10E4E2EAE /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 451BA0A68A0E9F2643CAEE8B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 590561AFFA62840BE471FA6C /* Pods_Runner.framework */, + 9A86449E2E481DAF240A4C43 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 7A241B832056F7EF308569CD /* Pods */ = { + isa = PBXGroup; + children = ( + C1ED55DE2008E052467D8EEE /* Pods-Runner.debug.xcconfig */, + 3B3924EB4EFB20EBBD2B4ABC /* Pods-Runner.release.xcconfig */, + 67B97F508EEB1822F385B2CA /* Pods-Runner.profile.xcconfig */, + BEB2D4DE482F97AD394FA281 /* Pods-RunnerTests.debug.xcconfig */, + E10A6C805F387C34EE3BB8DE /* Pods-RunnerTests.release.xcconfig */, + D615C076D73411EF04937461 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 7A241B832056F7EF308569CD /* Pods */, + 451BA0A68A0E9F2643CAEE8B /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 08AA52AD2E20CB62002761DA /* Runner.entitlements */, + 08AA52AB2E20C7A6002761DA /* GoogleService-Info.plist */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 71508C70265B20D68CEDEB2B /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 38773EE4B183EF6E545B4EEC /* Frameworks */, + ); + 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 = ( + 43A38C6A8942D3DC83D35FFB /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + EC2C6A798B6BDC7BF0967B53 /* [CP] Embed Pods Frameworks */, + 29AE0CDADA67C9D2C65A81BC /* [CP] Copy Pods Resources */, + ); + 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 */, + 08AA52AC2E20C7A6002761DA /* GoogleService-Info.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 29AE0CDADA67C9D2C65A81BC /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + 43A38C6A8942D3DC83D35FFB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 71508C70265B20D68CEDEB2B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + EC2C6A798B6BDC7BF0967B53 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = G9PD7TR2FK; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "CTRANS logistics"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.3; + PRODUCT_BUNDLE_IDENTIFIER = com.cargocalculater.cargocalculaterapp; + 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; + baseConfigurationReference = BEB2D4DE482F97AD394FA281 /* Pods-RunnerTests.debug.xcconfig */; + 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.cargocalculater.cargocalculaterapp.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; + baseConfigurationReference = E10A6C805F387C34EE3BB8DE /* Pods-RunnerTests.release.xcconfig */; + 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.cargocalculater.cargocalculaterapp.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; + baseConfigurationReference = D615C076D73411EF04937461 /* Pods-RunnerTests.profile.xcconfig */; + 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.cargocalculater.cargocalculaterapp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = G9PD7TR2FK; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "CTRANS logistics"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.3; + PRODUCT_BUNDLE_IDENTIFIER = com.cargocalculater.cargocalculaterapp; + 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; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = G9PD7TR2FK; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "CTRANS logistics"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.3; + PRODUCT_BUNDLE_IDENTIFIER = com.cargocalculater.cargocalculaterapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c8b77a0 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,14 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..e8ebfde Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/102.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/102.png new file mode 100644 index 0000000..00ad2f3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/102.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..656ff01 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/108.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/108.png new file mode 100644 index 0000000..ce86046 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/108.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..db488c3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..6f02a2c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..ab10a6d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..2296727 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..2f1d78b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..35c5d30 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..8255359 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..f47b4ef Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..07ff66f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..1dd180e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..9b90952 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..8bf6045 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/234.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/234.png new file mode 100644 index 0000000..481e03d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/234.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..fa3b334 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/258.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/258.png new file mode 100644 index 0000000..119e644 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/258.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..1be6158 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..07366c4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..7005397 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..86e036d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..251b685 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..f0f4fb8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..a45afdb Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..c5a356b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..1bf72f2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..191ffb3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..866d477 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/66.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/66.png new file mode 100644 index 0000000..5e0d236 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/66.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..c149783 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..fff66c9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..30681ba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..49b3430 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..b1daa5e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/92.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/92.png new file mode 100644 index 0000000..bf611a0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/92.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..c139d9a --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,350 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + }, + { + "filename" : "48.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", + "subtype" : "38mm" + }, + { + "filename" : "55.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", + "subtype" : "42mm" + }, + { + "filename" : "58.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "66.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "33x33", + "subtype" : "45mm" + }, + { + "filename" : "80.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", + "subtype" : "38mm" + }, + { + "filename" : "88.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", + "subtype" : "40mm" + }, + { + "filename" : "92.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "46x46", + "subtype" : "41mm" + }, + { + "filename" : "100.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", + "subtype" : "44mm" + }, + { + "filename" : "102.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "51x51", + "subtype" : "45mm" + }, + { + "filename" : "108.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "54x54", + "subtype" : "49mm" + }, + { + "filename" : "172.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", + "subtype" : "38mm" + }, + { + "filename" : "196.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", + "subtype" : "42mm" + }, + { + "filename" : "216.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "108x108", + "subtype" : "44mm" + }, + { + "filename" : "234.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "117x117", + "subtype" : "45mm" + }, + { + "filename" : "258.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "129x129", + "subtype" : "49mm" + }, + { + "filename" : "1024.png", + "idiom" : "watch-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/1024.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/1024.png new file mode 100644 index 0000000..2ec6a58 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/1024.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/256.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/256.png new file mode 100644 index 0000000..fae45d6 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/256.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/512.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/512.png new file mode 100644 index 0000000..60a1b90 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/512.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..83681a2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "256.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "512.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "1024.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..7aa6dfb --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..96e6e08 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyDTwx66p44jPi0IPLWAzf0RwVBnBgn07h4 + GCM_SENDER_ID + 955070107009 + PLIST_VERSION + 1 + BUNDLE_ID + com.cargocalculater.cargocalculaterapp + PROJECT_ID + cargocalculator-46c5e + STORAGE_BUCKET + cargocalculator-46c5e.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:955070107009:ios:c91723f49eb66284a5ac80 + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..8d3cf48 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,64 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + CTRANS logistics + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + cargocalculaterapp + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSApplicationQueriesSchemes + + sms + tel + + LSRequiresIPhoneOS + + NSLocalNetworkUsageDescription + This app requires access to the local network for debugging purposes. + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + ITSAppUsesNonExemptEncryption + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..9caae07 --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,43 @@ +import 'package:cargocalculaterapp/router/app_routes.dart'; +import 'package:cargocalculaterapp/router/name_routes.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'core/app_bloc/app_bloc.dart'; +import 'core/theme/theme_data.dart'; +import 'generated/l10n.dart'; + +class App extends StatelessWidget { + const App({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return KeyboardDismissOnTap( + child: MaterialApp( + navigatorKey: rootNavigatorKey, + initialRoute: Routes.initial, + onGenerateRoute: AppRoutes.onGenerateRoute, + title: 'CargoApp', + debugShowCheckedModeBanner: false, + themeMode: ThemeMode.values.byName( + state.themeMode?.name ?? ThemeMode.light.name, + ), + theme: state.themeMode == ThemeMode.light ? lightTheme : darkTheme, + darkTheme: darkTheme, + locale: Locale(state.appLocale), + supportedLocales: AppLocalization.delegate.supportedLocales, + localizationsDelegates: const [ + AppLocalization.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + ), + ); + }, + ); + } +} diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart new file mode 100644 index 0000000..33cd478 --- /dev/null +++ b/lib/constants/constants.dart @@ -0,0 +1,130 @@ +import 'package:dio/dio.dart'; + +import '../core/entity/name/name_entity.dart'; + +class Constants { + static String baseUrl = "https://ctrans-api.felix-its.uz"; +} +class Validations { + static const someThingWentWrong = 'Something went wrong!'; +} + +class DioConstants { + static final options = Options( + contentType: 'application/json', + sendTimeout: const Duration(seconds: 30), + ); +} + +class AppKeys { + static const locale = 'locale'; + static const accessToken = 'access_token'; + static const firstName = 'firstname'; + static const lastName = 'lastname'; + static const email = 'email'; + static const isFirst = "isFirst"; + static const gender = "gender"; + static const userId = "userId"; + static const dateOfBirth = "dateOfBirth"; + static const refreshToken = "refreshToken"; + static const phone = "phone"; + static const hasProfile = "hasProfile"; + static const isProd = 'isProd'; + static const page = 'page'; + static const limit = 'limit'; + static const status = 'status'; + static const sort = 'sort'; + static const themeMode = 'themeMode'; + static const ucode = 'UCode'; +} + +class AppConst { + static const limit = 20; + static const reviewLimit = 10; + static const all = "all"; + static const status = "status"; + static const waiting = "WAITING"; + static const partReceived = "PART_RECEIVED"; + static const fullReceived = "FULL_RECEIVED"; + static const shipped = "SHIPPED"; + static const leftChine = "LEFT_CHINA"; + static const enteredUzbekistan = "ENTERED_UZBEKISTAN"; + static const tashkentWarehouse = "TASHKENT_WAREHOUSE"; + static const customsClearance = "CUSTOMS_CLEARANCE"; + static const delivered = "DELIVERED"; + static const clickAction = "page_link"; + static const Map statusList = { + all: NameEntity(uz: "Barchasi", ru: "Все", zh: "全部"), + waiting: NameEntity(uz: "Kutilmoqda", ru: "Ожидание", zh: "等待中"), + partReceived: NameEntity( + uz: "Yarim keldi", + ru: "Частичный прибыл", + zh: "部分接收", + ), + fullReceived: NameEntity( + uz: "To'liq keldi", + ru: "Полный прибл", + zh: "已全部接收", + ), + shipped: NameEntity(uz: "Yuborildi", ru: "Отправлено", zh: "已发货"), + leftChine: NameEntity( + uz: "Xitoydan chiqdi", + ru: "Покинул Китай", + zh: "离开中国", + ), + enteredUzbekistan: NameEntity( + uz: "O'zbekistonga kirdi", + ru: "Прибыл в Узбекистан", + zh: "进入乌兹别克斯坦", + ), + tashkentWarehouse: NameEntity( + uz: "Toshkent omborida", + ru: "На складе в Ташкенте", + zh: "塔什干仓库", + ), + customsClearance: NameEntity( + uz: "Bojxona rasmiylashtiruvi", + ru: "Таможенное оформление", + zh: "清关中", + ), + delivered: NameEntity(uz: "Yetkazib berildi", ru: "Доставлено", zh: "已送达"), + }; + static const warehouseOptions = { + "1": NameEntity(uz: "Yiwu", ru: "Иу", zh: "义乌"), + "2": NameEntity(uz: "Foshan", ru: "Фошань", zh: "佛山"), + "3": NameEntity(uz: "Qashqar", ru: "Кашгар", zh: "喀什"), + "4": NameEntity(uz: "Hargos", ru: "Харгос", zh: "哈戈斯"), + }; +} + +class Urls { + static const login = '/api/v1/auth/login/user'; + static const signUp = '/api/v1/auth/register'; + static const getProfile = '/api/v1/mobile/me'; + static const updateProfile = '/api/v1/mobile/me/{id}'; + static const verify = '/api/v1/auth/verify-sms'; + static const orderList = '/api/v1/mobile/orders'; + static const calculatePrice = '/api/v1/leads/calculate'; + static const lead = '/api/v1/leads'; + static const addFcm = '/api/v1/user/update-sfm-token'; + static const comment = '/api/v1/testimonials'; + static const notification = '/api/v1/notifications'; + static const notificationRead = '/api/v1/notifications/mark-as-read'; + static const banners = '/api/v1/slider'; + static const delete = '/api/v1/mobile/me'; +} + +class CacheKeys { + static const profile = "profile.cache"; +} + +class Warnings { + Warnings._(); + + static const someThingWentWrong = 'Something went wrong!'; +} + +class RegExConst { + static final phoneRegex = RegExp(r'^\+?\d{12}$'); + static final emailRegex = RegExp(r'^[a-zA-Z0-9._%+-]+@gmail\.com$'); +} diff --git a/lib/core/app_bloc/app_bloc.dart b/lib/core/app_bloc/app_bloc.dart new file mode 100644 index 0000000..1333795 --- /dev/null +++ b/lib/core/app_bloc/app_bloc.dart @@ -0,0 +1,46 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import '../../injector_container.dart'; +import '../local_source/local_source.dart'; +import '../theme/app_theme.dart'; + +part 'app_event.dart'; + +part 'app_state.dart'; + +class AppBloc extends Bloc { + AppBloc() + : super( + AppState( + lightTheme: lightThemes.values.first, + darkTheme: darkThemes.values.first, + themeMode: ThemeMode.values.byName(sl().getThemeMode()), + appLocale: sl().getLocale(), + ), + ) { + on(_switchToLightHandler); + on(_switchToDarkHandler); + on(_changeLocale); + } + + void _changeLocale(AppChangeLocale event, Emitter emit) { + sl().setLocale(event.appLocale); + emit(state.copyWith(appLocale: event.appLocale)); + } + + void _switchToLightHandler( + AppThemeSwitchLight event, + Emitter emit, + ) { + sl().setThemeMode(ThemeMode.light.name); + emit( + state.copyWith(lightTheme: event.lightTheme, themeMode: ThemeMode.light), + ); + } + + void _switchToDarkHandler(AppThemeSwitchDark event, Emitter emit) { + sl().setThemeMode(ThemeMode.dark.name); + emit(state.copyWith(darkTheme: event.darkTheme, themeMode: ThemeMode.dark)); + } +} diff --git a/lib/core/app_bloc/app_event.dart b/lib/core/app_bloc/app_event.dart new file mode 100644 index 0000000..b0ca836 --- /dev/null +++ b/lib/core/app_bloc/app_event.dart @@ -0,0 +1,32 @@ +part of 'app_bloc.dart'; + +sealed class AppEvent extends Equatable { + const AppEvent(); +} + +final class AppThemeSwitchLight extends AppEvent { + final ThemeData lightTheme; + + const AppThemeSwitchLight({required this.lightTheme}); + + @override + List get props => [lightTheme]; +} + +final class AppThemeSwitchDark extends AppEvent { + final ThemeData darkTheme; + + const AppThemeSwitchDark({required this.darkTheme}); + + @override + List get props => [darkTheme]; +} + +final class AppChangeLocale extends AppEvent { + final String appLocale; + + const AppChangeLocale(this.appLocale); + + @override + List get props => [appLocale]; +} diff --git a/lib/core/app_bloc/app_state.dart b/lib/core/app_bloc/app_state.dart new file mode 100644 index 0000000..b1a3ac5 --- /dev/null +++ b/lib/core/app_bloc/app_state.dart @@ -0,0 +1,37 @@ +part of 'app_bloc.dart'; + +class AppState extends Equatable { + final ThemeData? lightTheme; + final ThemeData? darkTheme; + final ThemeMode? themeMode; + final String appLocale; + + const AppState({ + required this.appLocale, + this.lightTheme, + this.darkTheme, + this.themeMode, + }); + + AppState copyWith({ + ThemeData? lightTheme, + ThemeData? darkTheme, + ThemeMode? themeMode, + String? appLocale, + }) { + return AppState( + lightTheme: lightTheme ?? this.lightTheme, + darkTheme: darkTheme ?? this.darkTheme, + themeMode: themeMode ?? this.themeMode, + appLocale: appLocale ?? this.appLocale, + ); + } + + @override + List get props => [ + lightTheme, + darkTheme, + themeMode, + appLocale, + ]; +} diff --git a/lib/core/entity/name/name_entity.dart b/lib/core/entity/name/name_entity.dart new file mode 100644 index 0000000..15bc866 --- /dev/null +++ b/lib/core/entity/name/name_entity.dart @@ -0,0 +1,12 @@ +import 'package:equatable/equatable.dart'; + +class NameEntity extends Equatable { + final String? uz; + final String? ru; + final String? zh; + + const NameEntity({this.uz, this.ru, this.zh}); + + @override + List get props => [uz, ru, zh]; +} diff --git a/lib/core/error/exceptions.dart b/lib/core/error/exceptions.dart new file mode 100644 index 0000000..7b7fe33 --- /dev/null +++ b/lib/core/error/exceptions.dart @@ -0,0 +1,26 @@ +class ServerException implements Exception { + final String message; + + ServerException({required this.message}); + + factory ServerException.fromJson(Map json) { + return ServerException(message: json['message']); + } +} + +class NoInternetException implements Exception {} + +class CacheException implements Exception { + final String message; + + CacheException({required this.message}); + + @override + String toString() { + return message; + } +} + +class MultipleOrdersException implements Exception { + MultipleOrdersException(); +} diff --git a/lib/core/error/failure.dart b/lib/core/error/failure.dart new file mode 100644 index 0000000..64339cb --- /dev/null +++ b/lib/core/error/failure.dart @@ -0,0 +1,33 @@ +import 'package:equatable/equatable.dart'; + +abstract class Failure extends Equatable {} + +class ServerFailure extends Failure { + final String message; + + ServerFailure({required this.message}); + + @override + List get props => [message]; +} + +class NoInternetFailure extends Failure { + @override + List get props => []; +} + +class CacheFailure extends Failure { + final String message; + + CacheFailure({required this.message}); + + @override + List get props => [message]; +} + +class MultipleOrderFailure extends Failure { + MultipleOrderFailure(); + + @override + List get props => []; +} diff --git a/lib/core/extension/build_context_extension.dart b/lib/core/extension/build_context_extension.dart new file mode 100644 index 0000000..2bb3ee4 --- /dev/null +++ b/lib/core/extension/build_context_extension.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +import '../theme/colors/app_colors.dart'; +import '../theme/theme_text_style.dart'; + +extension BuildContextExt on BuildContext { + ThemeTextStyles get text => Theme.of(this).extension()!; + + AppThemeColors get color => Theme.of(this).extension()!; + + bool get isDarkMode => Theme.of(this).brightness == Brightness.dark; +} diff --git a/lib/core/extension/color_extension.dart b/lib/core/extension/color_extension.dart new file mode 100644 index 0000000..425d0de --- /dev/null +++ b/lib/core/extension/color_extension.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +extension HexColor on Color { + /// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#". + static Color fromHex(String hexString) { + final buffer = StringBuffer(); + if (hexString.length == 6 || hexString.length == 7) buffer.write('ff'); + buffer.write(hexString.replaceFirst('#', '')); + return Color(int.parse(buffer.toString(), radix: 16)); + } +} diff --git a/lib/core/extension/extension.dart b/lib/core/extension/extension.dart new file mode 100644 index 0000000..ce0a2d8 --- /dev/null +++ b/lib/core/extension/extension.dart @@ -0,0 +1,13 @@ +extension SliverCountExtension on int { + int get doubleTheListCount => (this * 2) - 1; + + int get exactIndex => (this ~/ 2); + + int get lastIndex => (this * 2) - 2; +} + +extension VersionParsing on String { + int toVersion() { + return int.tryParse(replaceAll(".", "")) ?? 0; + } +} diff --git a/lib/core/extension/size_extentione.dart b/lib/core/extension/size_extentione.dart new file mode 100644 index 0000000..81e4c49 --- /dev/null +++ b/lib/core/extension/size_extentione.dart @@ -0,0 +1,12 @@ +import 'package:flutter/cupertino.dart'; +import '../../router/app_routes.dart'; + +extension ScreenSize on double { + double get w { + return this / 375 * MediaQuery.sizeOf(rootNavigatorKey.currentContext!).width; + } + + double get h { + return this / 812 * MediaQuery.sizeOf(rootNavigatorKey.currentContext!).height; + } +} \ No newline at end of file diff --git a/lib/core/functions/base_finctions.dart b/lib/core/functions/base_finctions.dart new file mode 100644 index 0000000..68b8860 --- /dev/null +++ b/lib/core/functions/base_finctions.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import '../../router/app_routes.dart'; +import '../entity/name/name_entity.dart'; +import '../models/name.dart'; + +class Functions { + static String getTranslatedItem(NameEntity? name, BuildContext context) { + var local = Localizations.localeOf(context); + switch (local.languageCode) { + case 'uz': + { + return name?.uz ?? " "; + } + case "ru": + { + return name?.ru ?? " "; + } + case "zh": + { + return name?.zh ?? " "; + } + default: + { + return name?.uz ?? " "; + } + } + } + + static String getTranslatedNameModel(Name? name, BuildContext context) { + var local = Localizations.localeOf(context); + switch (local.languageCode) { + case 'uz': + { + return name?.uz ?? " "; + } + case "ru": + { + return name?.ru ?? " "; + } + case "zh": + { + return name?.zh ?? " "; + } + default: + { + return name?.uz ?? " "; + } + } + } + static void showErrorSnackBar(String message) { + final snackBar = SnackBar( + backgroundColor: Colors.red, + content: Row( + children: [ + const Icon(Icons.error, color: Colors.white), + const SizedBox(width: 8), + Expanded( + child: Text( + message, + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 3), + ); + + ScaffoldMessenger.of( + rootNavigatorKey.currentContext!, + ).showSnackBar(snackBar); + } +} diff --git a/lib/core/functions/debouncer.dart b/lib/core/functions/debouncer.dart new file mode 100644 index 0000000..7b4e82c --- /dev/null +++ b/lib/core/functions/debouncer.dart @@ -0,0 +1,13 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; + +class Debouncer { + static Timer? _timer; + + static run(VoidCallback action) { + if (null != _timer) { + _timer?.cancel(); + } + _timer = Timer(const Duration(milliseconds: 600), action); + } +} diff --git a/lib/core/input_format/custom_text_input_format.dart b/lib/core/input_format/custom_text_input_format.dart new file mode 100644 index 0000000..b639723 --- /dev/null +++ b/lib/core/input_format/custom_text_input_format.dart @@ -0,0 +1,37 @@ +import 'package:flutter/services.dart'; + +class CustomTextInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + var re = RegExp(('[^0-9]')); + if (newValue.selection.baseOffset == 0 && newValue.text.isNotEmpty) { + return newValue; + } + if (newValue.text.isEmpty) { + return newValue.copyWith(text: ''); + } else if (newValue.text.compareTo(oldValue.text) != 0) { + final selectionIndexFromTheRight = + newValue.text.length - newValue.selection.extentOffset; + final reversedText = + String.fromCharCodes(newValue.text.runes.toList().reversed); + final chars = reversedText.replaceAll(re, '').split(''); + var reversedNewString = ''; + for (var i = 0; i < chars.length; i++) { + if (i % 3 == 0 && i != 0) reversedNewString += ' '; + reversedNewString += chars[i]; + } + final newString = + String.fromCharCodes(reversedNewString.runes.toList().reversed); + + return TextEditingValue( + text: newString, + selection: TextSelection.collapsed( + offset: newString.length - selectionIndexFromTheRight, + ), + ); + } else { + return newValue; + } + } +} \ No newline at end of file diff --git a/lib/core/input_format/mask_text_formatter.dart b/lib/core/input_format/mask_text_formatter.dart new file mode 100644 index 0000000..73173b5 --- /dev/null +++ b/lib/core/input_format/mask_text_formatter.dart @@ -0,0 +1,64 @@ +import 'package:flutter/services.dart'; + +class MaskedTextInputFormatter extends TextInputFormatter { + final String mask; + final String separator; + + MaskedTextInputFormatter({ + required this.mask, + required this.separator, + }); + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + String text = newValue.text; + String newText = newValue.toJSON()['text'].toString(); + if (text.isNotEmpty) { + if (text.length > oldValue.text.length) { + if (text.length > mask.length) return oldValue; + if (text.length < mask.length && mask[text.length - 1] == separator) { + return TextEditingValue( + text: + '${oldValue.text}$separator${text.substring(text.length - 1)}', + selection: TextSelection.collapsed( + offset: newValue.selection.end + 1, + ), + ); + } + if (text.length == mask.replaceAll(separator, "").length && + oldValue.text.isEmpty) { + String newText = ''; + int t = 0; + for (int i = 0; i < text.length; i++) { + if (mask[i + t] == separator) { + newText += separator; + t++; + } + newText += text[i]; + } + return TextEditingValue( + text: newText, + selection: TextSelection.collapsed(offset: newText.length), + ); + } + } else { + if (newText.substring(newText.length - 1) == separator) { + return TextEditingValue( + text: newValue.text.substring(0, newValue.text.length - 1), + selection: TextSelection.collapsed( + offset: newValue.selection.end - 1, + ), + ); + } + } + return TextEditingValue( + text: newText, + selection: TextSelection.collapsed( + offset: newValue.selection.end, + ), + ); + } + return newValue; + } +} diff --git a/lib/core/local_source/local_source.dart b/lib/core/local_source/local_source.dart new file mode 100644 index 0000000..abda609 --- /dev/null +++ b/lib/core/local_source/local_source.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; + +import '../../constants/constants.dart'; + +class LocalSource { + final Box box; + + LocalSource(this.box); + + Future clear() async { + await box.clear(); + } + + Future setLocale(String locale) async { + await box.put(AppKeys.locale, locale); + } + + String getLocale() { + return box.get(AppKeys.locale, defaultValue: "uz") ?? "uz"; + } + + void setAccessToken(String accessToken) { + box.put(AppKeys.accessToken, accessToken); + } + + String? getAccessToken() { + return box.get(AppKeys.accessToken); + } + + void setFirstName(String firstName) { + box.put(AppKeys.firstName, firstName); + } + + String? getFirstName() { + return box.get(AppKeys.firstName); + } + + void setLastName(String lastName) { + box.put(AppKeys.lastName, lastName); + } + + String? getLastName() { + return box.get(AppKeys.lastName); + } + + void setEmail(String email) { + box.put(AppKeys.email, email); + } + + String? getEmail() { + return box.get(AppKeys.email); + } + + void setIsFirstEnter(bool isFirst) { + box.put(AppKeys.isFirst, isFirst); + } + + bool getIsFirstEnter() { + return box.get(AppKeys.isFirst) ?? true; + } + + void setUserId(String id) { + box.put(AppKeys.userId, id); + } + + String getUserId() { + return box.get(AppKeys.userId) ?? ""; + } + + void setDateOfBirth(String id) { + box.put(AppKeys.dateOfBirth, id); + } + + String getDateOfBirth() { + return box.get(AppKeys.dateOfBirth) ?? ""; + } + + void setRefreshToken(String id) { + box.put(AppKeys.refreshToken, id); + } + + String getRefreshToken() { + return box.get(AppKeys.refreshToken) ?? ""; + } + + void setPhone(String id) { + box.put(AppKeys.phone, id); + } + + String getPhone() { + return box.get(AppKeys.phone) ?? ""; + } + + void setHasProfile(bool id) { + box.put(AppKeys.hasProfile, id); + } + + bool getHasProfile() { + return box.get(AppKeys.hasProfile) ?? false; + } + + void setGender(String id) { + box.put(AppKeys.gender, id); + } + + String getGender() { + return box.get(AppKeys.gender) ?? ""; + } + + void setIsProd(bool isProd) { + box.put(AppKeys.isProd, isProd); + } + + bool getIsProd() { + return box.get(AppKeys.isProd) ?? true; + } + + void setThemeMode(String mode) { + box.put(AppKeys.themeMode, mode); + } + + String getThemeMode() { + return box.get(AppKeys.themeMode) ?? ThemeMode.light.name; + } + + void setUCode(String mode) { + box.put(AppKeys.ucode, mode); + } + + String getUCode() { + return box.get(AppKeys.ucode) ?? ""; + } +} diff --git a/lib/core/models/name.dart b/lib/core/models/name.dart new file mode 100644 index 0000000..1bdf8a2 --- /dev/null +++ b/lib/core/models/name.dart @@ -0,0 +1,24 @@ +import 'package:equatable/equatable.dart'; + +class Name extends Equatable { + const Name({this.uz, this.ru, this.zh}); + + factory Name.fromJson(dynamic json) { + return Name(uz: json['uz'], ru: json['ru'], zh: json['zh']); + } + + final String? uz; + final String? ru; + final String? zh; + + Map toJson() { + final map = {}; + map['uz'] = uz; + map['ru'] = ru; + map['zh'] = zh; + return map; + } + + @override + List get props => [uz, ru, zh]; +} diff --git a/lib/core/platform/network_info.dart b/lib/core/platform/network_info.dart new file mode 100644 index 0000000..db0a3d4 --- /dev/null +++ b/lib/core/platform/network_info.dart @@ -0,0 +1,14 @@ +import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; + +abstract class NetworkInfo { + Future get isConnected; +} + +class NetworkInfoImpl implements NetworkInfo{ + final InternetConnection internetConnectionCheckerPlus; + + NetworkInfoImpl(this.internetConnectionCheckerPlus); + + @override + Future get isConnected => internetConnectionCheckerPlus.hasInternetAccess; +} \ No newline at end of file diff --git a/lib/core/theme/app_text_styles.dart b/lib/core/theme/app_text_styles.dart new file mode 100644 index 0000000..1e24523 --- /dev/null +++ b/lib/core/theme/app_text_styles.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; + +import 'colors/app_colors.dart'; + +class AppTextStyles { + AppTextStyles._(); + + static const profileCategory = TextStyle( + color: LightThemeColors.textColor, + fontSize: 16, + fontWeight: FontWeight.w500, + ); + static const titleBig = TextStyle( + color: LightThemeColors.textColor, + fontSize: 24, + fontWeight: FontWeight.w700, + ); + static const authTitle = TextStyle( + color: LightThemeColors.textColor, + fontSize: 20, + fontWeight: FontWeight.w700, + ); + static const timer = TextStyle( + color: ThemeColors.timerRed, + fontSize: 12, + fontWeight: FontWeight.w400, + ); + static const categoryName = TextStyle( + color: LightThemeColors.textColor, + fontSize: 14, + fontWeight: FontWeight.w500, + height: 1, + letterSpacing: 0, + ); + static const bigTitle = TextStyle( + color: LightThemeColors.textColor, + fontSize: 20, + fontWeight: FontWeight.w600, + ); + static const orderTimer = TextStyle( + color: ThemeColors.timerRed, + fontSize: 16, + fontWeight: FontWeight.w400, + ); + static const timerStyle = TextStyle( + color: ThemeColors.timerRed, + fontSize: 16, + fontWeight: FontWeight.w400, + ); + static const saleRed = TextStyle( + color: ThemeColors.timerRed, + fontSize: 16, + fontWeight: FontWeight.w700, + ); + static const timerDesc = TextStyle( + color: ThemeColors.timerRed, + fontSize: 16, + fontWeight: FontWeight.w600, + ); + static const statusText = TextStyle( + color: LightThemeColors.buttonColorText, + fontSize: 13, + fontWeight: FontWeight.w600, + ); + + static const language = TextStyle( + color: LightThemeColors.textColor, + fontSize: 14, + fontWeight: FontWeight.w400, + ); + static const languageBig = TextStyle( + color: LightThemeColors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ); + + static const authDesc = TextStyle( + color: LightThemeColors.secondaryText, + fontSize: 14, + fontWeight: FontWeight.w400, + ); + static const timerBlue = TextStyle( + color: LightThemeColors.primaryColor, + fontSize: 14, + fontWeight: FontWeight.w500, + ); + static const orderListTitle = TextStyle( + color: LightThemeColors.black40Transparent, + fontSize: 10, + fontWeight: FontWeight.w500, + height: 1, + ); + static const status = TextStyle( + color: ThemeColors.green173text, + fontSize: 14, + fontWeight: FontWeight.w600, + height: 1, + letterSpacing: 0, + ); + static const orderTitle = TextStyle( + color: LightThemeColors.textColor, + fontSize: 16, + fontWeight: FontWeight.w700, + ); + static const statusNumber = TextStyle( + color: LightThemeColors.statusNumber, + fontSize: 12, + fontWeight: FontWeight.w400, + ); + static const statusDesc = TextStyle( + color: LightThemeColors.textColor, + fontSize: 12, + fontWeight: FontWeight.w600, + ); + static const secondaryText14 = TextStyle( + color: LightThemeColors.lightSecondary, + fontSize: 14, + fontWeight: FontWeight.w500, + ); +} diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart new file mode 100644 index 0000000..60e757b --- /dev/null +++ b/lib/core/theme/app_theme.dart @@ -0,0 +1,14 @@ +import 'package:cargocalculaterapp/core/theme/theme_data.dart'; +import 'package:flutter/material.dart'; + +enum LightThemes { violetTheme } + +enum DarkThemes { violetTheme } + +Map lightThemes = { + LightThemes.violetTheme: lightTheme, +}; + +Map darkThemes = { + DarkThemes.violetTheme: darkTheme, +}; diff --git a/lib/core/theme/colors/app_colors.dart b/lib/core/theme/colors/app_colors.dart new file mode 100644 index 0000000..27742c5 --- /dev/null +++ b/lib/core/theme/colors/app_colors.dart @@ -0,0 +1,273 @@ +import 'package:flutter/material.dart'; + +class ThemeColors { + static const timerRed = Color(0xFFCF0000); + static const successInputSMS = Color(0xFF36B82C); + static const warningSurface = Color(0xFFFFEFE4); + static const green173Light = Color(0xFFC7FCF6); + static const green191Light = Color(0xFFE9FBFF); + static const warning = Color(0xFFEE5800); + static const green173text = Color(0xFF02685E); + static const green191text = Color(0xFF00668F); +} + +class LightThemeColors extends ThemeColors { + static const dividerColor = Color(0x0dFFFFFF); + static const accentColor = Color(0xFF4275ba); // Neon Yellow + static const darkBackground = Color(0xFF171717); // Dark Grey + static const grey = Color(0xFF242424); // Grey + static const midGrey = Color(0xFF474747); // Mid Grey + static const lightGrey = Color(0xFF747474); // Light Grey + static const white = Color(0xFFffffff); + static const white60Transparent = Color(0x99FFFFFF); + static const white30Transparent = Color(0x4dFFFFFF); + static const white10Transparent = Color(0x1aFFFFFF); + static const white20Transparent = Color(0x33FFFFFF); + static const white40Transparent = Color(0x66FFFFFF); + static const errorColor = Color(0xFFCF0000); + + ///shimmer + static const backgroundGray = Color(0x80FFFFFF); + static const shimmerHighlight = Color(0xffffffff); + static const statusNumber = Color(0xff495057); + + ///new colors + static const primaryColor = Color(0xff0074B0); + static const textColor = Color(0xFF0C0C0C); + static const secondaryText = Color(0xFF85938E); + static const scaffoldBackgroundColor = Color(0xFFffffff); + static const borderColor = Color(0xFF707479); + static const edittextBackground = Color(0xFFF8FBFC); + static const buttonColorText = Color(0xffffffff); + static const disabledColor = Color(0x331783B2); + static const iconBackground = Color(0xffE3F3F6); + static const lightSecondary = Color(0xff858D93); + static const grayBackground = Color(0xffF8FBFC); + static const statusBackground = Color(0xffffffff); + static const lightBorder = Color(0xffE3E3E3); + static const black40Transparent = Color(0x66000000); + static const statusIconBackground = Color(0xffF1F3F5); +} + +class DarkThemeColors { + static const dividerColor = Color(0x0dFFFFFF); + static const accentColor = Color(0xFF4275ba); // Neon Yellow + static const darkBackground = Color(0xFF171717); // Dark Grey + static const grey = Color(0xFF242424); // Grey + static const midGrey = Color(0xFF474747); // Mid Grey + static const lightGrey = Color(0xFF747474); // Light Grey + static const white = Colors.white; + static const white60Transparent = Color(0x99FFFFFF); + static const white30Transparent = Color(0x4dFFFFFF); + static const white10Transparent = Color(0x1aFFFFFF); + static const errorColor = Color(0xFFCF0000); + + ///new colors + static const primaryColor = Color(0xff1783B2); + static const textColor = Color(0xFFFFFFFF); + static const secondaryText = Color(0xFFC7C7C7); + static const scaffoldBackgroundColor = Color(0xFF171717); + static const borderColor = Color(0xFF707479); + static const edittextBackground = Color(0xFF000000); + static const buttonColorText = Color(0xffffffff); + static const disabledColor = Color(0x331783B2); + static const iconBackground = Color(0xff1A272D); + static const lightSecondary = Color(0xffc7c7c7); + static const grayBackground = Color(0xff000000); + static const statusBackground = Color(0xff1A272D); + static const lightBorder = Color(0xff252525); + static const statusIconBackground = Color(0xff000000); +} + +@immutable +class AppThemeColors extends ThemeExtension { + final Color scaffoldBackgroundColor; + final Color textColor; + final Color dividerColor; + final Color accentColor; + final Color darkBackground; + final Color grey; + final Color midGrey; + final Color lightGrey; + final Color white; + final Color white60Transparent; + final Color white30Transparent; + final Color white10Transparent; + final Color buttonColorText; + + ///newcolors + final Color primaryColor; + final Color secondaryText; + final Color borderColor; + final Color edittextBackground; + final Color iconBackground; + final Color lightSecondary; + final Color grayBackground; + final Color statusBackground; + final Color lightBorder; + final Color statusIconBackground; + + const AppThemeColors({ + required this.scaffoldBackgroundColor, + required this.textColor, + required this.dividerColor, + required this.accentColor, + required this.darkBackground, + required this.grey, + required this.midGrey, + required this.lightGrey, + required this.white, + required this.white60Transparent, + required this.white30Transparent, + required this.white10Transparent, + required this.buttonColorText, + required this.primaryColor, + required this.secondaryText, + required this.borderColor, + required this.edittextBackground, + required this.iconBackground, + required this.lightSecondary, + required this.grayBackground, + required this.statusBackground, + required this.lightBorder, + required this.statusIconBackground, + }); + + @override + ThemeExtension copyWith({ + Color? scaffoldBackgroundColor, + Color? textColor, + Color? dividerColor, + Color? accentColor, + Color? darkBackground, + Color? grey, + Color? midGrey, + Color? lightGrey, + Color? white, + Color? white60Transparent, + Color? white30Transparent, + Color? white10Transparent, + Color? buttonColorText, + Color? primaryColor, + Color? secondaryText, + Color? borderColor, + Color? edittextBackground, + Color? iconBackground, + Color? lightSecondary, + Color? grayBackground, + Color? statusBackground, + Color? lightBorder, + Color? statusIconBackground, + }) { + return AppThemeColors( + scaffoldBackgroundColor: + scaffoldBackgroundColor ?? this.scaffoldBackgroundColor, + textColor: textColor ?? this.textColor, + dividerColor: dividerColor ?? this.dividerColor, + accentColor: accentColor ?? this.accentColor, + darkBackground: darkBackground ?? this.darkBackground, + grey: grey ?? this.grey, + midGrey: midGrey ?? this.midGrey, + lightGrey: lightGrey ?? this.lightGrey, + white: white ?? this.white, + white60Transparent: white60Transparent ?? this.white60Transparent, + white30Transparent: white30Transparent ?? this.white30Transparent, + white10Transparent: white10Transparent ?? this.white10Transparent, + buttonColorText: buttonColorText ?? this.buttonColorText, + primaryColor: primaryColor ?? this.primaryColor, + secondaryText: secondaryText ?? this.secondaryText, + borderColor: borderColor ?? this.borderColor, + edittextBackground: edittextBackground ?? this.edittextBackground, + iconBackground: iconBackground ?? this.iconBackground, + lightSecondary: lightSecondary ?? this.lightSecondary, + grayBackground: grayBackground ?? this.grayBackground, + statusBackground: statusBackground ?? this.statusBackground, + lightBorder: lightBorder ?? this.lightBorder, + statusIconBackground: statusIconBackground ?? this.statusIconBackground, + ); + } + + @override + AppThemeColors lerp(ThemeExtension? other, double t) { + if (other is! AppThemeColors) return this; + + return AppThemeColors( + scaffoldBackgroundColor: Color.lerp(scaffoldBackgroundColor, other.scaffoldBackgroundColor, t)!, + textColor: Color.lerp(textColor, other.textColor, t)!, + dividerColor: Color.lerp(dividerColor, other.dividerColor, t)!, + accentColor: Color.lerp(accentColor, other.accentColor, t)!, + darkBackground: Color.lerp(darkBackground, other.darkBackground, t)!, + grey: Color.lerp(grey, other.grey, t)!, + midGrey: Color.lerp(midGrey, other.midGrey, t)!, + lightGrey: Color.lerp(lightGrey, other.lightGrey, t)!, + white: Color.lerp(white, other.white, t)!, + white60Transparent: Color.lerp(white60Transparent, other.white60Transparent, t)!, + white30Transparent: Color.lerp(white30Transparent, other.white30Transparent, t)!, + white10Transparent: Color.lerp(white10Transparent, other.white10Transparent, t)!, + buttonColorText: Color.lerp(buttonColorText, other.buttonColorText, t)!, + primaryColor: Color.lerp(primaryColor, other.primaryColor, t)!, + secondaryText: Color.lerp(secondaryText, other.secondaryText, t)!, + borderColor: Color.lerp(borderColor, other.borderColor, t)!, + edittextBackground: Color.lerp(edittextBackground, other.edittextBackground, t)!, + iconBackground: Color.lerp(iconBackground, other.iconBackground, t)!, + lightSecondary: Color.lerp(lightSecondary, other.lightSecondary, t)!, + grayBackground: Color.lerp(grayBackground, other.grayBackground, t)!, + statusBackground: Color.lerp(statusBackground, other.statusBackground, t)!, + lightBorder: Color.lerp(lightBorder, other.lightBorder, t)!, + statusIconBackground: Color.lerp(statusIconBackground, other.statusIconBackground, t)!, + ); + } + + + static get light => const AppThemeColors( + scaffoldBackgroundColor: LightThemeColors.scaffoldBackgroundColor, + textColor: LightThemeColors.textColor, + dividerColor: LightThemeColors.dividerColor, + accentColor: LightThemeColors.accentColor, + darkBackground: LightThemeColors.darkBackground, + grey: LightThemeColors.grey, + midGrey: LightThemeColors.midGrey, + lightGrey: LightThemeColors.lightGrey, + white: LightThemeColors.white, + white60Transparent: LightThemeColors.white60Transparent, + white30Transparent: LightThemeColors.white30Transparent, + white10Transparent: LightThemeColors.white10Transparent, + buttonColorText: LightThemeColors.buttonColorText, + primaryColor: LightThemeColors.primaryColor, + secondaryText: LightThemeColors.secondaryText, + borderColor: LightThemeColors.borderColor, + edittextBackground: LightThemeColors.edittextBackground, + iconBackground: LightThemeColors.iconBackground, + lightSecondary: LightThemeColors.lightSecondary, + grayBackground: LightThemeColors.grayBackground, + statusBackground: LightThemeColors.statusBackground, + lightBorder: LightThemeColors.lightBorder, + statusIconBackground: LightThemeColors.statusIconBackground, + ); + + static get dark => const AppThemeColors( + scaffoldBackgroundColor: DarkThemeColors.scaffoldBackgroundColor, + textColor: DarkThemeColors.textColor, + dividerColor: DarkThemeColors.dividerColor, + accentColor: DarkThemeColors.accentColor, + darkBackground: DarkThemeColors.darkBackground, + grey: DarkThemeColors.grey, + midGrey: DarkThemeColors.midGrey, + lightGrey: DarkThemeColors.lightGrey, + white: DarkThemeColors.white, + white60Transparent: DarkThemeColors.white60Transparent, + white30Transparent: DarkThemeColors.white30Transparent, + white10Transparent: DarkThemeColors.white10Transparent, + buttonColorText: DarkThemeColors.buttonColorText, + primaryColor: DarkThemeColors.primaryColor, + secondaryText: DarkThemeColors.secondaryText, + borderColor: DarkThemeColors.borderColor, + edittextBackground: DarkThemeColors.edittextBackground, + iconBackground: DarkThemeColors.iconBackground, + lightSecondary: DarkThemeColors.lightSecondary, + grayBackground: DarkThemeColors.grayBackground, + statusBackground: DarkThemeColors.statusBackground, + lightBorder: DarkThemeColors.lightBorder, + statusIconBackground: DarkThemeColors.statusIconBackground, + ); +} diff --git a/lib/core/theme/theme_data.dart b/lib/core/theme/theme_data.dart new file mode 100644 index 0000000..1e85d02 --- /dev/null +++ b/lib/core/theme/theme_data.dart @@ -0,0 +1,434 @@ +import 'package:cargocalculaterapp/core/theme/theme_text_style.dart'; +import 'package:flutter/material.dart'; +import 'colors/app_colors.dart'; + +final appTheme = ThemeData( + fontFamily: "Inter", + extensions: >[ + AppThemeColors.light, + AppThemeColors.dark, + ThemeTextStyles.light, + ThemeTextStyles.dark, + ], + useMaterial3: true, + applyElevationOverlayColor: true, + // disabledColor: ThemeColors.disabledColor, + // splashColor: ThemeColors.primary1Transparent, + // focusColor: ThemeColors.primaryColor, + // colorSchemeSeed: ThemeColors.primaryColor, + visualDensity: VisualDensity.standard, + materialTapTargetSize: MaterialTapTargetSize.padded, + pageTransitionsTheme: const PageTransitionsTheme( + builders: { + TargetPlatform.android: CupertinoPageTransitionsBuilder(), + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + }, + ), + // textButtonTheme: TextButtonThemeData( + // style: ButtonStyle( + // padding: MaterialStateProperty.all( + // EdgeInsets.zero, + // ), + // ), + // ), + dividerTheme: const DividerThemeData(thickness: 1), +); + +final lightTheme = appTheme.copyWith( + extensions: >[ + AppThemeColors.light, + ThemeTextStyles.light, + ], + scaffoldBackgroundColor: LightThemeColors.scaffoldBackgroundColor, + brightness: Brightness.light, + dividerTheme: appTheme.dividerTheme.copyWith( + color: LightThemeColors.dividerColor, + ), + colorScheme: const ColorScheme.light( + secondary: LightThemeColors.white, + primary: LightThemeColors.accentColor, + ), + // listTileTheme: const ListTileThemeData( + // minVerticalPadding: 14, + // minLeadingWidth: 16, + // horizontalTitleGap: 12, + // tileColor: LightThemeColors.textFieldBackGround, + // selectedColor: LightThemeColors.backgroundColor, + // selectedTileColor: LightThemeColors.backgroundColor, + // shape: RoundedRectangleBorder( + // borderRadius: AppUtils.kBorderRadius4, + // ), + // ), + tabBarTheme: TabBarThemeData( + labelColor: LightThemeColors.accentColor, + unselectedLabelColor: LightThemeColors.grey, + labelPadding: EdgeInsets.zero, + labelStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w400), + unselectedLabelStyle: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + ), + indicator: const UnderlineTabIndicator( + borderSide: BorderSide(color: LightThemeColors.accentColor, width: 2.0), + ), + overlayColor: WidgetStateProperty.resolveWith((states) { + return LightThemeColors.lightGrey; + }), + ), + appBarTheme: const AppBarTheme( + elevation: 0, + centerTitle: true, + scrolledUnderElevation: 0, + foregroundColor: LightThemeColors.scaffoldBackgroundColor, + backgroundColor: LightThemeColors.white, + surfaceTintColor: LightThemeColors.scaffoldBackgroundColor, + shadowColor: Colors.black, + titleTextStyle: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: LightThemeColors.textColor, + ), + toolbarHeight: 56, + iconTheme: IconThemeData(color: LightThemeColors.primaryColor), + ), + inputDecorationTheme: const InputDecorationTheme( + contentPadding: EdgeInsets.all(16), + alignLabelWithHint: true, + labelStyle: TextStyle( + color: LightThemeColors.white, + fontSize: 16, + fontWeight: FontWeight.w400, + ), + hintStyle: TextStyle( + color: LightThemeColors.secondaryText, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: LightThemeColors.errorColor), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: LightThemeColors.borderColor), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: LightThemeColors.borderColor), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: LightThemeColors.primaryColor), + ), + disabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: LightThemeColors.white10Transparent), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: LightThemeColors.errorColor), + ), + filled: true, + isDense: true, + fillColor: LightThemeColors.edittextBackground, + floatingLabelAlignment: FloatingLabelAlignment.start, + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ButtonStyle( + overlayColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + if (states.contains(WidgetState.hovered)) { + return LightThemeColors.errorColor; + } + return null; + }), + elevation: WidgetStateProperty.all(0), + textStyle: WidgetStateProperty.all( + const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: LightThemeColors.textColor, + ), + ), + foregroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return LightThemeColors.white; + } else { + return LightThemeColors.white; + } + }), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return LightThemeColors.disabledColor; + } else { + return LightThemeColors.primaryColor; + } + }), + maximumSize: WidgetStateProperty.all( + const Size(double.infinity, 56), + ), + minimumSize: WidgetStateProperty.all( + const Size(double.infinity, 56), + ), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(24)), + ), + ), + ), + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: LightThemeColors.scaffoldBackgroundColor, + type: BottomNavigationBarType.fixed, + showSelectedLabels: true, + selectedLabelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + unselectedLabelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + unselectedItemColor: LightThemeColors.lightSecondary, + selectedItemColor: LightThemeColors.primaryColor, + selectedIconTheme: IconThemeData(size: 25), + unselectedIconTheme: IconThemeData(size: 25), + elevation: 2, + ), + textButtonTheme: TextButtonThemeData( + style: ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + ), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + elevation: WidgetStateProperty.all(2), + foregroundColor: WidgetStateProperty.resolveWith((states) { + return LightThemeColors.scaffoldBackgroundColor; + }), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return Colors.transparent; + } else { + return Colors.transparent; + } + }), + maximumSize: WidgetStateProperty.all( + const Size(double.infinity, double.infinity), + ), + minimumSize: WidgetStateProperty.all( + const Size(double.minPositive, double.minPositive), + ), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + ), + textStyle: WidgetStateProperty.all( + const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: LightThemeColors.accentColor, + ), + ), + // overlayColor: WidgetStateProperty.resolveWith((states) { + // return LightThemeColors.lightGrey; + // }), + ), + ), + bottomSheetTheme: const BottomSheetThemeData( + backgroundColor: Colors.transparent, + // surfaceTintColor: Colors.transparent, + modalBackgroundColor: Colors.transparent, + modalElevation: 0, + elevation: 0, + ), +); + +final darkTheme = appTheme.copyWith( + extensions: >[ + AppThemeColors.dark, + ThemeTextStyles.dark, + ], + scaffoldBackgroundColor: DarkThemeColors.scaffoldBackgroundColor, + brightness: Brightness.dark, + dividerTheme: appTheme.dividerTheme.copyWith( + color: DarkThemeColors.dividerColor, + ), + colorScheme: const ColorScheme.dark(secondary: Colors.white), + tabBarTheme: TabBarThemeData( + labelColor: DarkThemeColors.accentColor, + unselectedLabelColor: DarkThemeColors.grey, + labelPadding: EdgeInsets.zero, + labelStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w400), + unselectedLabelStyle: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + ), + indicator: const UnderlineTabIndicator( + borderSide: BorderSide(color: DarkThemeColors.accentColor, width: 2.0), + ), + overlayColor: WidgetStateProperty.resolveWith((states) { + return DarkThemeColors.lightGrey; + }), + ), + appBarTheme: const AppBarTheme( + elevation: 0, + centerTitle: true, + scrolledUnderElevation: 0, + foregroundColor: DarkThemeColors.scaffoldBackgroundColor, + backgroundColor: DarkThemeColors.scaffoldBackgroundColor, + surfaceTintColor: DarkThemeColors.scaffoldBackgroundColor, + shadowColor: Colors.black, + titleTextStyle: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: DarkThemeColors.textColor, + ), + toolbarHeight: 56, + iconTheme: IconThemeData(color: DarkThemeColors.primaryColor), + ), + inputDecorationTheme: const InputDecorationTheme( + alignLabelWithHint: true, + contentPadding: EdgeInsets.all(16), + labelStyle: TextStyle( + color: DarkThemeColors.textColor, + fontSize: 16, + fontWeight: FontWeight.w400, + ), + hintStyle: TextStyle( + color: DarkThemeColors.secondaryText, + fontSize: 16, + fontWeight: FontWeight.w400, + ), + + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: DarkThemeColors.errorColor), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: DarkThemeColors.borderColor), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: DarkThemeColors.borderColor), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: DarkThemeColors.primaryColor), + ), + disabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: DarkThemeColors.white10Transparent), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + borderSide: BorderSide(color: DarkThemeColors.errorColor), + ), + filled: true, + isDense: true, + fillColor: DarkThemeColors.edittextBackground, + floatingLabelAlignment: FloatingLabelAlignment.start, + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ButtonStyle( + overlayColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + if (states.contains(WidgetState.hovered)) { + return DarkThemeColors.errorColor; + } + return null; + }), + elevation: WidgetStateProperty.all(0), + textStyle: WidgetStateProperty.all( + const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: DarkThemeColors.buttonColorText, + ), + ), + foregroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return DarkThemeColors.white60Transparent; + } else { + return DarkThemeColors.buttonColorText; + } + }), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return DarkThemeColors.disabledColor; + } else { + return DarkThemeColors.primaryColor; + } + }), + maximumSize: WidgetStateProperty.all( + const Size(double.infinity, 56), + ), + minimumSize: WidgetStateProperty.all( + const Size(double.infinity, 56), + ), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(24)), + ), + ), + ), + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: DarkThemeColors.scaffoldBackgroundColor, + type: BottomNavigationBarType.fixed, + showSelectedLabels: true, + selectedLabelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + unselectedLabelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + unselectedItemColor: DarkThemeColors.lightSecondary, + selectedItemColor: DarkThemeColors.primaryColor, + selectedIconTheme: IconThemeData(size: 25), + unselectedIconTheme: IconThemeData(size: 25), + elevation: 2, + ), + textButtonTheme: TextButtonThemeData( + style: ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + ), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + elevation: WidgetStateProperty.all(2), + foregroundColor: WidgetStateProperty.resolveWith((states) { + return DarkThemeColors.scaffoldBackgroundColor; + }), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return Colors.transparent; + } else { + return Colors.transparent; + } + }), + maximumSize: WidgetStateProperty.all( + const Size(double.infinity, double.infinity), + ), + minimumSize: WidgetStateProperty.all( + const Size(double.minPositive, double.minPositive), + ), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + ), + textStyle: WidgetStateProperty.all( + const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: DarkThemeColors.accentColor, + ), + ), + // overlayColor: WidgetStateProperty.resolveWith((states) { + // return DarkThemeColors.lightGrey; + // }), + ), + ), + bottomSheetTheme: const BottomSheetThemeData( + backgroundColor: Colors.transparent, + // surfaceTintColor: Colors.transparent, + modalBackgroundColor: Colors.transparent, + modalElevation: 0, + elevation: 0, + ), +); diff --git a/lib/core/theme/theme_text_style.dart b/lib/core/theme/theme_text_style.dart new file mode 100644 index 0000000..5765eab --- /dev/null +++ b/lib/core/theme/theme_text_style.dart @@ -0,0 +1,118 @@ +import 'package:cargocalculaterapp/core/theme/colors/app_colors.dart'; +import 'package:flutter/material.dart'; + +import 'app_text_styles.dart'; + +@immutable +class ThemeTextStyles extends ThemeExtension { + final TextStyle profileCategory; + final TextStyle titleBig; + final TextStyle authTitle; + final TextStyle categoryName; + final TextStyle bigTitle; + final TextStyle statusText; + final TextStyle authDesc; + final TextStyle orderListTitle; + final TextStyle orderTitle; + final TextStyle statusNumber; + final TextStyle statusDesc; + final TextStyle secondaryText14; + + @override + ThemeExtension copyWith({ + TextStyle? profileCategory, + TextStyle? titleBig, + TextStyle? authTitle, + TextStyle? categoryName, + TextStyle? bigTitle, + TextStyle? statusText, + TextStyle? authDesc, + TextStyle? orderListTitle, + TextStyle? orderTitle, + TextStyle? statusNumber, + TextStyle? statusDesc, + TextStyle? secondaryText14, + }) { + return ThemeTextStyles( + profileCategory: profileCategory ?? this.profileCategory, + titleBig: titleBig ?? this.titleBig, + authTitle: authTitle ?? this.authTitle, + categoryName: categoryName ?? this.categoryName, + bigTitle: bigTitle ?? this.bigTitle, + statusText: statusText ?? this.statusText, + authDesc: authDesc ?? this.authDesc, + orderListTitle: orderListTitle ?? this.orderListTitle, + orderTitle: orderTitle ?? this.orderTitle, + statusNumber: statusNumber ?? this.statusNumber, + statusDesc: statusDesc ?? this.statusDesc, + secondaryText14: secondaryText14 ?? this.secondaryText14, + ); + } + + const ThemeTextStyles({ + required this.profileCategory, + required this.titleBig, + required this.authTitle, + required this.categoryName, + required this.bigTitle, + required this.statusText, + required this.authDesc, + required this.orderListTitle, + required this.orderTitle, + required this.statusNumber, + required this.statusDesc, + required this.secondaryText14, + }); + + static get light => const ThemeTextStyles( + profileCategory: AppTextStyles.profileCategory, + titleBig: AppTextStyles.titleBig, + authTitle: AppTextStyles.authTitle, + categoryName: AppTextStyles.categoryName, + bigTitle: AppTextStyles.bigTitle, + statusText: AppTextStyles.statusText, + authDesc: AppTextStyles.authDesc, + orderListTitle: AppTextStyles.orderListTitle, + orderTitle: AppTextStyles.orderTitle, + statusNumber: AppTextStyles.statusNumber, + statusDesc: AppTextStyles.statusDesc, + secondaryText14: AppTextStyles.secondaryText14, + ); + + static get dark => ThemeTextStyles( + profileCategory: AppTextStyles.profileCategory.copyWith( + color: DarkThemeColors.textColor, + ), + titleBig: AppTextStyles.titleBig.copyWith(color: DarkThemeColors.textColor), + authTitle: AppTextStyles.authTitle.copyWith(color: DarkThemeColors.textColor), + categoryName: AppTextStyles.categoryName.copyWith(color: DarkThemeColors.textColor), + bigTitle: AppTextStyles.bigTitle.copyWith(color: DarkThemeColors.textColor), + statusText: AppTextStyles.statusText.copyWith(color: DarkThemeColors.buttonColorText), + authDesc: AppTextStyles.authDesc.copyWith(color: DarkThemeColors.secondaryText), + orderListTitle: AppTextStyles.orderListTitle.copyWith(color: DarkThemeColors.secondaryText), + orderTitle: AppTextStyles.orderTitle.copyWith(color: DarkThemeColors.textColor), + statusNumber: AppTextStyles.statusNumber.copyWith(color: DarkThemeColors.secondaryText), + statusDesc: AppTextStyles.statusDesc.copyWith(color: DarkThemeColors.textColor), + secondaryText14: AppTextStyles.secondaryText14.copyWith(color: DarkThemeColors.lightSecondary), + ); + + @override + ThemeTextStyles lerp(ThemeExtension? other, double t) { + if (other is! ThemeTextStyles) return this; + + return ThemeTextStyles( + profileCategory: TextStyle.lerp(profileCategory, other.profileCategory, t)!, + titleBig: TextStyle.lerp(titleBig, other.titleBig, t)!, + authTitle: TextStyle.lerp(authTitle, other.authTitle, t)!, + categoryName: TextStyle.lerp(categoryName, other.categoryName, t)!, + bigTitle: TextStyle.lerp(bigTitle, other.bigTitle, t)!, + statusText: TextStyle.lerp(statusText, other.statusText, t)!, + authDesc: TextStyle.lerp(authDesc, other.authDesc, t)!, + orderListTitle: TextStyle.lerp(orderListTitle, other.orderListTitle, t)!, + orderTitle: TextStyle.lerp(orderTitle, other.orderTitle, t)!, + statusNumber: TextStyle.lerp(statusNumber, other.statusNumber, t)!, + statusDesc: TextStyle.lerp(statusDesc, other.statusDesc, t)!, + secondaryText14: TextStyle.lerp(secondaryText14, other.secondaryText14, t)!, + ); + } +} diff --git a/lib/core/usecase/usecase.dart b/lib/core/usecase/usecase.dart new file mode 100644 index 0000000..123e68c --- /dev/null +++ b/lib/core/usecase/usecase.dart @@ -0,0 +1,14 @@ +import 'package:dartz/dartz.dart'; +import 'package:equatable/equatable.dart'; +import '../error/failure.dart'; + +abstract class UseCase { + Future> call(Params params); +} + +class NoParams extends Equatable { + const NoParams(); + + @override + List get props => []; +} diff --git a/lib/core/utils/app_utils.dart b/lib/core/utils/app_utils.dart new file mode 100644 index 0000000..ac04d85 --- /dev/null +++ b/lib/core/utils/app_utils.dart @@ -0,0 +1,484 @@ +import 'package:flutter/material.dart'; + +import '../theme/colors/app_colors.dart'; + +class AppUtils { + AppUtils._(); + + static const kDividerR = Divider( + height: 1, + thickness: 1, + color: LightThemeColors.dividerColor, + ); + static const kGap12 = SliverToBoxAdapter(child: kBoxHeight12); + static const kGap16 = SliverToBoxAdapter(child: kBoxHeight16); + static const kGap8 = SliverToBoxAdapter(child: kBoxHeight8); + static const kGap4 = SliverToBoxAdapter(child: kBoxHeight4); + static const kGap10 = SliverToBoxAdapter(child: kBoxHeight10); + static const kGap22 = SliverToBoxAdapter(child: kBoxHeight22); + static const kGap24 = SliverToBoxAdapter(child: kBoxHeight24); + static const kGapBox = SliverToBoxAdapter(child: SizedBox()); + static const kBoxWith4 = SizedBox(width: 4); + static const kBoxWith8 = SizedBox(width: 8); + static const kBoxWith12 = SizedBox(width: 12); + + /// divider + static const kDivider = Divider( + height: 2, + thickness: 2, + color: LightThemeColors.dividerColor, + ); + static const kPad16Divider = Divider( + height: 2, + thickness: 2, + indent: 16, + color: LightThemeColors.dividerColor, + ); + static const kGap16Divider = SliverToBoxAdapter(child: kPadHor16Divider); + static const kPadHor16Divider = Divider( + height: 1, + thickness: 1, + indent: 16, + endIndent: 16, + ); + static const kPad60Divider = Padding( + padding: EdgeInsets.only(left: 60), + child: Divider(height: 1, thickness: 1), + ); + + /// spacer + static const kSpacer = Spacer(); + + /// Sizedbox + static const kBox = SizedBox.shrink(); + static const kBoxHeight2 = SizedBox(height: 2); + static const kBoxHeight3 = SizedBox(height: 3); + static const kBoxHeight4 = SizedBox(height: 4); + static const kBoxHeight6 = SizedBox(height: 6); + static const kBoxHeight8 = SizedBox(height: 8); + static const kBoxHeight10 = SizedBox(height: 10); + static const kBoxHeight12 = SizedBox(height: 12); + static const kBoxHeight14 = SizedBox(height: 14); + static const kBoxHeight16 = SizedBox(height: 16); + static const kBoxHeight18 = SizedBox(height: 18); + static const kBoxHeight20 = SizedBox(height: 20); + static const kBoxHeight22 = SizedBox(height: 22); + static const kBoxHeight24 = SizedBox(height: 24); + static const kBoxHeight26 = SizedBox(height: 26); + static const kBoxHeight28 = SizedBox(height: 28); + static const kBoxHeight40 = SizedBox(height: 40); + static const kBoxHeight42 = SizedBox(height: 42); + static const kBoxHeight38 = SizedBox(height: 48); + static const kBoxHeight45 = SizedBox(height: 45); + static const kBoxHeight48 = SizedBox(height: 48); + static const kBoxHeight58 = SizedBox(height: 58); + + static const kBoxHeight30 = SizedBox(height: 30); + static const kBoxHeight32 = SizedBox(height: 32); + static const kBoxHeight34 = SizedBox(height: 34); + static const kBoxHeight36 = SizedBox(height: 36); + static const kBoxHeight64 = SizedBox(height: 64); + static const kBoxHeight84 = SizedBox(height: 84); + static const kBoxHeight124 = SizedBox(height: 124); + static const kBoxHeight204 = SizedBox(height: 204); + static const kBoxWidth2 = SizedBox(width: 2); + static const kBoxWidth8 = SizedBox(width: 8); + static const kBoxWidth6 = SizedBox(width: 6); + static const kBoxWidth4 = SizedBox(width: 4); + static const kBoxWidth3 = SizedBox(width: 3); + static const kBoxWidth10 = SizedBox(width: 10); + static const kBoxWidth12 = SizedBox(width: 12); + static const kBoxWidth14 = SizedBox(width: 14); + static const kBoxWidth20 = SizedBox(width: 20); + static const kBoxWidth16 = SizedBox(width: 16); + static const kBoxWidth22 = SizedBox(width: 22); + static const kBoxWidth24 = SizedBox(width: 24); + static const kBoxWidth30 = SizedBox(width: 30); + static const kBoxWidth40 = SizedBox(width: 40); + static const kBoxWidth56 = SizedBox(width: 56); + static const kBoxWidth95 = SizedBox(width: 95); + + /// padding + static const kPaddingAll4 = EdgeInsets.all(4); + static const kPaddingAll3 = EdgeInsets.all(3); + static const kPaddingAll2 = EdgeInsets.all(2); + static const kPaddingAll1 = EdgeInsets.all(1); + static const kPaddingHorizontal12Vertical8 = EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8, + ); + static const kPaddingHorizontal12Vertical10 = EdgeInsets.symmetric( + horizontal: 12, + ); + static const kPaddingVer10 = EdgeInsets.symmetric(vertical: 10); + static const kPaddingAll6 = EdgeInsets.all(6); + static const kPaddingHorizontal16Vertical8 = EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8, + ); + static const kPaddingAll8 = EdgeInsets.all(8); + static const kPaddingAll12 = EdgeInsets.all(12); + static const kPaddingAll10 = EdgeInsets.all(10); + static const kPaddingAll16 = EdgeInsets.all(16); + static const kPaddingAll18 = EdgeInsets.all(18); + static const kPaddingAll20 = EdgeInsets.all(20); + static const kPaddingAllB16 = EdgeInsets.fromLTRB(16, 16, 16, 0); + static const kPaddingAll24 = EdgeInsets.all(24); + static const kPaddingHorizontal16 = EdgeInsets.symmetric(horizontal: 16); + static const kPaddingHorizontal16T0B8 = EdgeInsets.only( + left: 16, + right: 16, + bottom: 8, + top: 0, + ); + static const kPaddingHorizontal16T0B16 = EdgeInsets.only( + left: 16, + right: 16, + bottom: 16, + top: 0, + ); + static const kPaddingHorizontal32T32B20 = EdgeInsets.only( + left: 32, + right: 32, + bottom: 20, + top: 32, + ); + static const kPaddingLTRB8 = EdgeInsets.only( + left: 16, + right: 16, + top: 16, + bottom: 8, + ); + static const kPaddingL8T12R16B12 = EdgeInsets.only( + left: 12, + right: 16, + top: 12, + bottom: 8, + ); + static const kPaddingLTR = EdgeInsets.only(left: 16, right: 16, top: 16); + + static const kPaddingL37 = EdgeInsets.only(left: 37); + static const kPaddingT4R8B4 = EdgeInsets.only(right: 8, bottom: 4, top: 4); + static const kPaddingT24 = EdgeInsets.only(top: 24); + + static const kPaddingR4 = EdgeInsets.only(right: 4); + static const kPaddingR10 = EdgeInsets.only(right: 10); + static const kPaddingR16 = EdgeInsets.only(right: 16); + static const kPaddingLT8RB = EdgeInsets.only( + left: 16, + right: 16, + top: 8, + bottom: 16, + ); + static const kPaddingLT0RB = EdgeInsets.only( + left: 16, + right: 16, + top: 0, + bottom: 16, + ); + static const kPadding12LTRB0 = EdgeInsets.only( + left: 12, + right: 12, + top: 12, + bottom: 0, + ); + static const kPaddingTop10 = EdgeInsets.only(top: 10); + static const kPaddingTop15 = EdgeInsets.only(top: 15); + static const kPaddingTop21 = EdgeInsets.only(top: 21); + static const kPaddingTop24 = EdgeInsets.only(top: 24); + static const kPaddingTop28 = EdgeInsets.only(top: 28); + static const kPaddingTop20 = EdgeInsets.only(top: 40); + + static const kPaddingL16R12 = EdgeInsets.only(left: 16, right: 12, bottom: 8); + static const kPaddingL16R12TB8 = EdgeInsets.only( + left: 16, + right: 12, + bottom: 8, + top: 8, + ); + static const kPaddingL16R16T0B24 = EdgeInsets.only( + left: 16, + right: 16, + bottom: 24, + top: 0, + ); + static const kPaddingL16R16T24B16 = EdgeInsets.only( + left: 16, + right: 16, + top: 24, + bottom: 16, + ); + static const kPaddingL16R16T24B0 = EdgeInsets.only( + left: 16, + right: 16, + top: 24, + ); + static const kPaddingL16R16T16B24 = EdgeInsets.only( + left: 16, + right: 16, + top: 16, + bottom: 24, + ); + static const kPaddingL8T8 = EdgeInsets.only(left: 8, top: 8); + static const kPaddingL60 = EdgeInsets.only(left: 60); + static const kPaddingT20B24R16 = EdgeInsets.only( + top: 20, + bottom: 24, + right: 16, + ); + static const kPaddingL16T20B24 = EdgeInsets.only( + left: 16, + top: 20, + bottom: 24, + ); + static const kPaddingR20T16B16 = EdgeInsets.only( + right: 20, + top: 16, + bottom: 16, + ); + + static const kPaddingHor32Ver20 = EdgeInsets.symmetric( + horizontal: 32, + vertical: 20, + ); + static const kPaddingHor16 = EdgeInsets.symmetric(horizontal: 16); + static const kPaddingHor4 = EdgeInsets.symmetric(horizontal: 4); + static const kPaddingHor6 = EdgeInsets.symmetric(horizontal: 6); + static const kPaddingVer12 = EdgeInsets.symmetric(vertical: 12); + static const kPaddingVer16 = EdgeInsets.symmetric(vertical: 16); + static const kPaddingVer24 = EdgeInsets.symmetric(vertical: 24); + static const kPaddingVer44 = EdgeInsets.symmetric(vertical: 44); + static const kPaddingHor18 = EdgeInsets.symmetric(horizontal: 18); + static const kPaddingHor20 = EdgeInsets.symmetric(horizontal: 20); + static const kPaddingHor24 = EdgeInsets.symmetric(horizontal: 24); + static const kPaddingHor28 = EdgeInsets.symmetric(horizontal: 28); + static const kPaddingHor12 = EdgeInsets.symmetric(horizontal: 12); + static const kPaddingHor10 = EdgeInsets.symmetric(horizontal: 10); + static const kPaddingHor32 = EdgeInsets.symmetric(horizontal: 32); + static const kPaddingHor34 = EdgeInsets.symmetric(horizontal: 34); + static const kPaddingHor36 = EdgeInsets.symmetric(horizontal: 36); + static const kPaddingHor38 = EdgeInsets.symmetric(horizontal: 38); + static const kPaddingHor44 = EdgeInsets.symmetric(horizontal: 44); + static const kPaddingVer8 = EdgeInsets.symmetric(vertical: 8); + static const kPaddingHor16Ver12 = EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ); + static const kPaddingHor12Ver16 = EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, + ); + static const kPaddingHor32Ver16 = EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ); + static const kPaddingHor16Ver8 = EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ); + static const kPaddingHor8Ver4 = EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ); + static const kPaddingHor8Ver5 = EdgeInsets.symmetric( + horizontal: 8, + vertical: 5, + ); + static const kPaddingHor6Ver4 = EdgeInsets.symmetric( + horizontal: 6, + vertical: 4, + ); + static const kPaddingHor12Ver8 = EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ); + static const kPaddingVer10Hor16 = EdgeInsets.symmetric( + vertical: 10, + horizontal: 16, + ); + static const kPaddingVer12Hor18 = EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ); + static const kPaddingVer8Hor12 = EdgeInsets.symmetric( + vertical: 8, + horizontal: 12, + ); + static const kPaddingVer10Hor12 = EdgeInsets.symmetric( + vertical: 10, + horizontal: 12, + ); + static const kPaddingVer2Hor4 = EdgeInsets.symmetric( + vertical: 2, + horizontal: 4, + ); + static const kPaddingVer2Hor8 = EdgeInsets.symmetric( + vertical: 2, + horizontal: 8, + ); + static const kPaddingVer3Hor10 = EdgeInsets.symmetric( + vertical: 3, + horizontal: 10, + ); + static const kPaddingVer2Hor10 = EdgeInsets.symmetric( + vertical: 2, + horizontal: 10, + ); + static const kPaddingVer2Hor6 = EdgeInsets.symmetric( + vertical: 2, + horizontal: 6, + ); + static const kPaddingVer5Hor2 = EdgeInsets.symmetric( + vertical: 5, + horizontal: 2, + ); + static const kPaddingVer18Hor16 = EdgeInsets.symmetric( + vertical: 18, + horizontal: 16, + ); + static const kPaddingVer24Hor16 = EdgeInsets.symmetric( + vertical: 24, + horizontal: 16, + ); + static const kPaddingVer24Hor8 = EdgeInsets.symmetric( + vertical: 24, + horizontal: 8, + ); + static const kPaddingVer14Hor12 = EdgeInsets.symmetric( + vertical: 14, + horizontal: 12, + ); + static const kPaddingVer14Hor16 = EdgeInsets.symmetric( + vertical: 14, + horizontal: 16, + ); + static const kPaddingVer16Hor24 = EdgeInsets.symmetric( + vertical: 16, + horizontal: 24, + ); + static const kPaddingVer4Hor12 = EdgeInsets.symmetric( + vertical: 4, + horizontal: 12, + ); + static const kPaddingVer4Hor16 = EdgeInsets.symmetric( + vertical: 4, + horizontal: 16, + ); + static const kPaddingVer6Hor12 = EdgeInsets.symmetric( + vertical: 6, + horizontal: 12, + ); + static const kPaddingVer12Hor14 = EdgeInsets.symmetric( + vertical: 12, + horizontal: 14, + ); + static const kPaddingVer28Hor28 = EdgeInsets.symmetric( + vertical: 28, + horizontal: 28, + ); + + /// border radius + static const kRadius = Radius.zero; + static const kRadius8 = Radius.circular(8); + static const kRadius6 = Radius.circular(6); + static const kRadius12 = Radius.circular(12); + static const kBorderRadius = BorderRadius.all(Radius.circular(0)); + static const kBorderRadius2 = BorderRadius.all(Radius.circular(2)); + static const kBorderRadius4 = BorderRadius.all(Radius.circular(4)); + static const kBorderRadius5 = BorderRadius.all(Radius.circular(5)); + static const kBorderRadius6 = BorderRadius.all(Radius.circular(6)); + static const kBorderRadius8 = BorderRadius.all(Radius.circular(8)); + static const kBorderRadiusTop8 = BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ); + static const kBorderRadiusTop10 = BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ); + static const kBorderRadiusTop12 = BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ); + static const kBorderRadiusTop16 = BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ); + static const kBorderRadiusBottom16 = BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16), + ); + static const kBorderRadiusTop24 = BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ); + static const kBorderRadiusBottom8 = BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ); + static const kBorderRadiusBottom10 = BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + ); + static const kBorderRadiusTopBot12 = BorderRadius.only( + topRight: AppUtils.kRadius12, + bottomRight: AppUtils.kRadius12, + ); + static const kBorderRadiusLeftTopBob12 = BorderRadius.only( + topLeft: AppUtils.kRadius12, + bottomLeft: AppUtils.kRadius12, + ); + static const kBorderRadius12 = BorderRadius.all(Radius.circular(12)); + static const kBorderRadius10 = BorderRadius.all(Radius.circular(10)); + static const kBorderRadius16 = BorderRadius.all(Radius.circular(16)); + static const kBorderRadius14 = BorderRadius.all(Radius.circular(14)); + static const kBorderRadius24 = BorderRadius.all(Radius.circular(24)); + static const kBorderRadius20 = BorderRadius.all(Radius.circular(20)); + static const kBorderRadius32 = BorderRadius.all(Radius.circular(32)); + static const kBorderRadius48 = BorderRadius.all(Radius.circular(48)); + static const kBorderRadius64 = BorderRadius.all(Radius.circular(64)); + // static const kBoxDecoration = BoxDecoration( + // borderRadius: AppUtils.kBorderRadius24, + // color: LightThemeColors.whiteBackground, + // ); + // static const kBoxDecorationGray = BoxDecoration( + // borderRadius: AppUtils.kBorderRadius24, + // color: LightThemeColors.backgroundGray, + // ); + // + // static const kBoxDecoration12 = BoxDecoration( + // borderRadius: AppUtils.kBorderRadius12, + // color: LightThemeColors.whiteBackground, + // ); + // static const kBoxDecoration12BorderColor = BoxDecoration( + // borderRadius: AppUtils.kBorderRadius12, + // color: LightThemeColors.badgeGray, + // ); + // static const kBoxDecoration12Gray = BoxDecoration( + // borderRadius: AppUtils.kBorderRadius12, + // color: LightThemeColors.scaffoldBackgroundColor, + // ); + // static BoxDecoration kBoxDecoration12WhiteBorderRed = BoxDecoration( + // borderRadius: AppUtils.kBorderRadius12, + // color: LightThemeColors.whiteBackground, + // border: Border.all(color: LightThemeColors.red), + // ); + // static const kBoxDecorationTop24 = BoxDecoration( + // color: LightThemeColors.white, + // borderRadius: AppUtils.kBorderRadiusTop24, + // ); + // static const kBoxDecorationTop = BoxDecoration( + // borderRadius: BorderRadius.only( + // topLeft: Radius.circular(24), + // topRight: Radius.circular(24), + // ), + // color: LightThemeColors.whiteBackground, + // ); + // static const kBoxDecorationBottom = BoxDecoration( + // borderRadius: BorderRadius.only( + // bottomLeft: Radius.circular(24), + // bottomRight: Radius.circular(24), + // ), + // color: LightThemeColors.whiteBackground, + // ); +} diff --git a/lib/core/widgets/drop_down/custom_drop_down.dart b/lib/core/widgets/drop_down/custom_drop_down.dart new file mode 100644 index 0000000..64d829c --- /dev/null +++ b/lib/core/widgets/drop_down/custom_drop_down.dart @@ -0,0 +1,99 @@ +import 'package:cargocalculaterapp/constants/constants.dart'; +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/functions/base_finctions.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import '../../theme/app_text_styles.dart'; +import '../../utils/app_utils.dart'; + +class CustomDropDown extends StatelessWidget { + const CustomDropDown({ + super.key, + this.name, + required this.isRequired, + this.errorText, + this.isError, + this.style, + required this.onChange, + this.value, + }); + + final String? name; + final bool isRequired; + final String? errorText; + final bool? isError; + final TextStyle? style; + final String? value; + final Function(String?) onChange; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (name?.isNotEmpty ?? false) + Text(name ?? '', style: style ?? context.text.categoryName), + AppUtils.kBoxWidth4, + if (isRequired) SvgPicture.asset("assets/svg/ic_required.svg"), + ], + ), + AppUtils.kBoxHeight8, + InputDecorator( + decoration: InputDecoration( + fillColor: context.color.white10Transparent, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: context.color.borderColor, + ), + borderRadius: AppUtils.kBorderRadius12, + ), + contentPadding: AppUtils.kPaddingAll16, + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + dropdownColor: context.color.grayBackground, + hint: Text(AppLocalization.current.warehouse,style: TextStyle( + color: context.color.secondaryText, + fontSize: 16, + fontWeight: FontWeight.w400, + ),), + icon: Icon( + Icons.keyboard_arrow_down_outlined, + size: 24, + color: context.color.textColor, + ), + isDense: true, + iconSize: 0, + value: value, + items: + AppConst.warehouseOptions.keys.map((entry) { + return DropdownMenuItem( + value: entry, + child: Text( + Functions.getTranslatedItem( + AppConst.warehouseOptions[entry], + context, + ), + style: context.text.profileCategory, + ), + ); + }).toList(), + onChanged: onChange, + ), + ), + ), + if (isError ?? false) AppUtils.kBoxHeight4, + if (isError ?? false) + Text( + errorText ?? "", + style: AppTextStyles.timer.copyWith(fontSize: 12), + ), + ], + ); + } +} diff --git a/lib/core/widgets/loading/custom_loading.dart b/lib/core/widgets/loading/custom_loading.dart new file mode 100644 index 0000000..8a5a1b3 --- /dev/null +++ b/lib/core/widgets/loading/custom_loading.dart @@ -0,0 +1,24 @@ +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import '../../theme/colors/app_colors.dart'; + +class CustomLoadingWidget extends StatelessWidget { + final Color? color; + + const CustomLoadingWidget({super.key, this.color}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Platform.isAndroid + ? CircularProgressIndicator( + color: color ?? LightThemeColors.lightGrey, + ) + : CupertinoActivityIndicator(color: color), + ], + ); + } +} diff --git a/lib/core/widgets/loading/progress_hud.dart b/lib/core/widgets/loading/progress_hud.dart new file mode 100644 index 0000000..34e1f5d --- /dev/null +++ b/lib/core/widgets/loading/progress_hud.dart @@ -0,0 +1,90 @@ +import "dart:io"; +import "package:cargocalculaterapp/core/theme/colors/app_colors.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "dart:ui"; + +/// +/// Wrap around any widget that makes an async call to show a modal progress +/// indicator while the async call is in progress. +/// +/// HUD=Heads Up Display +/// +class ModalProgressHUD extends StatelessWidget { + /// A required [bool] to toggle the modal overlay. + final bool inAsyncCall; + + /// A [double] specifying the opacity of the modal overlay, defaults to 0.3 + final double opacity; + + /// A [Color] object which is assigned to the loading barrier, defaults to grey + final Color color; + + /// A [Widget] which is shown at the center of the modal overlay, + /// defaults to the standard android spinner animation. + + /// An [Offset] object which is applied to the [progressIndicator] when specified. + final Offset? offset; + + /// A [bool] which controls whether the modal overlay can be dismissible when interated. + final bool dismissible; + + /// A [Widget] over which the modal overlay is activated. + final Widget child; + + /// A [double] value specifying the amount of background blur when progress hud is active. + final double blur; + + const ModalProgressHUD({ + super.key, + required this.inAsyncCall, + this.opacity = 0.3, + this.color = Colors.grey, + this.offset, + this.dismissible = false, + required this.child, + this.blur = 0.0, + }); + + @override + Widget build(BuildContext context) { + Widget layOutProgressIndicator; + if (offset == null) { + layOutProgressIndicator = Center( + child: + Platform.isIOS + ? const CupertinoActivityIndicator() + : const CircularProgressIndicator( + color: LightThemeColors.accentColor, + ), + ); + } else { + layOutProgressIndicator = Positioned( + left: offset!.dx, + top: offset!.dy, + child: + Platform.isIOS + ? const CupertinoActivityIndicator() + : const CircularProgressIndicator( + color: LightThemeColors.accentColor, + ), + ); + } + + return Stack( + children: [ + child, + if (inAsyncCall) ...[ + BackdropFilter( + filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), + child: Opacity( + opacity: opacity, + child: ModalBarrier(dismissible: dismissible, color: color), + ), + ), + layOutProgressIndicator, + ], + ], + ); + } +} diff --git a/lib/core/widgets/rating/rating_bar_widget.dart b/lib/core/widgets/rating/rating_bar_widget.dart new file mode 100644 index 0000000..f997278 --- /dev/null +++ b/lib/core/widgets/rating/rating_bar_widget.dart @@ -0,0 +1,95 @@ +import 'package:cargocalculaterapp/core/extension/extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../utils/app_utils.dart'; + +typedef RatingChangeCallback = void Function(double rating)?; + +const starPoints = 5; + +class RatingBarWidget extends StatelessWidget { + final double rating; + final double iconsSize; + final Color activeRatingColor; + final Color inactiveRatingColor; + final RatingChangeCallback onRatingChanged; + final bool isAnimate; + final Widget separator; + final bool isSharp; + + const RatingBarWidget({ + super.key, + required this.rating, + this.iconsSize = 18, + this.activeRatingColor = const Color(0xffFFA047), + this.inactiveRatingColor = const Color(0xffCED5DF), + this.onRatingChanged, + this.isAnimate = false, + this.separator = AppUtils.kBoxWidth3, + this.isSharp = false, + }); + + @override + Widget build(BuildContext context) { + if (isAnimate) { + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 350), + curve: Curves.easeInOut, + tween: Tween(begin: 0, end: rating), + builder: (_, value, __) { + return Row( + mainAxisSize: MainAxisSize.min, + children: List.generate( + 10, + (index) => index.isEven + ? buildStar(index.exactIndex, rating, isSharp) + : separator, + ), + ); + }, + ); + } + + return Row( + mainAxisSize: MainAxisSize.min, + children: List.generate( + 10, + (index) => index.isEven + ? buildStar(index.exactIndex, rating, isSharp) + : separator, + ), + ); + } + + Widget buildStar(int index, double rating, isSharp) { + Widget icon; + double present = rating - index; + icon = ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: (bounds) { + return LinearGradient( + tileMode: TileMode.clamp, + colors: [activeRatingColor, inactiveRatingColor], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [present, present], + ).createShader(bounds); + }, + child: SvgPicture.asset( + isSharp + ? "assets/svg/ic_star_selected.svg" + : "assets/svg/ic_star_selected.svg", + height: iconsSize, + width: iconsSize, + ), + ); + + return InkResponse( + onTap: onRatingChanged == null + ? null + : () => onRatingChanged!(index + 1.0), + child: icon, + ); + } +} diff --git a/lib/core/widgets/shimmer/shimmer_widget.dart b/lib/core/widgets/shimmer/shimmer_widget.dart new file mode 100644 index 0000000..b5296c6 --- /dev/null +++ b/lib/core/widgets/shimmer/shimmer_widget.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; +import '../../theme/colors/app_colors.dart'; +import '../../utils/app_utils.dart'; + +class ShimmerWidget extends StatelessWidget { + const ShimmerWidget({ + super.key, + required this.width, + required this.height, + this.borderRadius = AppUtils.kBorderRadius6, + this.margin, + }); + + final double width; + final double height; + final BorderRadiusGeometry? borderRadius; + final EdgeInsetsGeometry? margin; + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + enabled: true, + baseColor: + Theme.of(context).brightness == Brightness.dark + ? const Color(0x80FFFFFF) + : const Color(0xffF8F8F9), + highlightColor: LightThemeColors.shimmerHighlight, + child: Container( + width: width, + height: height, + margin: margin, + decoration: BoxDecoration( + color: LightThemeColors.white, + borderRadius: borderRadius, + ), + ), + ); + } +} diff --git a/lib/core/widgets/text_filds/custom_text_field_name.dart b/lib/core/widgets/text_filds/custom_text_field_name.dart new file mode 100644 index 0000000..fd719ae --- /dev/null +++ b/lib/core/widgets/text_filds/custom_text_field_name.dart @@ -0,0 +1,108 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; +import '../../theme/app_text_styles.dart'; +import '../../theme/colors/app_colors.dart'; +import '../../utils/app_utils.dart'; + +class CustomTextFieldName extends StatelessWidget { + const CustomTextFieldName({ + super.key, + this.name, + required this.hint, + this.readOnly = false, + this.onTap, + this.prefixWidget, + this.controller, + this.format, + this.inputType, + this.onchange, + this.style, + this.suffix, + this.errorText, + this.isError, + this.isRequired = false, + this.maxLines, + this.minLines, + this.inputStyle, + this.textCapitalization = TextCapitalization.sentences, + this.fillColor, + this.obscureText = false, + }); + + final String? name; + final String hint; + final bool readOnly; + final Function()? onTap; + final Widget? prefixWidget; + final TextEditingController? controller; + final List? format; + final TextInputType? inputType; + final bool isRequired; + final Function(String)? onchange; + final TextStyle? style; + final Widget? suffix; + final String? errorText; + final bool? isError; + final int? maxLines; + final int? minLines; + final TextStyle? inputStyle; + final TextCapitalization textCapitalization; + final Color? fillColor; + final bool obscureText; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (name?.isNotEmpty ?? false) + Text(name ?? '', style: style ?? context.text.categoryName), + AppUtils.kBoxWidth4, + if (isRequired) SvgPicture.asset("assets/svg/ic_required.svg"), + ], + ), + AppUtils.kBoxHeight8, + TextField( + style: inputStyle ?? TextStyle(color: context.color.textColor), + keyboardType: inputType, + controller: controller, + onTap: onTap, + readOnly: readOnly, + onChanged: onchange, + inputFormatters: format, + maxLines: maxLines, + minLines: minLines, + obscureText: obscureText, + textCapitalization: textCapitalization, + decoration: InputDecoration( + fillColor: fillColor, + hintText: hint, + prefixIcon: prefixWidget, + suffixIcon: suffix, + focusedBorder: OutlineInputBorder( + borderRadius: AppUtils.kBorderRadius16, + borderSide: BorderSide( + color: + !(isError ?? false) + ? context.color.accentColor + : ThemeColors.timerRed, + ), + ), + ), + ), + if (isError ?? false) AppUtils.kBoxHeight4, + if (isError ?? false) + Text( + errorText ?? "", + style: AppTextStyles.timer.copyWith(fontSize: 12), + ), + ], + ); + } +} diff --git a/lib/features/auth/data/data_source/remote/auth_remote_data_source.dart b/lib/features/auth/data/data_source/remote/auth_remote_data_source.dart new file mode 100644 index 0000000..517d379 --- /dev/null +++ b/lib/features/auth/data/data_source/remote/auth_remote_data_source.dart @@ -0,0 +1,18 @@ +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart'; + +abstract class AuthRemoteDataSource { + Future login(LoginRequest request); + + Future signUp(SignUpRequest request); + + Future verify(AuthVerificationRequest request); + + Future addFcm(FcmAddRequest request); +} diff --git a/lib/features/auth/data/data_source/remote/auth_remote_data_source_impl.dart b/lib/features/auth/data/data_source/remote/auth_remote_data_source_impl.dart new file mode 100644 index 0000000..16f6a9e --- /dev/null +++ b/lib/features/auth/data/data_source/remote/auth_remote_data_source_impl.dart @@ -0,0 +1,105 @@ +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart'; + +import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart'; +import 'package:dio/dio.dart'; + +import '../../../../../constants/constants.dart'; +import '../../../../../core/error/exceptions.dart'; +import '../../../../../core/local_source/local_source.dart'; +import '../../../../../injector_container.dart'; +import 'auth_remote_data_source.dart'; + +class AuthRemoteDataSourceImpl extends AuthRemoteDataSource { + final Dio dio; + + AuthRemoteDataSourceImpl(this.dio); + + @override + Future login(LoginRequest request) async { + try { + final Response response = await dio.post( + Constants.baseUrl + Urls.login, + options: DioConstants.options, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return LoginResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future signUp(SignUpRequest request) async { + try { + final Response response = await dio.post( + Constants.baseUrl + Urls.signUp, + options: DioConstants.options, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return SignUpResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data);} on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future verify( + AuthVerificationRequest request, + ) async { + try { + final Response response = await dio.post( + Constants.baseUrl + Urls.verify, + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return AuthVerificationResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future addFcm(FcmAddRequest request) async { + try { + final Response response = await dio.put( + Constants.baseUrl + Urls.addFcm, + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return FcmAddResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } +} diff --git a/lib/features/auth/data/model/auth_verification_request.dart b/lib/features/auth/data/model/auth_verification_request.dart new file mode 100644 index 0000000..4127343 --- /dev/null +++ b/lib/features/auth/data/model/auth_verification_request.dart @@ -0,0 +1,21 @@ +class AuthVerificationRequest { + AuthVerificationRequest({this.phoneNumber, this.smsCode, this.email}); + + AuthVerificationRequest.fromJson(dynamic json) { + phoneNumber = json['phone_number']; + smsCode = json['smsCode']; + email = json['email']; + } + + String? phoneNumber; + String? smsCode; + String? email; + + Map toJson() { + final map = {}; + map['phone_number'] = phoneNumber; + map['smsCode'] = smsCode; + map['email'] = email; + return map; + } +} diff --git a/lib/features/auth/data/model/auth_verification_response.dart b/lib/features/auth/data/model/auth_verification_response.dart new file mode 100644 index 0000000..ccf716b --- /dev/null +++ b/lib/features/auth/data/model/auth_verification_response.dart @@ -0,0 +1,16 @@ +class AuthVerificationResponse { + AuthVerificationResponse({ + this.accessToken,}); + + AuthVerificationResponse.fromJson(dynamic json) { + accessToken = json['access_token']; + } + String? accessToken; + + Map toJson() { + final map = {}; + map['access_token'] = accessToken; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/auth/data/model/fcm_add_request.dart b/lib/features/auth/data/model/fcm_add_request.dart new file mode 100644 index 0000000..f95c10b --- /dev/null +++ b/lib/features/auth/data/model/fcm_add_request.dart @@ -0,0 +1,16 @@ +class FcmAddRequest { + FcmAddRequest({ + this.sfmToken,}); + + FcmAddRequest.fromJson(dynamic json) { + sfmToken = json['sfm_token']; + } + String? sfmToken; + + Map toJson() { + final map = {}; + map['sfm_token'] = sfmToken; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/auth/data/model/fcm_add_response.dart b/lib/features/auth/data/model/fcm_add_response.dart new file mode 100644 index 0000000..04be02b --- /dev/null +++ b/lib/features/auth/data/model/fcm_add_response.dart @@ -0,0 +1,51 @@ +class FcmAddResponse { + FcmAddResponse({ + this.message, + this.user,}); + + FcmAddResponse.fromJson(dynamic json) { + message = json['message']; + user = json['user'] != null ? User.fromJson(json['user']) : null; + } + String? message; + User? user; + + Map toJson() { + final map = {}; + map['message'] = message; + if (user != null) { + map['user'] = user?.toJson(); + } + return map; + } + +} + +class User { + User({ + this.fullname, + this.phoneNumber, + this.email, + this.sfmToken,}); + + User.fromJson(dynamic json) { + fullname = json['fullname']; + phoneNumber = json['phone_number']; + email = json['email']; + sfmToken = json['sfm_token']; + } + String? fullname; + String? phoneNumber; + String? email; + String? sfmToken; + + Map toJson() { + final map = {}; + map['fullname'] = fullname; + map['phone_number'] = phoneNumber; + map['email'] = email; + map['sfm_token'] = sfmToken; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/auth/data/model/login_request.dart b/lib/features/auth/data/model/login_request.dart new file mode 100644 index 0000000..aa1f6b9 --- /dev/null +++ b/lib/features/auth/data/model/login_request.dart @@ -0,0 +1,24 @@ +class LoginRequest { + LoginRequest({ + this.email, + this.phone, + this.ucode,}); + + LoginRequest.fromJson(dynamic json) { + email = json['email']; + phone = json['phone']; + ucode = json['ucode']; + } + String? email; + String? phone; + String? ucode; + + Map toJson() { + final map = {}; + map['email'] = email; + map['phone'] = phone; + map['ucode'] = ucode; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/auth/data/model/login_response.dart b/lib/features/auth/data/model/login_response.dart new file mode 100644 index 0000000..d09cc49 --- /dev/null +++ b/lib/features/auth/data/model/login_response.dart @@ -0,0 +1,20 @@ +class LoginResponse { + LoginResponse({ + this.token, + this.refreshToken,}); + + LoginResponse.fromJson(dynamic json) { + token = json['token']; + refreshToken = json['refreshToken']; + } + String? token; + String? refreshToken; + + Map toJson() { + final map = {}; + map['token'] = token; + map['refreshToken'] = refreshToken; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/auth/data/model/sign_up_request.dart b/lib/features/auth/data/model/sign_up_request.dart new file mode 100644 index 0000000..f165112 --- /dev/null +++ b/lib/features/auth/data/model/sign_up_request.dart @@ -0,0 +1,25 @@ +class SignUpRequest { + SignUpRequest({ + this.phoneNumber, + this.email, + this.fullname,}); + + SignUpRequest.fromJson(dynamic json) { + phoneNumber = json['phone_number']; + email = json['email']; + fullname = json['fullname']; + } + String? phoneNumber; + String? email; + String? fullname; + + + Map toJson() { + final map = {}; + map['phone_number'] = phoneNumber; + map['email'] = email; + map['fullname'] = fullname; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/auth/data/model/sign_up_response.dart b/lib/features/auth/data/model/sign_up_response.dart new file mode 100644 index 0000000..694370f --- /dev/null +++ b/lib/features/auth/data/model/sign_up_response.dart @@ -0,0 +1,16 @@ +class SignUpResponse { + SignUpResponse({ + this.token,}); + + SignUpResponse.fromJson(dynamic json) { + token = json['token']; + } + String? token; + + Map toJson() { + final map = {}; + map['token'] = token; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/auth/data/repository/auth_repository_impl.dart b/lib/features/auth/data/repository/auth_repository_impl.dart new file mode 100644 index 0000000..9f836c3 --- /dev/null +++ b/lib/features/auth/data/repository/auth_repository_impl.dart @@ -0,0 +1,73 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart'; +import 'package:dartz/dartz.dart'; +import '../../../../core/error/exceptions.dart'; +import '../../domain/repository/auth_repository.dart'; +import '../data_source/remote/auth_remote_data_source.dart'; + +class AuthRepositoryImpl extends AuthRepository { + final AuthRemoteDataSource remoteDataSource; + + AuthRepositoryImpl(this.remoteDataSource); + + @override + Future> login(LoginRequest request) async { + try { + final response = await remoteDataSource.login(request); + return Right(response); + } catch (e) { + if (e is ServerException) { + return Left(ServerFailure(message: e.message)); + } + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> signUp(SignUpRequest request) async { + try { + final response = await remoteDataSource.signUp(request); + return Right(response); + } catch (e) { + if (e is ServerException) { + return Left(ServerFailure(message: e.message)); + } + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> verify( + AuthVerificationRequest request, + ) async { + try { + final response = await remoteDataSource.verify(request); + return Right(response); + } catch (e) { + if (e is ServerException) { + return Left(ServerFailure(message: e.message)); + } + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> fcmAdd(FcmAddRequest request) async { + try { + final response = await remoteDataSource.addFcm(request); + return Right(response); + } catch (e) { + if (e is ServerException) { + return Left(ServerFailure(message: e.message)); + } + return Left(ServerFailure(message: e.toString())); + } + } +} diff --git a/lib/features/auth/domain/repository/auth_repository.dart b/lib/features/auth/domain/repository/auth_repository.dart new file mode 100644 index 0000000..cc6b1bd --- /dev/null +++ b/lib/features/auth/domain/repository/auth_repository.dart @@ -0,0 +1,23 @@ +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../../../../core/error/failure.dart'; + +abstract class AuthRepository { + Future> login(LoginRequest request); + + Future> signUp(SignUpRequest request); + + Future> verify( + AuthVerificationRequest request, + ); + + Future> fcmAdd(FcmAddRequest request); +} diff --git a/lib/features/auth/domain/usecases/fcm_add_usecase.dart b/lib/features/auth/domain/usecases/fcm_add_usecase.dart new file mode 100644 index 0000000..b74e723 --- /dev/null +++ b/lib/features/auth/domain/usecases/fcm_add_usecase.dart @@ -0,0 +1,18 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../repository/auth_repository.dart'; + +class FcmAddUseCase extends UseCase { + final AuthRepository repository; + + FcmAddUseCase(this.repository); + + @override + Future> call(FcmAddRequest params) async { + return await repository.fcmAdd(params); + } +} diff --git a/lib/features/auth/domain/usecases/login_usecase.dart b/lib/features/auth/domain/usecases/login_usecase.dart new file mode 100644 index 0000000..20b6cf8 --- /dev/null +++ b/lib/features/auth/domain/usecases/login_usecase.dart @@ -0,0 +1,19 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../repository/auth_repository.dart'; + +class LoginUseCase extends UseCase { + final AuthRepository repository; + + LoginUseCase(this.repository); + + @override + Future> call(LoginRequest params) async { + final response = await repository.login(params); + return response; + } +} diff --git a/lib/features/auth/domain/usecases/sign_up_usecase.dart b/lib/features/auth/domain/usecases/sign_up_usecase.dart new file mode 100644 index 0000000..a84f71a --- /dev/null +++ b/lib/features/auth/domain/usecases/sign_up_usecase.dart @@ -0,0 +1,19 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../repository/auth_repository.dart'; + +class SignUpUseCase extends UseCase { + final AuthRepository repository; + + SignUpUseCase(this.repository); + + @override + Future> call(SignUpRequest params) async { + final response = await repository.signUp(params); + return response; + } +} diff --git a/lib/features/auth/domain/usecases/verify_phone_usecase.dart b/lib/features/auth/domain/usecases/verify_phone_usecase.dart new file mode 100644 index 0000000..846db34 --- /dev/null +++ b/lib/features/auth/domain/usecases/verify_phone_usecase.dart @@ -0,0 +1,22 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart'; +import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../repository/auth_repository.dart'; + +class VerifyPhoneUseCase + extends UseCase { + final AuthRepository repository; + + VerifyPhoneUseCase(this.repository); + + @override + Future> call( + AuthVerificationRequest params, + ) async { + final response = await repository.verify(params); + return response; + } +} diff --git a/lib/features/auth/presentation/bloc/auth/auth_bloc.dart b/lib/features/auth/presentation/bloc/auth/auth_bloc.dart new file mode 100644 index 0000000..b1cc728 --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth/auth_bloc.dart @@ -0,0 +1,116 @@ +import 'package:cargocalculaterapp/core/local_source/local_source.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:cargocalculaterapp/router/app_routes.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../constants/constants.dart'; +import '../../../../../injector_container.dart'; +import '../../../../../router/name_routes.dart'; +import '../../../data/model/login_request.dart'; +import '../../../domain/usecases/login_usecase.dart'; +import '../../pages/auth_confirm/argument/auth_confirm_argument.dart'; + +part 'auth_event.dart'; + +part 'auth_state.dart'; + +class AuthBloc extends Bloc { + AuthBloc(this.loginUseCase) + : super( + const AuthState( + passwordHidden: true, + isLoading: false, + isButtonEnabled: false, + ), + ) { + on(_showHidePassword); + on(_onInputEnter); + on(_onSubmit); + } + + final LoginUseCase loginUseCase; + + void _showHidePassword( + PasswordVisibilityEvent event, + Emitter emit, + ) { + emit(state.copyWith(passwordHidden: !state.passwordHidden)); + } + + void _onInputEnter(OnInputEnterEvent event, Emitter emit) { + emit( + state.copyWith( + isButtonEnabled: + (isEmail(event.login) || isPhoneNumber(event.login)) && + event.password.isNotEmpty, + ), + ); + } + + Future _onSubmit(SubmitEvent event, Emitter emit) async { + emit(state.copyWith(isLoading: true)); + final response = await loginUseCase( + LoginRequest( + email: isEmail(event.login) ? event.login : "", + phone: isPhoneNumber(event.login) + ? event.login.replaceAll("+", "") + : "", + ucode: event.password, + ), + ); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + showErrorSnackBar(); + }, + (r) { + emit(state.copyWith(isLoading: false)); + sl().setUCode(event.password); + Navigator.pushNamed( + rootNavigatorKey.currentContext!, + Routes.authConfirm, + arguments: AuthConfirmArgument( + fullName: "", + mail: isEmail(event.login) ? event.login : "", + phoneNumber: isPhoneNumber(event.login) ? event.login : "", + fromLoginPage: true, + password: event.password, + ), + ); + }, + ); + } + + bool isEmail(String input) { + return RegExConst.emailRegex.hasMatch(input); + } + + bool isPhoneNumber(String input) { + return RegExConst.phoneRegex.hasMatch(input); + } + + void showErrorSnackBar() { + final snackBar = SnackBar( + backgroundColor: Colors.red, + content: Row( + children: [ + const Icon(Icons.error, color: Colors.white), + const SizedBox(width: 8), + Expanded( + child: Text( + AppLocalization.current.login_error, + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 3), + ); + + ScaffoldMessenger.of( + rootNavigatorKey.currentContext!, + ).showSnackBar(snackBar); + } +} diff --git a/lib/features/auth/presentation/bloc/auth/auth_event.dart b/lib/features/auth/presentation/bloc/auth/auth_event.dart new file mode 100644 index 0000000..ad5a805 --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth/auth_event.dart @@ -0,0 +1,32 @@ +part of 'auth_bloc.dart'; + +sealed class AuthEvent extends Equatable { + const AuthEvent(); +} + +final class PasswordVisibilityEvent extends AuthEvent { + const PasswordVisibilityEvent(); + + @override + List get props => []; +} + +final class OnInputEnterEvent extends AuthEvent { + final String login; + final String password; + + const OnInputEnterEvent({required this.login, required this.password}); + + @override + List get props => [login, password]; +} + +final class SubmitEvent extends AuthEvent { + final String login; + final String password; + + const SubmitEvent({required this.login, required this.password}); + + @override + List get props => [login, password]; +} diff --git a/lib/features/auth/presentation/bloc/auth/auth_state.dart b/lib/features/auth/presentation/bloc/auth/auth_state.dart new file mode 100644 index 0000000..e9f22ff --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth/auth_state.dart @@ -0,0 +1,28 @@ +part of 'auth_bloc.dart'; + +class AuthState extends Equatable { + const AuthState({ + required this.passwordHidden, + required this.isLoading, + required this.isButtonEnabled, + }); + + final bool passwordHidden; + final bool isLoading; + final bool isButtonEnabled; + + AuthState copyWith({ + bool? passwordHidden, + bool? isLoading, + bool? isButtonEnabled, + }) { + return AuthState( + isLoading: isLoading ?? this.isLoading, + passwordHidden: passwordHidden ?? this.passwordHidden, + isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled, + ); + } + + @override + List get props => [passwordHidden, isLoading, isButtonEnabled]; +} diff --git a/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_bloc.dart b/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_bloc.dart new file mode 100644 index 0000000..9ea0d82 --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_bloc.dart @@ -0,0 +1,112 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../core/error/failure.dart'; +import '../../../../../core/functions/base_finctions.dart'; +import '../../../../../core/local_source/local_source.dart'; +import '../../../../../injector_container.dart'; +import '../../../../../service/notification_service.dart'; +import '../../../data/model/auth_verification_request.dart'; +import '../../../data/model/fcm_add_request.dart'; +import '../../../data/model/login_request.dart'; +import '../../../data/model/sign_up_request.dart'; +import '../../../domain/usecases/fcm_add_usecase.dart'; +import '../../../domain/usecases/login_usecase.dart'; +import '../../../domain/usecases/sign_up_usecase.dart'; +import '../../../domain/usecases/verify_phone_usecase.dart'; +import '../../pages/auth_confirm/argument/auth_confirm_argument.dart'; + +part 'auth_confirm_event.dart'; + +part 'auth_confirm_state.dart'; + +class AuthConfirmBloc extends Bloc { + AuthConfirmBloc( + this.signUpUseCase, + this.verifyPhoneUseCase, + this.loginUseCase, + this.fcmAddUseCase, + ) : super(const InitialState(true)) { + on(_timerChanged); + on(_codeChanged); + on(_resendCode); + on(_onSubmitCode); + } + + final SignUpUseCase signUpUseCase; + final VerifyPhoneUseCase verifyPhoneUseCase; + final LoginUseCase loginUseCase; + final FcmAddUseCase fcmAddUseCase; + + Future _timerChanged( + TimerChangedEvent event, + Emitter emit, + ) async { + emit(InitialState(event.isVisible)); + } + + void _codeChanged(SmsCodeEnterEvent event, Emitter emit) { + if (event.code.length == 4) { + emit(ButtonEnableState(state.isTimerVisible, event.code)); + } else { + emit(InitialState(state.isTimerVisible)); + } + } + + Future _resendCode( + ResendCodeEvent event, + Emitter emit, + ) async { + add(const TimerChangedEvent(isVisible: true)); + if (event.argument.fromLoginPage) { + await loginUseCase( + LoginRequest( + email: event.argument.mail, + phone: event.argument.phoneNumber?.replaceAll("+", ""), + ucode: event.argument.password, + ), + ); + } else { + await signUpUseCase( + SignUpRequest( + fullname: event.argument.fullName, + email: event.argument.mail, + phoneNumber: event.argument.phoneNumber?.replaceAll("+", ""), + ), + ); + } + } + + Future _onSubmitCode( + OnSubmitEvent event, + Emitter emit, + ) async { + emit(LoadingState(state.isTimerVisible)); + final response = await verifyPhoneUseCase( + AuthVerificationRequest( + phoneNumber: event.argument?.phoneNumber?.replaceAll("+", ""), + smsCode: event.code, + email: event.argument?.mail ?? "", + ), + ); + await response.fold( + (l) { + if (l is ServerFailure) { + Functions.showErrorSnackBar(l.message); + } + emit(InitialState(state.isTimerVisible)); + }, + (r) async { + sl().setHasProfile(true); + sl().setAccessToken(r.accessToken ?? ""); + await _addFcmToken(); + emit(SuccessSate(state.isTimerVisible)); + }, + ); + } + + Future _addFcmToken() async { + final String fcmToken = await NotificationService.getFcmToken(); + await fcmAddUseCase(FcmAddRequest(sfmToken: fcmToken)); + } +} diff --git a/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_event.dart b/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_event.dart new file mode 100644 index 0000000..a3a4cba --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_event.dart @@ -0,0 +1,42 @@ +part of 'auth_confirm_bloc.dart'; + +sealed class AuthConfirmEvent extends Equatable { + const AuthConfirmEvent(); +} + +final class SmsCodeEnterEvent extends AuthConfirmEvent { + final String code; + + const SmsCodeEnterEvent(this.code); + + @override + List get props => [code]; +} + +final class TimerChangedEvent extends AuthConfirmEvent { + final bool isVisible; + + const TimerChangedEvent({required this.isVisible}); + + @override + List get props => [isVisible]; +} + +final class OnSubmitEvent extends AuthConfirmEvent { + final String code; + final AuthConfirmArgument? argument; + + const OnSubmitEvent({required this.code, required this.argument}); + + @override + List get props => [code, argument]; +} + +final class ResendCodeEvent extends AuthConfirmEvent { + final AuthConfirmArgument argument; + + const ResendCodeEvent({required this.argument}); + + @override + List get props => [argument]; +} diff --git a/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_state.dart b/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_state.dart new file mode 100644 index 0000000..053f550 --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth_confirm/auth_confirm_state.dart @@ -0,0 +1,44 @@ +part of 'auth_confirm_bloc.dart'; + +sealed class AuthConfirmState extends Equatable { + const AuthConfirmState(this.isTimerVisible); + + final bool isTimerVisible; +} + +class InitialState extends AuthConfirmState { + const InitialState(super.isTimerVisible); + + @override + List get props => [super.isTimerVisible]; +} + +class LoadingState extends AuthConfirmState { + const LoadingState(super.isTimerVisible); + + @override + List get props => [super.isTimerVisible]; +} + +class SuccessSate extends AuthConfirmState { + const SuccessSate(super.isTimerVisible); + + @override + List get props => [super.isTimerVisible]; +} + +class ErrorState extends AuthConfirmState { + const ErrorState(super.isTimerVisible); + + @override + List get props => [super.isTimerVisible]; +} + +class ButtonEnableState extends AuthConfirmState { + const ButtonEnableState(super.isTimerVisible, this.code); + + final String code; + + @override + List get props => [super.isTimerVisible, code]; +} diff --git a/lib/features/auth/presentation/bloc/sign_up/sign_up_bloc.dart b/lib/features/auth/presentation/bloc/sign_up/sign_up_bloc.dart new file mode 100644 index 0000000..1dd63c9 --- /dev/null +++ b/lib/features/auth/presentation/bloc/sign_up/sign_up_bloc.dart @@ -0,0 +1,95 @@ +import 'package:cargocalculaterapp/core/local_source/local_source.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:cargocalculaterapp/router/app_routes.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../constants/constants.dart'; +import '../../../../../core/error/failure.dart'; +import '../../../../../core/functions/base_finctions.dart'; +import '../../../../../injector_container.dart'; +import '../../../../../router/name_routes.dart'; +import '../../../data/model/sign_up_request.dart'; +import '../../../domain/usecases/sign_up_usecase.dart'; +import '../../pages/auth_confirm/argument/auth_confirm_argument.dart'; + +part 'sign_up_event.dart'; + +part 'sign_up_state.dart'; + +class SignUpBloc extends Bloc { + SignUpBloc(this.signUpUseCase) + : super( + const SignUpState( + isLoading: false, + isButtonEnabled: false, + passwordHidden: true, + ), + ) { + on(_passwordVisibility); + on(_onDataEnter); + on(_signUp); + } + + final SignUpUseCase signUpUseCase; + + void _passwordVisibility( + PasswordVisibilityEvent event, + Emitter emit, + ) { + emit(state.copyWith(isButtonEnabled: !state.isButtonEnabled)); + } + + void _onDataEnter(OnInputEnterEvent event, Emitter emit) { + emit( + state.copyWith( + isButtonEnabled: + (isEmail(event.login) || isPhoneNumber(event.login)) && + event.fullName.isNotEmpty, + ), + ); + } + + bool isEmail(String input) { + return RegExConst.emailRegex.hasMatch(input); + } + + bool isPhoneNumber(String input) { + return RegExConst.phoneRegex.hasMatch(input); + } + + Future _signUp(SubmitEvent event, Emitter emit) async { + emit(state.copyWith(isLoading: true)); + final response = await signUpUseCase( + SignUpRequest( + fullname: event.fullName, + email: isEmail(event.login) ? event.login : "", + phoneNumber: isPhoneNumber(event.login) + ? event.login.replaceAll("+", "") + : "", + ), + ); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + if (l is ServerFailure) { + Functions.showErrorSnackBar(AppLocalization.current.phone_registered); + } + }, + (r) { + sl().setAccessToken(r.token ?? ""); + emit(state.copyWith(isLoading: false)); + Navigator.pushNamed( + rootNavigatorKey.currentContext!, + Routes.authConfirm, + arguments: AuthConfirmArgument( + fullName: event.fullName, + mail: isEmail(event.login) ? event.login : "", + phoneNumber: isPhoneNumber(event.login) ? event.login : "", + fromLoginPage: false, + ), + ); + }, + ); + } +} diff --git a/lib/features/auth/presentation/bloc/sign_up/sign_up_event.dart b/lib/features/auth/presentation/bloc/sign_up/sign_up_event.dart new file mode 100644 index 0000000..23160da --- /dev/null +++ b/lib/features/auth/presentation/bloc/sign_up/sign_up_event.dart @@ -0,0 +1,32 @@ +part of 'sign_up_bloc.dart'; + +sealed class SignUpEvent extends Equatable { + const SignUpEvent(); +} + +final class PasswordVisibilityEvent extends SignUpEvent { + const PasswordVisibilityEvent(); + + @override + List get props => []; +} + +final class OnInputEnterEvent extends SignUpEvent { + final String login; + final String fullName; + + const OnInputEnterEvent({required this.login, required this.fullName}); + + @override + List get props => [login, fullName]; +} + +final class SubmitEvent extends SignUpEvent { + final String login; + final String fullName; + + const SubmitEvent({required this.login, required this.fullName}); + + @override + List get props => [login, fullName]; +} diff --git a/lib/features/auth/presentation/bloc/sign_up/sign_up_state.dart b/lib/features/auth/presentation/bloc/sign_up/sign_up_state.dart new file mode 100644 index 0000000..2509997 --- /dev/null +++ b/lib/features/auth/presentation/bloc/sign_up/sign_up_state.dart @@ -0,0 +1,28 @@ +part of 'sign_up_bloc.dart'; + +class SignUpState extends Equatable { + const SignUpState({ + required this.passwordHidden, + required this.isLoading, + required this.isButtonEnabled, + }); + + final bool passwordHidden; + final bool isLoading; + final bool isButtonEnabled; + + SignUpState copyWith({ + bool? passwordHidden, + bool? isLoading, + bool? isButtonEnabled, + }) { + return SignUpState( + isLoading: isLoading ?? this.isLoading, + passwordHidden: passwordHidden ?? this.passwordHidden, + isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled, + ); + } + + @override + List get props => [passwordHidden, isLoading, isButtonEnabled]; +} diff --git a/lib/features/auth/presentation/pages/auth/auth_page.dart b/lib/features/auth/presentation/pages/auth/auth_page.dart new file mode 100644 index 0000000..50415c7 --- /dev/null +++ b/lib/features/auth/presentation/pages/auth/auth_page.dart @@ -0,0 +1,185 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import '../../../../../core/widgets/loading/custom_loading.dart'; +import '../../../../../core/widgets/text_filds/custom_text_field_name.dart'; +import '../../../../../router/name_routes.dart'; +import '../../bloc/auth/auth_bloc.dart'; +import '../mixin/auth_mixin.dart'; + +class AuthPage extends StatefulWidget { + const AuthPage({super.key}); + + @override + State createState() => _AuthPageState(); +} + +class _AuthPageState extends State with AuthMixin { + @override + void initState() { + initControllers(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Scaffold( + appBar: AppBar(), + body: ListView( + padding: AppUtils.kPaddingHor16, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 60), + child: SvgPicture.asset("assets/svg/ic_logo_auth.svg"), + ), + AppUtils.kBoxHeight48, + Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius12, + color: context.color.iconBackground, + ), + padding: AppUtils.kPaddingAll6, + child: SvgPicture.asset("assets/svg/ic_user.svg"), + ), + AppUtils.kBoxWidth12, + Text( + AppLocalization.current.auth_login, + style: context.text.authTitle, + ), + ], + ), + AppUtils.kBoxHeight32, + CustomTextFieldName( + hint: AppLocalization.current.enter_phone_or_mail, + inputType: TextInputType.text, + name: AppLocalization.current.phone, + controller: loginController, + onchange: (value) { + context.read().add( + OnInputEnterEvent( + login: value, + password: passwordController.text, + ), + ); + }, + ), + AppUtils.kBoxHeight16, + CustomTextFieldName( + hint: "********", + prefixWidget: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "CT-", + style: context.text.profileCategory.copyWith( + fontWeight: FontWeight.w400, + ), + ), + ], + ), + inputType: TextInputType.number, + name: AppLocalization.current.password, + obscureText: state.passwordHidden, + maxLines: 1, + controller: passwordController, + suffix: IconButton( + onPressed: () { + context.read().add( + const PasswordVisibilityEvent(), + ); + }, + icon: Icon( + state.passwordHidden + ? Icons.visibility_off_rounded + : Icons.visibility_rounded, + ), + ), + onchange: (value) { + context.read().add( + OnInputEnterEvent( + login: loginController.text, + password: value, + ), + ); + }, + ), + AppUtils.kBoxHeight48, + Padding( + padding: AppUtils.kPaddingHor34, + child: ElevatedButton( + onPressed: state.isButtonEnabled && !state.isLoading + ? () { + context.read().add( + SubmitEvent( + login: loginController.text, + password: "CT-${passwordController.text}", + ), + ); + } + : null, + child: state.isLoading + ? const CustomLoadingWidget() + : Text(AppLocalization.current.auth), + ), + ), + AppUtils.kBoxHeight16, + Text( + AppLocalization.current.no_account, + style: context.text.authDesc, + textAlign: TextAlign.center, + ), + AppUtils.kBoxHeight16, + Padding( + padding: AppUtils.kPaddingHor34, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.pushNamed(context, Routes.signUp); + }, + borderRadius: AppUtils.kBorderRadius24, + child: Ink( + decoration: BoxDecoration( + color: context.color.scaffoldBackgroundColor, + borderRadius: AppUtils.kBorderRadius24, + border: Border.all(color: context.color.primaryColor), + ), + height: 56, + padding: AppUtils.kPaddingHor16, + child: Center( + child: Text( + AppLocalization.current.sign_up, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: context.color.textColor, + ), + ), + ), + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + @override + void dispose() { + disposeControllers(); + super.dispose(); + } +} diff --git a/lib/features/auth/presentation/pages/auth_confirm/argument/auth_confirm_argument.dart b/lib/features/auth/presentation/pages/auth_confirm/argument/auth_confirm_argument.dart new file mode 100644 index 0000000..f70c3ba --- /dev/null +++ b/lib/features/auth/presentation/pages/auth_confirm/argument/auth_confirm_argument.dart @@ -0,0 +1,15 @@ +class AuthConfirmArgument { + final String? fullName; + final String? mail; + final String? phoneNumber; + final bool fromLoginPage; + final String? password; + + AuthConfirmArgument({ + this.fullName, + required this.mail, + required this.phoneNumber, + required this.fromLoginPage, + this.password, + }); +} diff --git a/lib/features/auth/presentation/pages/auth_confirm/auth_confirm_page.dart b/lib/features/auth/presentation/pages/auth_confirm/auth_confirm_page.dart new file mode 100644 index 0000000..7dc35c2 --- /dev/null +++ b/lib/features/auth/presentation/pages/auth_confirm/auth_confirm_page.dart @@ -0,0 +1,233 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/core/widgets/loading/custom_loading.dart'; +import 'package:cargocalculaterapp/features/auth/presentation/pages/auth_confirm/timer_widget.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:cargocalculaterapp/router/name_routes.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:pin_code_fields/pin_code_fields.dart'; +import '../../../../../core/theme/app_text_styles.dart'; +import '../../../../../core/theme/colors/app_colors.dart'; +import '../../bloc/auth_confirm/auth_confirm_bloc.dart'; +import '../mixin/auth_confirm_mixin.dart'; +import 'argument/auth_confirm_argument.dart'; + +class AuthConfirmPage extends StatefulWidget { + const AuthConfirmPage({super.key, required this.argument}); + + final AuthConfirmArgument? argument; + + @override + State createState() => _AuthConfirmPageState(); +} + +class _AuthConfirmPageState extends State + with AuthConfirmMixin { + @override + void initState() { + initControllers(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is SuccessSate) { + Navigator.pushNamedAndRemoveUntil( + context, + Routes.main, + (route) => route.isFirst, + ); + } + }, + builder: (context, state) { + return Scaffold( + appBar: AppBar(), + body: Padding( + padding: AppUtils.kPaddingAll16, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.iconBackground, + ), + padding: AppUtils.kPaddingAll16, + child: SvgPicture.asset( + "assets/svg/ic_mail.svg", + width: 32, + height: 32, + ), + ), + AppUtils.kBoxHeight16, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + (widget.argument?.mail?.isNotEmpty ?? false) + ? AppLocalization.current.confirm_email + : AppLocalization.current.confirm_phone_text, + style: context.text.authTitle, + textAlign: TextAlign.center, + ), + ), + AppUtils.kBoxHeight16, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + (widget.argument?.mail?.isNotEmpty ?? false) + ? AppLocalization.current.enter_code_mail( + widget.argument?.mail ?? "", + ) + : AppLocalization.current.enter_code_phone( + widget.argument?.phoneNumber ?? "", + ), + style: context.text.authDesc, + textAlign: TextAlign.center, + ), + ), + AppUtils.kBoxHeight48, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 296, + child: PinCodeTextField( + controller: confirmController, + appContext: context, + autoDisposeControllers: false, + length: 4, + animationType: AnimationType.scale, + enabled: true, + enablePinAutofill: true, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + onChanged: (code) { + context.read().add( + SmsCodeEnterEvent(code), + ); + }, + autoFocus: true, + keyboardType: const TextInputType.numberWithOptions(), + showCursor: true, + textStyle: context.text.bigTitle, + cursorColor: context.color.accentColor, + cursorHeight: 20, + enableActiveFill: true, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + pinTheme: PinTheme( + activeColor: + state is ErrorState + ? ThemeColors.timerRed + : state is SuccessSate + ? ThemeColors.successInputSMS + : context.color.borderColor, + selectedFillColor: + context.color.scaffoldBackgroundColor, + activeFillColor: + context.color.scaffoldBackgroundColor, + shape: PinCodeFieldShape.box, + inactiveFillColor: + context.color.scaffoldBackgroundColor, + borderRadius: const BorderRadius.all( + Radius.circular(12), + ), + borderWidth: 1, + inactiveColor: + state is ErrorState + ? ThemeColors.timerRed + : state is SuccessSate + ? ThemeColors.successInputSMS + : context.color.borderColor, + fieldWidth: 56, + fieldHeight: 64, + selectedColor: + state is ErrorState + ? ThemeColors.timerRed + : state is SuccessSate + ? ThemeColors.successInputSMS + : context.color.primaryColor, + ), + ), + ), + ], + ), + if (state is ErrorState) AppUtils.kBoxHeight16, + if (state is ErrorState) + Center( + child: Text( + AppLocalization.current.incorrect_code, + style: AppTextStyles.timerBlue, + ), + ), + AppUtils.kBoxHeight24, + if (state.isTimerVisible) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalization.current.send_sms, + style: AppTextStyles.timerBlue, + ), + const TimerWidget(), + ], + ), + if (!state.isTimerVisible) + Center( + child: GestureDetector( + onTap: () { + if (widget.argument != null) { + context.read().add( + ResendCodeEvent(argument: widget.argument!), + ); + } + }, + child: Text( + AppLocalization.current.send_sms, + style: AppTextStyles.timerBlue, + ), + ), + ), + AppUtils.kBoxHeight48, + Padding( + padding: AppUtils.kPaddingHor34, + child: ElevatedButton( + onPressed: + (confirmController.text.length == 4 && + state is! LoadingState) + ? () { + context.read().add( + OnSubmitEvent( + code: confirmController.text, + argument: widget.argument, + ), + ); + } + : null, + child: + state is LoadingState + ? const CustomLoadingWidget() + : Text(AppLocalization.current.confirm), + ), + ), + ], + ), + ), + ); + }, + ); + } + + @override + void dispose() { + disposeController(); + super.dispose(); + } +} diff --git a/lib/features/auth/presentation/pages/auth_confirm/timer_widget.dart b/lib/features/auth/presentation/pages/auth_confirm/timer_widget.dart new file mode 100644 index 0000000..0c9e6d6 --- /dev/null +++ b/lib/features/auth/presentation/pages/auth_confirm/timer_widget.dart @@ -0,0 +1,49 @@ +import 'dart:async'; +import 'package:cargocalculaterapp/core/theme/app_text_styles.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../bloc/auth_confirm/auth_confirm_bloc.dart'; + +class TimerWidget extends StatefulWidget { + const TimerWidget({super.key}); + + @override + State createState() => _TimerWidgetState(); +} + +class _TimerWidgetState extends State { + Timer? timer; + int time = 60; + String timeText = "60"; + + @override + void initState() { + if (timer?.isActive ?? false) { + timer?.cancel(); + } + timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) async { + time--; + if (time == 0) { + timer.cancel(); + context.read().add( + const TimerChangedEvent(isVisible: false), + ); + } + setState(() { + timeText = time.toString().padLeft(2, "0"); + }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Text(" 00:$timeText", style: AppTextStyles.timerStyle); + } + + @override + void dispose() { + timer?.cancel(); + super.dispose(); + } +} diff --git a/lib/features/auth/presentation/pages/mixin/auth_confirm_mixin.dart b/lib/features/auth/presentation/pages/mixin/auth_confirm_mixin.dart new file mode 100644 index 0000000..618494d --- /dev/null +++ b/lib/features/auth/presentation/pages/mixin/auth_confirm_mixin.dart @@ -0,0 +1,13 @@ +import 'package:flutter/cupertino.dart'; + +mixin AuthConfirmMixin { + late TextEditingController confirmController; + + void initControllers() { + confirmController = TextEditingController(); + } + + void disposeController() { + confirmController.dispose(); + } +} diff --git a/lib/features/auth/presentation/pages/mixin/auth_mixin.dart b/lib/features/auth/presentation/pages/mixin/auth_mixin.dart new file mode 100644 index 0000000..d7b0060 --- /dev/null +++ b/lib/features/auth/presentation/pages/mixin/auth_mixin.dart @@ -0,0 +1,16 @@ +import 'package:flutter/cupertino.dart'; + +mixin AuthMixin { + late TextEditingController loginController; + late TextEditingController passwordController; + + void initControllers() { + loginController = TextEditingController(); + passwordController = TextEditingController(); + } + + void disposeControllers() { + loginController.dispose(); + passwordController.dispose(); + } +} diff --git a/lib/features/auth/presentation/pages/mixin/sign_up_mixin.dart b/lib/features/auth/presentation/pages/mixin/sign_up_mixin.dart new file mode 100644 index 0000000..e67ea66 --- /dev/null +++ b/lib/features/auth/presentation/pages/mixin/sign_up_mixin.dart @@ -0,0 +1,16 @@ +import 'package:flutter/cupertino.dart'; + +mixin SignUpMixin { + late TextEditingController loginController; + late TextEditingController fullNameController; + + void initControllers() { + loginController = TextEditingController(); + fullNameController = TextEditingController(); + } + + void disposeControllers() { + loginController.dispose(); + fullNameController.dispose(); + } +} diff --git a/lib/features/auth/presentation/pages/sign_up/sign_up_page.dart b/lib/features/auth/presentation/pages/sign_up/sign_up_page.dart new file mode 100644 index 0000000..3ea0fe7 --- /dev/null +++ b/lib/features/auth/presentation/pages/sign_up/sign_up_page.dart @@ -0,0 +1,158 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/features/auth/presentation/pages/mixin/sign_up_mixin.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import '../../../../../core/utils/app_utils.dart'; +import '../../../../../core/widgets/loading/custom_loading.dart'; +import '../../../../../core/widgets/text_filds/custom_text_field_name.dart'; +import '../../../../../generated/l10n.dart'; +import '../../bloc/sign_up/sign_up_bloc.dart'; + +class SignUpPage extends StatefulWidget { + const SignUpPage({super.key}); + + @override + State createState() => _SignUpPageState(); +} + +class _SignUpPageState extends State with SignUpMixin { + @override + void initState() { + initControllers(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Scaffold( + appBar: AppBar(), + body: ListView( + padding: AppUtils.kPaddingAll16, + children: [ + Center( + child: Container( + width: 64, + height: 64, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.iconBackground, + ), + padding: AppUtils.kPaddingAll16, + child: SvgPicture.asset( + "assets/svg/ic_add_user.svg", + width: 32, + height: 32, + ), + ), + ), + AppUtils.kBoxHeight16, + Text( + AppLocalization.current.sign_up, + style: context.text.authTitle, + textAlign: TextAlign.center, + ), + AppUtils.kBoxHeight24, + CustomTextFieldName( + hint: AppLocalization.current.full_name_hint, + inputType: TextInputType.name, + name: AppLocalization.current.full_name, + controller: fullNameController, + onchange: (value) { + context.read().add( + OnInputEnterEvent( + login: loginController.text, + fullName: value, + ), + ); + }, + ), + AppUtils.kBoxHeight16, + CustomTextFieldName( + hint: AppLocalization.current.enter_phone_or_mail, + inputType: TextInputType.text, + name: AppLocalization.current.phone, + controller: loginController, + onchange: (value) { + context.read().add( + OnInputEnterEvent( + login: value, + fullName: fullNameController.text, + ), + ); + }, + ), + AppUtils.kBoxHeight48, + Padding( + padding: AppUtils.kPaddingHor34, + child: ElevatedButton( + onPressed: + state.isButtonEnabled && !state.isLoading + ? () { + context.read().add( + SubmitEvent( + login: loginController.text, + fullName: fullNameController.text, + ), + ); + } + : null, + child: + state.isLoading + ? const CustomLoadingWidget() + : Text(AppLocalization.current.sign_up), + ), + ), + AppUtils.kBoxHeight16, + Text( + AppLocalization.current.has_account, + style: context.text.authDesc, + textAlign: TextAlign.center, + ), + AppUtils.kBoxHeight16, + Padding( + padding: AppUtils.kPaddingHor34, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.pop(context); + }, + borderRadius: AppUtils.kBorderRadius24, + child: Ink( + decoration: BoxDecoration( + color: context.color.scaffoldBackgroundColor, + borderRadius: AppUtils.kBorderRadius24, + border: Border.all(color: context.color.primaryColor), + ), + height: 56, + padding: AppUtils.kPaddingHor16, + child: Center( + child: Text( + AppLocalization.current.auth, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: context.color.textColor, + ), + ), + ), + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + @override + void dispose() { + disposeControllers(); + super.dispose(); + } +} diff --git a/lib/features/calculator/data/data_source/calculator_remote_data_source.dart b/lib/features/calculator/data/data_source/calculator_remote_data_source.dart new file mode 100644 index 0000000..d834af7 --- /dev/null +++ b/lib/features/calculator/data/data_source/calculator_remote_data_source.dart @@ -0,0 +1,9 @@ +import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart'; + +abstract class CalculatorRemoteDataSource { + Future priceCalculate(PriceCalculateRequest request); + Future createLead(LeadCreateRequest request); +} diff --git a/lib/features/calculator/data/data_source/calculator_remote_data_source_impl.dart b/lib/features/calculator/data/data_source/calculator_remote_data_source_impl.dart new file mode 100644 index 0000000..1b6cf6b --- /dev/null +++ b/lib/features/calculator/data/data_source/calculator_remote_data_source_impl.dart @@ -0,0 +1,64 @@ +import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart'; +import 'package:dio/dio.dart'; +import '../../../../constants/constants.dart'; +import '../../../../core/error/exceptions.dart'; +import '../../../../core/local_source/local_source.dart'; +import '../../../../injector_container.dart'; +import '../model/lead_create_response.dart'; +import '../model/lead_create_request.dart'; +import 'calculator_remote_data_source.dart'; + +class CalculatorRemoteDataSourceImpl extends CalculatorRemoteDataSource { + final Dio dio; + + CalculatorRemoteDataSourceImpl(this.dio); + + @override + Future priceCalculate( + PriceCalculateRequest request, + ) async { + try { + final Response response = await dio.post( + Constants.baseUrl + Urls.calculatePrice, + options: + DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return CalculatePriceResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future createLead(LeadCreateRequest request) async { + try { + final Response response = await dio.post( + Constants.baseUrl + Urls.lead, + options: + DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return LeadCreateResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } +} diff --git a/lib/features/calculator/data/model/calculate_price_response.dart b/lib/features/calculator/data/model/calculate_price_response.dart new file mode 100644 index 0000000..050e595 --- /dev/null +++ b/lib/features/calculator/data/model/calculate_price_response.dart @@ -0,0 +1,15 @@ +class CalculatePriceResponse { + CalculatePriceResponse({this.price}); + + CalculatePriceResponse.fromJson(dynamic json) { + price = double.tryParse(json['price'].toString()); + } + + double? price; + + Map toJson() { + final map = {}; + map['price'] = price; + return map; + } +} diff --git a/lib/features/calculator/data/model/lead_create_request.dart b/lib/features/calculator/data/model/lead_create_request.dart new file mode 100644 index 0000000..61686ca --- /dev/null +++ b/lib/features/calculator/data/model/lead_create_request.dart @@ -0,0 +1,73 @@ +class LeadCreateRequest { + LeadCreateRequest({ + this.warehouseCode, + this.wharehouseUz, + this.wharehouseRu, + this.wharehouseZh, + this.nameUz, + this.nameRu, + this.nameZh, + this.userUcode, + this.phoneNumber, + this.gmail, + this.weight, + this.price, + this.status, + this.averageWeightKg, + this.m3, + }); + + LeadCreateRequest.fromJson(dynamic json) { + warehouseCode = json['warehouse_code']; + wharehouseUz = json['wharehouseUz']; + wharehouseRu = json['wharehouseRu']; + wharehouseZh = json['wharehouseZh']; + nameUz = json['nameUz']; + nameRu = json['nameRu']; + nameZh = json['nameZh']; + userUcode = json['user_ucode']; + phoneNumber = json['phone_number']; + gmail = json['gmail']; + weight = json['weight']; + price = json['price']; + status = json['status']; + averageWeightKg = json['average_weight_kg']; + m3 = json['status']; + } + + int? warehouseCode; + String? wharehouseUz; + String? wharehouseRu; + String? wharehouseZh; + String? nameUz; + String? nameRu; + String? nameZh; + String? userUcode; + String? phoneNumber; + String? gmail; + String? weight; + String? price; + String? status; + String? averageWeightKg; + String? m3; + + Map toJson() { + final map = {}; + map['warehouse_code'] = warehouseCode; + map['wharehouseUz'] = wharehouseUz; + map['wharehouseRu'] = wharehouseRu; + map['wharehouseZh'] = wharehouseZh; + map['nameUz'] = nameUz; + map['nameRu'] = nameRu; + map['nameZh'] = nameZh; + map['user_ucode'] = userUcode; + map['phone_number'] = phoneNumber; + map['gmail'] = gmail; + map['weight'] = weight; + map['price'] = price; + map['status'] = status; + map['average_weight_kg'] = averageWeightKg; + map['m3'] = m3; + return map; + } +} diff --git a/lib/features/calculator/data/model/lead_create_response.dart b/lib/features/calculator/data/model/lead_create_response.dart new file mode 100644 index 0000000..6bae950 --- /dev/null +++ b/lib/features/calculator/data/model/lead_create_response.dart @@ -0,0 +1,118 @@ +class LeadCreateResponse { + LeadCreateResponse({ + this.userUcode, + this.phoneNumber, + this.gmail, + this.weight, + this.status, + this.nameUz, + this.nameRu, + this.nameZh, + this.wharehouseUz, + this.wharehouseRu, + this.wharehouseZh, + this.id, + this.createdAt, + this.updatedAt, + }); + + LeadCreateResponse.fromJson(dynamic json) { + userUcode = json['user_ucode']; + phoneNumber = json['phone_number']; + gmail = json['gmail']; + weight = json['weight']; + status = json['status'] != null ? Status.fromJson(json['status']) : null; + nameUz = json['nameUz']; + nameRu = json['nameRu']; + nameZh = json['nameZh']; + wharehouseUz = json['wharehouseUz']; + wharehouseRu = json['wharehouseRu']; + wharehouseZh = json['wharehouseZh']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + id = json['id']; + } + + String? userUcode; + String? phoneNumber; + String? gmail; + String? weight; + Status? status; + String? nameUz; + String? nameRu; + String? nameZh; + String? wharehouseUz; + String? wharehouseRu; + String? wharehouseZh; + String? id; + String? createdAt; + String? updatedAt; + + Map toJson() { + final map = {}; + map['user_ucode'] = userUcode; + map['phone_number'] = phoneNumber; + map['gmail'] = gmail; + map['weight'] = weight; + if (status != null) { + map['status'] = status?.toJson(); + } + map['nameUz'] = nameUz; + map['nameRu'] = nameRu; + map['nameZh'] = nameZh; + map['wharehouseUz'] = wharehouseUz; + map['wharehouseRu'] = wharehouseRu; + map['wharehouseZh'] = wharehouseZh; + map['createdAt'] = createdAt; + map['updatedAt'] = updatedAt; + + map['id'] = id; + return map; + } +} + +class Status { + Status({this.code, this.translations}); + + Status.fromJson(dynamic json) { + code = json['code']; + translations = + json['translations'] != null + ? Translations.fromJson(json['translations']) + : null; + } + + String? code; + Translations? translations; + + Map toJson() { + final map = {}; + map['code'] = code; + if (translations != null) { + map['translations'] = translations?.toJson(); + } + return map; + } +} + +class Translations { + Translations({this.uz, this.ru, this.zh}); + + Translations.fromJson(dynamic json) { + uz = json['uz']; + ru = json['ru']; + zh = json['zh']; + } + + String? uz; + String? ru; + String? zh; + + Map toJson() { + final map = {}; + map['uz'] = uz; + map['ru'] = ru; + map['zh'] = zh; + return map; + } +} diff --git a/lib/features/calculator/data/model/price_calculate_request.dart b/lib/features/calculator/data/model/price_calculate_request.dart new file mode 100644 index 0000000..b040f8d --- /dev/null +++ b/lib/features/calculator/data/model/price_calculate_request.dart @@ -0,0 +1,20 @@ +class PriceCalculateRequest { + PriceCalculateRequest({ + this.avg, + this.m3,}); + + PriceCalculateRequest.fromJson(dynamic json) { + avg = json['avg']; + m3 = json['m3']; + } + double? avg; + double? m3; + + Map toJson() { + final map = {}; + map['avg'] = avg; + map['m3'] = m3; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/calculator/data/repository/calculator_repository_impl.dart b/lib/features/calculator/data/repository/calculator_repository_impl.dart new file mode 100644 index 0000000..afd097b --- /dev/null +++ b/lib/features/calculator/data/repository/calculator_repository_impl.dart @@ -0,0 +1,37 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart'; +import 'package:cargocalculaterapp/features/calculator/domain/repository/calculator_repository.dart'; +import 'package:dartz/dartz.dart'; + +import '../data_source/calculator_remote_data_source.dart'; + +class CalculatorRepositoryImpl extends CalculatorRepository { + final CalculatorRemoteDataSource calculatorRemoteDataSource; + + CalculatorRepositoryImpl(this.calculatorRemoteDataSource); + + @override + Future> calculatePrice( + PriceCalculateRequest request, + ) async { + try { + final response = await calculatorRemoteDataSource.priceCalculate(request); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> createLead(LeadCreateRequest request) async { + try { + final response = await calculatorRemoteDataSource.createLead(request); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } +} diff --git a/lib/features/calculator/domain/repository/calculator_repository.dart b/lib/features/calculator/domain/repository/calculator_repository.dart new file mode 100644 index 0000000..0177c60 --- /dev/null +++ b/lib/features/calculator/domain/repository/calculator_repository.dart @@ -0,0 +1,15 @@ +import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart'; +import 'package:dartz/dartz.dart'; +import '../../../../core/error/failure.dart'; + +abstract class CalculatorRepository { + Future> calculatePrice( + PriceCalculateRequest request, + ); + Future> createLead( + LeadCreateRequest request, + ); +} diff --git a/lib/features/calculator/domain/usecase/calculate_price_usecase.dart b/lib/features/calculator/domain/usecase/calculate_price_usecase.dart new file mode 100644 index 0000000..9bcf345 --- /dev/null +++ b/lib/features/calculator/domain/usecase/calculate_price_usecase.dart @@ -0,0 +1,21 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart'; +import 'package:dartz/dartz.dart'; + +import '../repository/calculator_repository.dart'; + +class CalculatePriceUseCase + extends UseCase { + final CalculatorRepository repository; + + CalculatePriceUseCase(this.repository); + + @override + Future> call( + PriceCalculateRequest params, + ) async { + return await repository.calculatePrice(params); + } +} diff --git a/lib/features/calculator/domain/usecase/create_lead_usecase.dart b/lib/features/calculator/domain/usecase/create_lead_usecase.dart new file mode 100644 index 0000000..9869bf9 --- /dev/null +++ b/lib/features/calculator/domain/usecase/create_lead_usecase.dart @@ -0,0 +1,21 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart'; +import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../repository/calculator_repository.dart'; + +class CreateLeadUseCase + extends UseCase { + final CalculatorRepository repository; + + CreateLeadUseCase(this.repository); + + @override + Future> call( + LeadCreateRequest params, + ) async { + return await repository.createLead(params); + } +} diff --git a/lib/features/calculator/presentation/arguments/calculator_info_argument.dart b/lib/features/calculator/presentation/arguments/calculator_info_argument.dart new file mode 100644 index 0000000..8dc2a69 --- /dev/null +++ b/lib/features/calculator/presentation/arguments/calculator_info_argument.dart @@ -0,0 +1,17 @@ +class CalculatorInfoArgument { + final String productName; + final String weraHouse; + final double weight; + final double size; + final double averageWeight; + final double deliveryPrice; + + CalculatorInfoArgument({ + required this.productName, + required this.weraHouse, + required this.weight, + required this.size, + required this.averageWeight, + required this.deliveryPrice, + }); +} diff --git a/lib/features/calculator/presentation/bloc/calculator_bloc.dart b/lib/features/calculator/presentation/bloc/calculator_bloc.dart new file mode 100644 index 0000000..e6a02f7 --- /dev/null +++ b/lib/features/calculator/presentation/bloc/calculator_bloc.dart @@ -0,0 +1,76 @@ +import 'package:cargocalculaterapp/router/app_routes.dart'; +import 'package:cargocalculaterapp/router/name_routes.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../data/model/price_calculate_request.dart'; +import '../../domain/usecase/calculate_price_usecase.dart'; +import '../arguments/calculator_info_argument.dart'; + +part 'calculator_event.dart'; + +part 'calculator_state.dart'; + +class CalculatorBloc extends Bloc { + CalculatorBloc(this.calculatePriceUseCase) + : super(const CalculatorState(isLoading: false)) { + on(_weightSizeEntered); + on(_wareHouseSelect); + on(_calculate); + } + + final CalculatePriceUseCase calculatePriceUseCase; + + + void _weightSizeEntered( + WeightSizeEnterEvent event, + Emitter emit, + ) { + emit( + state.copyWith( + size: double.tryParse(event.size.replaceAll(",", "")), + weight: double.tryParse(event.weight.replaceAll(",", "")), + ), + ); + } + + void _wareHouseSelect( + WareHouseSelectEvent event, + Emitter emit, + ) { + emit(state.copyWith(selectedWarehouse: event.wareHouse)); + } + + Future _calculate( + CalculateEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true)); + final response = await calculatePriceUseCase( + PriceCalculateRequest( + m3: state.size, + avg: (state.weight ?? 0) / (state.size ?? 1), + ), + ); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + }, + (r) { + emit(state.copyWith(isLoading: false)); + Navigator.pushNamed( + rootNavigatorKey.currentContext!, + Routes.calculationInfo, + arguments: CalculatorInfoArgument( + productName: event.productName, + deliveryPrice: r.price ?? 0, + size: state.size ?? 0, + averageWeight: (state.weight ?? 0) / (state.size ?? 1), + weight: state.weight ?? 0, + weraHouse: state.selectedWarehouse ?? "", + ), + ); + }, + ); + } +} diff --git a/lib/features/calculator/presentation/bloc/calculator_event.dart b/lib/features/calculator/presentation/bloc/calculator_event.dart new file mode 100644 index 0000000..3f482cd --- /dev/null +++ b/lib/features/calculator/presentation/bloc/calculator_event.dart @@ -0,0 +1,33 @@ +part of 'calculator_bloc.dart'; + +sealed class CalculatorEvent extends Equatable { + const CalculatorEvent(); +} + +final class WeightSizeEnterEvent extends CalculatorEvent { + final String weight; + final String size; + + const WeightSizeEnterEvent({required this.weight, required this.size}); + + @override + List get props => [weight, size]; +} + +final class WareHouseSelectEvent extends CalculatorEvent { + final String wareHouse; + + const WareHouseSelectEvent({required this.wareHouse}); + + @override + List get props => [wareHouse]; +} + +final class CalculateEvent extends CalculatorEvent { + const CalculateEvent({required this.productName}); + + final String productName; + + @override + List get props => [productName]; +} diff --git a/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_bloc.dart b/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_bloc.dart new file mode 100644 index 0000000..711479c --- /dev/null +++ b/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_bloc.dart @@ -0,0 +1,120 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../../../../../constants/constants.dart'; +import '../../../../../generated/l10n.dart'; +import '../../../../../router/app_routes.dart'; +import '../../../../../router/name_routes.dart'; +import '../../../data/model/lead_create_request.dart'; +import '../../../domain/usecase/create_lead_usecase.dart'; +import '../../arguments/calculator_info_argument.dart'; +import '../../pages/calculator_info/dialog/order_dialog.dart'; + +part 'calculator_info_event.dart'; + +part 'calculator_info_state.dart'; + +class CalculatorInfoBloc + extends Bloc { + CalculatorInfoBloc(this.createLeadUseCase) + : super(const CalculatorInfoState(isLoading: false)) { + on(_createLead); + on(_showSuccessDialog); + } + + final CreateLeadUseCase createLeadUseCase; + + Future _createLead( + CreateLeadEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true)); + final local = Localizations.localeOf( + rootNavigatorKey.currentContext!, + ).languageCode; + final response = await createLeadUseCase( + LeadCreateRequest( + status: "NOT_CONTACTED", + m3: "${event.argument.size}", + averageWeightKg: "${event.argument.averageWeight}", + weight: "${event.argument.weight}", + nameRu: local == "ru" ? event.argument.productName : "", + nameUz: local == "uz" ? event.argument.productName : "", + nameZh: local == "zh" ? event.argument.productName : "", + price: "${event.argument.deliveryPrice}", + warehouseCode: int.tryParse(event.argument.weraHouse), + wharehouseRu: AppConst.warehouseOptions[event.argument.weraHouse]?.ru, + wharehouseUz: AppConst.warehouseOptions[event.argument.weraHouse]?.uz, + wharehouseZh: AppConst.warehouseOptions[event.argument.weraHouse]?.zh, + ), + ); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + }, + (r) { + emit(state.copyWith(isLoading: false)); + add(ShowSuccessDialogEvent(isCall: event.isCall)); + }, + ); + } + + void _showSuccessDialog( + ShowSuccessDialogEvent event, + Emitter emit, + ) { + if (event.isCall) { + showCupertinoModalPopup( + context: rootNavigatorKey.currentContext!, + builder: (context) => CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + child: Text( + "+998 99-110-22-22", + style: TextStyle(color: context.color.primaryColor), + ), + onPressed: () async { + final Uri url = Uri(scheme: 'tel', path: "+998991102222"); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } + Navigator.pop(rootNavigatorKey.currentContext!, true); + }, + ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () { + Navigator.pop(rootNavigatorKey.currentContext!); + }, + child: Text( + AppLocalization.current.cancel, + style: TextStyle(color: context.color.textColor), + ), + ), + ), + ).then((value) { + if (value is bool) { + Navigator.pushNamedAndRemoveUntil( + rootNavigatorKey.currentContext!, + Routes.main, + (route) => false, + ); + } + }); + } else { + showDialog( + context: rootNavigatorKey.currentContext!, + builder: (context) => OrderDialog(isCall: event.isCall), + ).then((value) { + Navigator.pushNamedAndRemoveUntil( + rootNavigatorKey.currentContext!, + Routes.main, + (route) => false, + ); + }); + } + } +} diff --git a/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_event.dart b/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_event.dart new file mode 100644 index 0000000..6da3d2a --- /dev/null +++ b/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_event.dart @@ -0,0 +1,24 @@ +part of 'calculator_info_bloc.dart'; + +sealed class CalculatorInfoEvent extends Equatable { + const CalculatorInfoEvent(); +} + +final class CreateLeadEvent extends CalculatorInfoEvent { + const CreateLeadEvent({required this.argument, required this.isCall}); + + final bool isCall; + final CalculatorInfoArgument argument; + + @override + List get props => [argument, isCall]; +} + +final class ShowSuccessDialogEvent extends CalculatorInfoEvent { + const ShowSuccessDialogEvent({required this.isCall}); + + final bool isCall; + + @override + List get props => [isCall]; +} diff --git a/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_state.dart b/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_state.dart new file mode 100644 index 0000000..b4c0337 --- /dev/null +++ b/lib/features/calculator/presentation/bloc/calculator_info/calculator_info_state.dart @@ -0,0 +1,14 @@ +part of 'calculator_info_bloc.dart'; + +class CalculatorInfoState extends Equatable { + const CalculatorInfoState({required this.isLoading}); + + final bool isLoading; + + CalculatorInfoState copyWith({bool? isLoading}) { + return CalculatorInfoState(isLoading: isLoading ?? this.isLoading); + } + + @override + List get props => [isLoading]; +} diff --git a/lib/features/calculator/presentation/bloc/calculator_state.dart b/lib/features/calculator/presentation/bloc/calculator_state.dart new file mode 100644 index 0000000..dc09efc --- /dev/null +++ b/lib/features/calculator/presentation/bloc/calculator_state.dart @@ -0,0 +1,32 @@ +part of 'calculator_bloc.dart'; + +class CalculatorState extends Equatable { + const CalculatorState({ + required this.isLoading, + this.weight, + this.size, + this.selectedWarehouse, + }); + + final bool isLoading; + final double? weight; + final double? size; + final String? selectedWarehouse; + + CalculatorState copyWith({ + bool? isLoading, + double? weight, + double? size, + String? selectedWarehouse, + }) { + return CalculatorState( + isLoading: isLoading ?? this.isLoading, + weight: weight ?? this.weight, + size: size ?? this.size, + selectedWarehouse: selectedWarehouse ?? this.selectedWarehouse, + ); + } + + @override + List get props => [isLoading, weight, size, selectedWarehouse]; +} diff --git a/lib/features/calculator/presentation/mixin/calculator_mixin.dart b/lib/features/calculator/presentation/mixin/calculator_mixin.dart new file mode 100644 index 0000000..c72d201 --- /dev/null +++ b/lib/features/calculator/presentation/mixin/calculator_mixin.dart @@ -0,0 +1,22 @@ +import 'package:flutter/cupertino.dart'; + +mixin CalculatorMixin{ + late TextEditingController weightController; + late TextEditingController sizeController; + late TextEditingController averageWeightController; + late TextEditingController productNameController; + + void initControllers(){ + weightController=TextEditingController(); + sizeController=TextEditingController(); + averageWeightController=TextEditingController(); + productNameController=TextEditingController(); + } + + void disposeController(){ + weightController.dispose(); + sizeController.dispose(); + averageWeightController.dispose(); + productNameController.dispose(); + } +} \ No newline at end of file diff --git a/lib/features/calculator/presentation/pages/calculator_info/calculator_info_page.dart b/lib/features/calculator/presentation/pages/calculator_info/calculator_info_page.dart new file mode 100644 index 0000000..a631682 --- /dev/null +++ b/lib/features/calculator/presentation/pages/calculator_info/calculator_info_page.dart @@ -0,0 +1,155 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/functions/base_finctions.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/core/widgets/loading/progress_hud.dart'; +import 'package:cargocalculaterapp/features/calculator/presentation/pages/calculator_info/widget/calculator_info_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../../constants/constants.dart'; +import '../../../../../generated/l10n.dart'; +import '../../arguments/calculator_info_argument.dart'; +import '../../bloc/calculator_info/calculator_info_bloc.dart'; + +class CalculatorInfoPage extends StatelessWidget { + const CalculatorInfoPage({super.key, required this.argument}); + + final CalculatorInfoArgument? argument; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Scaffold( + backgroundColor: context.color.grayBackground, + appBar: AppBar(title: Text(AppLocalization.current.calculator)), + body: ModalProgressHUD( + inAsyncCall: state.isLoading, + child: ListView( + padding: AppUtils.kPaddingAll16, + children: [ + Container( + margin: const EdgeInsets.only(bottom: 32), + padding: AppUtils.kPaddingAll16, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.statusBackground, + border: Border.all(color: context.color.lightBorder), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CalculatorInfoWidget( + title: AppLocalization.current.product_name, + name: argument?.productName ?? "-", + ), + CalculatorInfoWidget( + title: AppLocalization.current.warehouse, + name: Functions.getTranslatedItem( + AppConst.warehouseOptions[argument?.weraHouse], + context, + ), + ), + CalculatorInfoWidget( + title: AppLocalization.current.weight, + name: "${argument?.weight} kg", + ), + CalculatorInfoWidget( + title: AppLocalization.current.size, + name: "${argument?.size} m³", + ), + CalculatorInfoWidget( + title: AppLocalization.current.average_weight, + name: "${argument?.averageWeight}", + ), + CalculatorInfoWidget( + title: AppLocalization.current.delivery_price, + name: "${argument?.deliveryPrice}", + ), + ], + ), + ), + Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + if (argument != null) { + context.read().add( + CreateLeadEvent( + isCall: true, + argument: argument!, + ), + ); + } + }, + borderRadius: AppUtils.kBorderRadius24, + child: Ink( + decoration: BoxDecoration( + color: context.color.scaffoldBackgroundColor, + borderRadius: AppUtils.kBorderRadius24, + border: Border.all( + color: context.color.primaryColor, + ), + ), + height: 56, + padding: AppUtils.kPaddingHor16, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + "assets/svg/ic_phone.svg", + colorFilter: ColorFilter.mode( + context.color.textColor, + BlendMode.srcIn, + ), + ), + AppUtils.kBoxWidth4, + Text( + AppLocalization.current.phone_call, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: context.color.textColor, + ), + ), + ], + ), + ), + ), + ), + AppUtils.kBoxWidth16, + Expanded( + child: ElevatedButton( + onPressed: () { + if (argument != null) { + context.read().add( + CreateLeadEvent( + isCall: false, + argument: argument!, + ), + ); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset("assets/svg/ic_list_1.svg"), + AppUtils.kBoxWidth4, + Text(AppLocalization.current.send), + ], + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/calculator/presentation/pages/calculator_info/dialog/order_dialog.dart b/lib/features/calculator/presentation/pages/calculator_info/dialog/order_dialog.dart new file mode 100644 index 0000000..6434782 --- /dev/null +++ b/lib/features/calculator/presentation/pages/calculator_info/dialog/order_dialog.dart @@ -0,0 +1,45 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class OrderDialog extends StatelessWidget { + const OrderDialog({super.key, required this.isCall}); + + final bool isCall; + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: context.color.scaffoldBackgroundColor, + insetPadding: AppUtils.kPaddingAll16, + child: Padding( + padding: AppUtils.kPaddingVer24Hor16, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset("assets/svg/ic_done.svg"), + AppUtils.kBoxHeight20, + Text( + isCall + ? AppLocalization.current.done_phone + : AppLocalization.current.done_order, + style: context.text.authDesc.copyWith( + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + AppUtils.kBoxHeight20, + ElevatedButton( + onPressed: () { + Navigator.pop(context, true); + }, + child: Text(AppLocalization.current.done_ready), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/calculator/presentation/pages/calculator_info/widget/calculator_info_widget.dart b/lib/features/calculator/presentation/pages/calculator_info/widget/calculator_info_widget.dart new file mode 100644 index 0000000..e45eef2 --- /dev/null +++ b/lib/features/calculator/presentation/pages/calculator_info/widget/calculator_info_widget.dart @@ -0,0 +1,32 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../../core/utils/app_utils.dart'; + +class CalculatorInfoWidget extends StatelessWidget { + const CalculatorInfoWidget({super.key, required this.title, required this.name}); + + final String title; + final String name; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: Text(title, style: context.text.orderListTitle)), + AppUtils.kBoxWidth8, + Expanded( + child: Text( + name, + style: context.text.profileCategory, + textAlign: TextAlign.end, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/calculator/presentation/pages/calculator_page.dart b/lib/features/calculator/presentation/pages/calculator_page.dart new file mode 100644 index 0000000..7c4f920 --- /dev/null +++ b/lib/features/calculator/presentation/pages/calculator_page.dart @@ -0,0 +1,146 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/core/widgets/loading/progress_hud.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../core/widgets/drop_down/custom_drop_down.dart'; +import '../../../../core/widgets/text_filds/custom_text_field_name.dart'; +import '../bloc/calculator_bloc.dart'; +import '../mixin/calculator_mixin.dart'; + +class CalculatorPage extends StatefulWidget { + const CalculatorPage({super.key}); + + @override + State createState() => _CalculatorPageState(); +} + +class _CalculatorPageState extends State with CalculatorMixin { + @override + void initState() { + initControllers(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if ((state.weight ?? 0) > 0 && (state.size ?? 0) > 0) { + averageWeightController.text = + "${(state.weight ?? 0) / (state.size ?? 1)}"; + } + }, + builder: (context, state) { + return Scaffold( + backgroundColor: context.color.grayBackground, + appBar: AppBar(title: Text(AppLocalization.current.calculator)), + body: ModalProgressHUD( + inAsyncCall: state.isLoading, + child: ListView( + padding: AppUtils.kPaddingAll16, + children: [ + CustomTextFieldName( + hint: AppLocalization.current.weight, + name: AppLocalization.current.weight_in_kg, + isRequired: true, + format: [ + FilteringTextInputFormatter.allow( + RegExp(r'^\d*[.,]?\d{0,}$'), // accepts both "." and "," + ), + ], + controller: weightController, + inputType: const TextInputType.numberWithOptions( + decimal: true, + ), + onchange: (value) { + context.read().add( + WeightSizeEnterEvent( + weight: value, + size: sizeController.text, + ), + ); + }, + ), + AppUtils.kBoxHeight16, + CustomTextFieldName( + hint: AppLocalization.current.size, + name: AppLocalization.current.size_in_m3, + isRequired: true, + controller: sizeController, + format: [ + FilteringTextInputFormatter.allow( + RegExp(r'^\d*[.,]?\d{0,}$'), // accepts both "." and "," + ), + ], + inputType: const TextInputType.numberWithOptions( + decimal: true, + ), + onchange: (value) { + context.read().add( + WeightSizeEnterEvent( + weight: weightController.text, + size: value, + ), + ); + }, + ), + AppUtils.kBoxHeight16, + CustomTextFieldName( + hint: AppLocalization.current.average_weight, + name: AppLocalization.current.average_weight_in, + controller: averageWeightController, + readOnly: true, + ), + AppUtils.kBoxHeight16, + CustomTextFieldName( + hint: AppLocalization.current.product_name, + isRequired: true, + name: AppLocalization.current.product_name, + controller: productNameController, + inputType: TextInputType.name, + ), + AppUtils.kBoxHeight16, + CustomDropDown( + isRequired: false, + value: state.selectedWarehouse, + + onChange: (value) { + context.read().add( + WareHouseSelectEvent(wareHouse: value ?? ""), + ); + }, + name: AppLocalization.current.select_warehouse, + ), + AppUtils.kBoxHeight16, + ElevatedButton( + onPressed: ((state.weight ?? 0) > 0 && (state.size ?? 0) > 0) + ? () { + context.read().add( + CalculateEvent( + productName: productNameController.text, + ), + ); + } + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset("assets/svg/ic_calculator_1.svg"), + AppUtils.kBoxWith8, + Text(AppLocalization.current.calculate), + ], + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/calculator/presentation/pages/widgets/order_data_widget.dart b/lib/features/calculator/presentation/pages/widgets/order_data_widget.dart new file mode 100644 index 0000000..e090cc7 --- /dev/null +++ b/lib/features/calculator/presentation/pages/widgets/order_data_widget.dart @@ -0,0 +1,36 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../core/utils/app_utils.dart'; + +class OrderDataWidget extends StatelessWidget { + const OrderDataWidget({super.key, required this.title, required this.name}); + + final String title; + final String name; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: Text(title, style: context.text.profileCategory)), + AppUtils.kBoxWidth8, + Expanded( + child: Text( + name, + style: context.text.profileCategory, + textAlign: TextAlign.end, + ), + ), + ], + ), + + const Padding(padding: AppUtils.kPaddingVer8, child: AppUtils.kDivider), + ], + ); + } +} diff --git a/lib/features/home/data/data_source/remote/home_remote_data_source.dart b/lib/features/home/data/data_source/remote/home_remote_data_source.dart new file mode 100644 index 0000000..4517a56 --- /dev/null +++ b/lib/features/home/data/data_source/remote/home_remote_data_source.dart @@ -0,0 +1,25 @@ +import 'package:cargocalculaterapp/features/home/data/models/comment_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/comment_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart'; + +import '../../models/banner_response.dart'; + +abstract class HomeRemoteDataSource { + Future ordersList(Map request); + + Future orderSingle(String orderId); + + Future comment(CommentRequest request); + + Future notification(Map request); + + Future notificationRead( + ReadNotificationRequest request, + ); + + Future> bannersResponse(); +} diff --git a/lib/features/home/data/data_source/remote/home_remote_data_source_impl.dart b/lib/features/home/data/data_source/remote/home_remote_data_source_impl.dart new file mode 100644 index 0000000..a6506c6 --- /dev/null +++ b/lib/features/home/data/data_source/remote/home_remote_data_source_impl.dart @@ -0,0 +1,157 @@ +import 'package:cargocalculaterapp/features/home/data/models/banner_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/comment_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/comment_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart'; +import 'package:dio/dio.dart'; +import '../../../../../constants/constants.dart'; +import '../../../../../core/error/exceptions.dart'; +import '../../../../../core/local_source/local_source.dart'; +import '../../../../../injector_container.dart'; +import 'home_remote_data_source.dart'; + +class HomeRemoteDataSourceImpl extends HomeRemoteDataSource { + final Dio dio; + + HomeRemoteDataSourceImpl(this.dio); + + @override + Future ordersList(Map request) async { + try { + final Response response = await dio.get( + Constants.baseUrl + Urls.orderList, + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + queryParameters: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return OrdersListResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future orderSingle(String orderId) async { + try { + final Response response = await dio.get( + "${Constants.baseUrl}${Urls.orderList}/$orderId", + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return OrderSingleResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future comment(CommentRequest request) async { + try { + final Response response = await dio.post( + "${Constants.baseUrl}${Urls.comment}", + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return CommentResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future notification( + Map request, + ) async { + try { + final Response response = await dio.get( + "${Constants.baseUrl}${Urls.notification}", + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + queryParameters: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return NotificationResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future notificationRead( + ReadNotificationRequest request, + ) async { + try { + final Response response = await dio.put( + "${Constants.baseUrl}${Urls.notificationRead}", + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return ReadNotificationResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future> bannersResponse() async { + try { + final Response response = await dio.get( + "${Constants.baseUrl}${Urls.banners}", + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + final List images = (response.data as List) + .map((json) => BannerResponse.fromJson(json)) + .toList(); + return images; + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } +} diff --git a/lib/features/home/data/models/banner_response.dart b/lib/features/home/data/models/banner_response.dart new file mode 100644 index 0000000..c580dcf --- /dev/null +++ b/lib/features/home/data/models/banner_response.dart @@ -0,0 +1,27 @@ +class BannerResponse { + BannerResponse({this.id, this.image, this.createdAt, this.updatedAt, this.v}); + + BannerResponse.fromJson(dynamic json) { + id = json['_id']; + image = json['image']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + v = json['__v']; + } + + String? id; + String? image; + String? createdAt; + String? updatedAt; + int? v; + + Map toJson() { + final map = {}; + map['_id'] = id; + map['image'] = image; + map['createdAt'] = createdAt; + map['updatedAt'] = updatedAt; + map['__v'] = v; + return map; + } +} diff --git a/lib/features/home/data/models/comment_request.dart b/lib/features/home/data/models/comment_request.dart new file mode 100644 index 0000000..a981ec5 --- /dev/null +++ b/lib/features/home/data/models/comment_request.dart @@ -0,0 +1,24 @@ +class CommentRequest { + CommentRequest({ + this.message, + this.rate, + this.orderNumber,}); + + CommentRequest.fromJson(dynamic json) { + message = json['message']; + rate = json['rate']; + orderNumber = json['order_number']; + } + String? message; + int? rate; + String? orderNumber; + + Map toJson() { + final map = {}; + map['message'] = message; + map['rate'] = rate; + map['order_number'] = orderNumber; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/home/data/models/comment_response.dart b/lib/features/home/data/models/comment_response.dart new file mode 100644 index 0000000..ac7d60a --- /dev/null +++ b/lib/features/home/data/models/comment_response.dart @@ -0,0 +1,44 @@ +class CommentResponse { + CommentResponse({ + this.message, + this.rate, + this.userUcode, + this.orderNumber, + this.id, + this.createdAt, + this.updatedAt, + this.v,}); + + CommentResponse.fromJson(dynamic json) { + message = json['message']; + rate = json['rate']; + userUcode = json['user_ucode']; + orderNumber = json['order_number']; + id = json['_id']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + v = json['__v']; + } + String? message; + int? rate; + String? userUcode; + String? orderNumber; + String? id; + String? createdAt; + String? updatedAt; + int? v; + + Map toJson() { + final map = {}; + map['message'] = message; + map['rate'] = rate; + map['user_ucode'] = userUcode; + map['order_number'] = orderNumber; + map['_id'] = id; + map['createdAt'] = createdAt; + map['updatedAt'] = updatedAt; + map['__v'] = v; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/home/data/models/notification_response.dart b/lib/features/home/data/models/notification_response.dart new file mode 100644 index 0000000..62b5770 --- /dev/null +++ b/lib/features/home/data/models/notification_response.dart @@ -0,0 +1,79 @@ +import 'package:cargocalculaterapp/core/models/name.dart'; + +class NotificationResponse { + NotificationResponse({this.notifications, this.totalCount}); + + NotificationResponse.fromJson(dynamic json) { + if (json['notifications'] != null) { + notifications = []; + json['notifications'].forEach((v) { + notifications?.add(Notifications.fromJson(v)); + }); + } + totalCount = json['totalCount']; + } + + List? notifications; + int? totalCount; + + Map toJson() { + final map = {}; + if (notifications != null) { + map['notifications'] = notifications?.map((v) => v.toJson()).toList(); + } + map['totalCount'] = totalCount; + return map; + } +} + +class Notifications { + Notifications({ + this.id, + this.userUcode, + this.orderNumber, + this.text, + this.status, + this.isRead, + this.createdAt, + this.updatedAt, + this.v, + }); + + Notifications.fromJson(dynamic json) { + id = json['_id']; + userUcode = json['user_ucode']; + orderNumber = json['order_number']; + text = json['text'] != null ? Name.fromJson(json['text']) : null; + status = json['status']; + isRead = json['is_read']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + v = json['__v']; + } + + String? id; + String? userUcode; + String? orderNumber; + Name? text; + String? status; + bool? isRead; + String? createdAt; + String? updatedAt; + int? v; + + Map toJson() { + final map = {}; + map['_id'] = id; + map['user_ucode'] = userUcode; + map['order_number'] = orderNumber; + if (text != null) { + map['text'] = text?.toJson(); + } + map['status'] = status; + map['is_read'] = isRead; + map['createdAt'] = createdAt; + map['updatedAt'] = updatedAt; + map['__v'] = v; + return map; + } +} diff --git a/lib/features/home/data/models/order_single_response.dart b/lib/features/home/data/models/order_single_response.dart new file mode 100644 index 0000000..ccf1e2f --- /dev/null +++ b/lib/features/home/data/models/order_single_response.dart @@ -0,0 +1,145 @@ +import 'package:cargocalculaterapp/core/models/name.dart'; + +class OrderSingleResponse { + OrderSingleResponse({ + this.id, + this.userUcode, + this.phoneNumber, + this.gmail, + this.quantity, + this.nameUz, + this.nameRu, + this.nameZh, + this.weight, + this.m3, + this.averageWeightKg, + this.wharehouseUz, + this.wharehouseRu, + this.wharehouseZh, + this.deliveryPrice, + this.shipmentId, + this.leadId, + this.status, + this.partiallyArrived, + this.fullArrived, + this.orderNumber, + this.createdAt, + this.updatedAt, + this.v, + this.arrivalDate, + this.images, + }); + + OrderSingleResponse.fromJson(dynamic json) { + userUcode = json['user_ucode']; + phoneNumber = json['phone_number']; + gmail = json['gmail']; + quantity = json['quantity']; + nameUz = json['nameUz']; + nameRu = json['nameRu']; + nameZh = json['nameZh']; + weight = json['weight']; + m3 = json['m3']; + averageWeightKg = json['average_weight_kg']; + wharehouseUz = json['wharehouseUz']; + wharehouseRu = json['wharehouseRu']; + wharehouseZh = json['wharehouseZh']; + deliveryPrice = json['delivery_price']; + shipmentId = json['shipment_id']; + leadId = json['lead_id']; + status = json['status'] != null ? Status.fromJson(json['status']) : null; + partiallyArrived = json['partially_arrived']; + fullArrived = json['full_arrived']; + orderNumber = json['order_number']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + v = json['__v']; + id = json['id']; + arrivalDate = json['arrival_date']; + images = json['images'] != null ? json['images'].cast() : []; + } + + String? userUcode; + String? phoneNumber; + String? gmail; + int? quantity; + String? nameUz; + String? nameRu; + String? nameZh; + String? weight; + String? m3; + String? averageWeightKg; + String? wharehouseUz; + String? wharehouseRu; + String? wharehouseZh; + String? deliveryPrice; + String? shipmentId; + String? leadId; + Status? status; + bool? partiallyArrived; + bool? fullArrived; + String? orderNumber; + String? createdAt; + String? updatedAt; + int? v; + String? id; + String? arrivalDate; + List? images; + + Map toJson() { + final map = {}; + map['user_ucode'] = userUcode; + map['phone_number'] = phoneNumber; + map['gmail'] = gmail; + map['quantity'] = quantity; + map['nameUz'] = nameUz; + map['nameRu'] = nameRu; + map['nameZh'] = nameZh; + map['weight'] = weight; + map['m3'] = m3; + map['average_weight_kg'] = averageWeightKg; + map['wharehouseUz'] = wharehouseUz; + map['wharehouseRu'] = wharehouseRu; + map['wharehouseZh'] = wharehouseZh; + map['delivery_price'] = deliveryPrice; + map['shipment_id'] = shipmentId; + map['lead_id'] = leadId; + if (status != null) { + map['status'] = status?.toJson(); + } + map['partially_arrived'] = partiallyArrived; + map['full_arrived'] = fullArrived; + map['order_number'] = orderNumber; + map['createdAt'] = createdAt; + map['updatedAt'] = updatedAt; + map['__v'] = v; + map['id'] = id; + map['arrival_date'] = arrivalDate; + map['images'] = images; + return map; + } +} + +class Status { + Status({this.code, this.translations}); + + Status.fromJson(dynamic json) { + code = json['code']; + translations = + json['translations'] != null + ? Name.fromJson(json['translations']) + : null; + } + + String? code; + Name? translations; + + Map toJson() { + final map = {}; + map['code'] = code; + if (translations != null) { + map['translations'] = translations?.toJson(); + } + return map; + } +} diff --git a/lib/features/home/data/models/orders_list_response.dart b/lib/features/home/data/models/orders_list_response.dart new file mode 100644 index 0000000..d6cbf3c --- /dev/null +++ b/lib/features/home/data/models/orders_list_response.dart @@ -0,0 +1,160 @@ +import '../../../../core/models/name.dart'; + +class OrdersListResponse { + OrdersListResponse({this.totalCount, this.data}); + + OrdersListResponse.fromJson(dynamic json) { + totalCount = json['totalCount']; + if (json['data'] != null) { + data = []; + json['data'].forEach((v) { + data?.add(Data.fromJson(v)); + }); + } + } + + int? totalCount; + List? data; + + Map toJson() { + final map = {}; + map['totalCount'] = totalCount; + if (data != null) { + map['data'] = data?.map((v) => v.toJson()).toList(); + } + return map; + } +} + +class Data { + Data({ + this.id, + this.userUcode, + this.phoneNumber, + this.gmail, + this.quantity, + this.nameUz, + this.nameRu, + this.nameZh, + this.size, + this.wharehouseUz, + this.wharehouseRu, + this.wharehouseZh, + this.deliveryPrice, + this.shipmentId, + this.leadId, + this.status, + this.partiallyArrived, + this.fullArrived, + this.orderNumber, + this.createdAt, + this.updatedAt, + this.v, + this.arrivalDate, + }); + + Data.fromJson(dynamic json) { + userUcode = json['user_ucode']; + phoneNumber = json['phone_number']; + gmail = json['gmail']; + quantity = json['quantity']; + nameUz = json['nameUz']; + nameRu = json['nameRu']; + nameZh = json['nameZh']; + size = json['size']; + wharehouseUz = json['wharehouseUz']; + wharehouseRu = json['wharehouseRu']; + wharehouseZh = json['wharehouseZh']; + deliveryPrice = json['delivery_price']; + shipmentId = json['shipment_id']; + leadId = json['lead_id']; + status = json['status'] != null ? Status.fromJson(json['status']) : null; + partiallyArrived = json['partially_arrived']; + fullArrived = json['full_arrived']; + orderNumber = json['order_number']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + v = json['__v']; + id = json['id']; + arrivalDate = json['arrival_date']; + } + + String? userUcode; + String? phoneNumber; + String? gmail; + int? quantity; + String? nameUz; + String? nameRu; + String? nameZh; + String? size; + String? wharehouseUz; + String? wharehouseRu; + String? wharehouseZh; + String? deliveryPrice; + String? shipmentId; + String? leadId; + Status? status; + bool? partiallyArrived; + bool? fullArrived; + String? orderNumber; + String? createdAt; + String? updatedAt; + int? v; + String? id; + String? arrivalDate; + + Map toJson() { + final map = {}; + map['_id'] = id; + map['user_ucode'] = userUcode; + map['phone_number'] = phoneNumber; + map['gmail'] = gmail; + map['quantity'] = quantity; + map['nameUz'] = nameUz; + map['nameRu'] = nameRu; + map['nameZh'] = nameZh; + map['size'] = size; + map['wharehouseUz'] = wharehouseUz; + map['wharehouseRu'] = wharehouseRu; + map['wharehouseZh'] = wharehouseZh; + map['delivery_price'] = deliveryPrice; + map['shipment_id'] = shipmentId; + map['lead_id'] = leadId; + if (status != null) { + map['status'] = status?.toJson(); + } + map['partially_arrived'] = partiallyArrived; + map['full_arrived'] = fullArrived; + map['order_number'] = orderNumber; + map['createdAt'] = createdAt; + map['updatedAt'] = updatedAt; + map['__v'] = v; + map['id'] = id; + map['arrival_date'] = arrivalDate; + return map; + } +} + +class Status { + Status({this.code, this.translations}); + + Status.fromJson(dynamic json) { + code = json['code']; + translations = + json['translations'] != null + ? Name.fromJson(json['translations']) + : null; + } + + String? code; + Name? translations; + + Map toJson() { + final map = {}; + map['code'] = code; + if (translations != null) { + map['translations'] = translations?.toJson(); + } + return map; + } +} diff --git a/lib/features/home/data/models/read_notification_request.dart b/lib/features/home/data/models/read_notification_request.dart new file mode 100644 index 0000000..9795397 --- /dev/null +++ b/lib/features/home/data/models/read_notification_request.dart @@ -0,0 +1,16 @@ +class ReadNotificationRequest { + ReadNotificationRequest({ + this.notificationIds,}); + + ReadNotificationRequest.fromJson(dynamic json) { + notificationIds = json['notification_ids'] != null ? json['notification_ids'].cast() : []; + } + List? notificationIds; + + Map toJson() { + final map = {}; + map['notification_ids'] = notificationIds; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/home/data/models/read_notification_response.dart b/lib/features/home/data/models/read_notification_response.dart new file mode 100644 index 0000000..a2c66ec --- /dev/null +++ b/lib/features/home/data/models/read_notification_response.dart @@ -0,0 +1,16 @@ +class ReadNotificationResponse { + ReadNotificationResponse({ + this.message,}); + + ReadNotificationResponse.fromJson(dynamic json) { + message = json['message']; + } + String? message; + + Map toJson() { + final map = {}; + map['message'] = message; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/home/data/repository/home_repository_impl.dart b/lib/features/home/data/repository/home_repository_impl.dart new file mode 100644 index 0000000..a76222c --- /dev/null +++ b/lib/features/home/data/repository/home_repository_impl.dart @@ -0,0 +1,88 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/features/home/data/models/banner_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/comment_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/comment_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart'; +import 'package:dartz/dartz.dart'; +import '../../domain/repository/home_repository.dart'; +import '../data_source/remote/home_remote_data_source.dart'; + +class HomeRepositoryImpl extends HomeRepository { + final HomeRemoteDataSource remoteDataSource; + + HomeRepositoryImpl(this.remoteDataSource); + + @override + Future> ordersList( + Map request, + ) async { + try { + final response = await remoteDataSource.ordersList(request); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> orderSingle( + String orderId, + ) async { + try { + final response = await remoteDataSource.orderSingle(orderId); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> comment( + CommentRequest request, + ) async { + try { + final response = await remoteDataSource.comment(request); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> notification( + Map request, + ) async { + try { + final response = await remoteDataSource.notification(request); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> notificationRead( + ReadNotificationRequest request, + ) async { + try { + final response = await remoteDataSource.notificationRead(request); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future>> bannersGet() async { + try { + final response = await remoteDataSource.bannersResponse(); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } +} diff --git a/lib/features/home/domain/repository/home_repository.dart b/lib/features/home/domain/repository/home_repository.dart new file mode 100644 index 0000000..13d53da --- /dev/null +++ b/lib/features/home/domain/repository/home_repository.dart @@ -0,0 +1,30 @@ +import 'package:cargocalculaterapp/features/home/data/models/comment_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/comment_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart'; +import 'package:dartz/dartz.dart'; +import '../../../../core/error/failure.dart'; +import '../../data/models/banner_response.dart'; + +abstract class HomeRepository { + Future> ordersList( + Map request, + ); + + Future> orderSingle(String orderId); + + Future> comment(CommentRequest request); + + Future> notification( + Map request, + ); + + Future> notificationRead( + ReadNotificationRequest request, + ); + + Future>> bannersGet(); +} diff --git a/lib/features/home/domain/usecase/banners_usecase.dart b/lib/features/home/domain/usecase/banners_usecase.dart new file mode 100644 index 0000000..3729152 --- /dev/null +++ b/lib/features/home/domain/usecase/banners_usecase.dart @@ -0,0 +1,16 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:dartz/dartz.dart'; +import '../../data/models/banner_response.dart'; +import '../repository/home_repository.dart'; + +class BannersUseCase extends UseCase, NoParams> { + final HomeRepository repository; + + BannersUseCase(this.repository); + + @override + Future>> call(NoParams params) async { + return await repository.bannersGet(); + } +} diff --git a/lib/features/home/domain/usecase/comment_usecase.dart b/lib/features/home/domain/usecase/comment_usecase.dart new file mode 100644 index 0000000..f8004a9 --- /dev/null +++ b/lib/features/home/domain/usecase/comment_usecase.dart @@ -0,0 +1,16 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:dartz/dartz.dart'; +import '../../data/models/comment_request.dart'; +import '../../data/models/comment_response.dart'; +import '../repository/home_repository.dart'; + +class CommentUseCase extends UseCase{ + final HomeRepository repository; + + CommentUseCase(this.repository); + @override + Future> call(CommentRequest params) async { + return await repository.comment(params); + } +} \ No newline at end of file diff --git a/lib/features/home/domain/usecase/notification_read_usecase.dart b/lib/features/home/domain/usecase/notification_read_usecase.dart new file mode 100644 index 0000000..4694a5d --- /dev/null +++ b/lib/features/home/domain/usecase/notification_read_usecase.dart @@ -0,0 +1,19 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../repository/home_repository.dart'; + +class NotificationReadUseCase extends UseCase{ + final HomeRepository repository; + + NotificationReadUseCase(this.repository); + + @override + Future> call(ReadNotificationRequest params) async { + return await repository.notificationRead(params); + } + +} \ No newline at end of file diff --git a/lib/features/home/domain/usecase/notification_usecase.dart b/lib/features/home/domain/usecase/notification_usecase.dart new file mode 100644 index 0000000..882e531 --- /dev/null +++ b/lib/features/home/domain/usecase/notification_usecase.dart @@ -0,0 +1,20 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../../../../core/usecase/usecase.dart'; +import '../repository/home_repository.dart'; + +class NotificationUseCase + extends UseCase> { + final HomeRepository repository; + + NotificationUseCase(this.repository); + + @override + Future> call( + Map params, + ) async { + return await repository.notification(params); + } +} diff --git a/lib/features/home/domain/usecase/order_single_usecase.dart b/lib/features/home/domain/usecase/order_single_usecase.dart new file mode 100644 index 0000000..259a924 --- /dev/null +++ b/lib/features/home/domain/usecase/order_single_usecase.dart @@ -0,0 +1,17 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../repository/home_repository.dart'; + +class OrderSingleUseCase extends UseCase { + final HomeRepository repository; + + OrderSingleUseCase(this.repository); + + @override + Future> call(String params) async { + return await repository.orderSingle(params); + } +} diff --git a/lib/features/home/domain/usecase/orders_list_usecase.dart b/lib/features/home/domain/usecase/orders_list_usecase.dart new file mode 100644 index 0000000..f54401e --- /dev/null +++ b/lib/features/home/domain/usecase/orders_list_usecase.dart @@ -0,0 +1,19 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart'; +import 'package:cargocalculaterapp/features/home/domain/repository/home_repository.dart'; +import 'package:dartz/dartz.dart'; + +class OrdersListUseCase + extends UseCase> { + final HomeRepository repository; + + OrdersListUseCase(this.repository); + + @override + Future> call( + Map params, + ) async { + return await repository.ordersList(params); + } +} diff --git a/lib/features/home/presentation/bloc/comment/comment_bloc.dart b/lib/features/home/presentation/bloc/comment/comment_bloc.dart new file mode 100644 index 0000000..0d1b9cf --- /dev/null +++ b/lib/features/home/presentation/bloc/comment/comment_bloc.dart @@ -0,0 +1,53 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../router/app_routes.dart'; +import '../../../data/models/comment_request.dart'; +import '../../../domain/usecase/comment_usecase.dart'; + +part 'comment_event.dart'; + +part 'comment_state.dart'; + +class CommentBloc extends Bloc { + CommentBloc(this.commentUseCase) + : super(const CommentState(rating: 0, isLoading: false)) { + on(_ratingChange); + on(_commentEnter); + on(_commentSubmit); + } + + final CommentUseCase commentUseCase; + + void _ratingChange(RatingChangedEvent event, Emitter emit) { + emit(state.copyWith(rating: event.rating)); + } + + void _commentEnter(CommentEnterEvent event, Emitter emit) { + emit(state.copyWith(comment: event.comment)); + } + + Future _commentSubmit( + SubmitEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true)); + final response = await commentUseCase( + CommentRequest( + orderNumber: event.orderId, + rate: state.rating.toInt(), + message: state.comment, + ), + ); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + }, + (r) { + emit(state.copyWith(isLoading: false)); + Navigator.pop(rootNavigatorKey.currentContext!); + }, + ); + } +} diff --git a/lib/features/home/presentation/bloc/comment/comment_event.dart b/lib/features/home/presentation/bloc/comment/comment_event.dart new file mode 100644 index 0000000..2b977e0 --- /dev/null +++ b/lib/features/home/presentation/bloc/comment/comment_event.dart @@ -0,0 +1,32 @@ +part of 'comment_bloc.dart'; + +sealed class CommentEvent extends Equatable { + const CommentEvent(); +} + +final class RatingChangedEvent extends CommentEvent { + final double rating; + + const RatingChangedEvent({required this.rating}); + + @override + List get props => [rating]; +} + +final class CommentEnterEvent extends CommentEvent { + final String comment; + + const CommentEnterEvent({required this.comment}); + + @override + List get props => [comment]; +} + +final class SubmitEvent extends CommentEvent { + const SubmitEvent({required this.orderId}); + + final String orderId; + + @override + List get props => [orderId]; +} diff --git a/lib/features/home/presentation/bloc/comment/comment_state.dart b/lib/features/home/presentation/bloc/comment/comment_state.dart new file mode 100644 index 0000000..d45f91e --- /dev/null +++ b/lib/features/home/presentation/bloc/comment/comment_state.dart @@ -0,0 +1,24 @@ +part of 'comment_bloc.dart'; + +class CommentState extends Equatable { + const CommentState({ + required this.rating, + required this.isLoading, + this.comment, + }); + + final double rating; + final bool isLoading; + final String? comment; + + CommentState copyWith({double? rating, bool? isLoading, String? comment}) { + return CommentState( + rating: rating ?? this.rating, + isLoading: isLoading ?? this.isLoading, + comment: comment ?? this.comment, + ); + } + + @override + List get props => [rating, isLoading, comment]; +} diff --git a/lib/features/home/presentation/bloc/home_bloc.dart b/lib/features/home/presentation/bloc/home_bloc.dart new file mode 100644 index 0000000..3b6987e --- /dev/null +++ b/lib/features/home/presentation/bloc/home_bloc.dart @@ -0,0 +1,127 @@ +import 'package:cargocalculaterapp/constants/constants.dart'; +import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../core/usecase/usecase.dart'; +import '../../data/models/banner_response.dart'; +import '../../domain/usecase/banners_usecase.dart'; +import '../../domain/usecase/orders_list_usecase.dart'; + +part 'home_event.dart'; + +part 'home_state.dart'; + +class HomeBloc extends Bloc { + HomeBloc(this.ordersListUseCase, this.bannersUseCase) + : super( + const HomeState( + paginationLoading: false, + isLoading: false, + page: 1, + selectedStatus: AppConst.all, + ), + ) { + on(_getAllOrders); + on(_getOrders); + on(_changeOrderStatus); + on(_paginationLoading); + on(_getBanner); + on(_refresh); + } + + final OrdersListUseCase ordersListUseCase; + final BannersUseCase bannersUseCase; + + final Map productsRequest = {}; + + void _getAllOrders(GetAllOrdersListEvent event, Emitter emit) { + productsRequest.remove(AppKeys.status); + productsRequest[AppKeys.page] = 1; + productsRequest[AppKeys.limit] = AppConst.limit; + add(const GetOrdersEvent()); + } + + Future _getOrders(GetOrdersEvent event, Emitter emit) async { + emit(state.copyWith(isLoading: event.isLoading)); + final response = await ordersListUseCase(productsRequest); + response.fold( + (l) { + emit(state.copyWith(isLoading: false, paginationLoading: false)); + }, + (r) { + emit( + state.copyWith( + isLoading: false, + paginationLoading: false, + ordersList: r, + page: productsRequest[AppKeys.page], + pageCount: ((r.totalCount ?? 1) / 20).ceil(), + ), + ); + }, + ); + } + + void _changeOrderStatus(OrderStatusEvent event, Emitter emit) { + if (event.status == state.selectedStatus) { + return; + } + emit(state.copyWith(selectedStatus: event.status)); + productsRequest[AppKeys.page] = 1; + if (event.status == AppConst.all) { + productsRequest.remove(AppConst.status); + } else { + productsRequest[AppConst.status] = event.status; + } + add(const GetOrdersEvent()); + } + + Future _paginationLoading( + PaginationEvent event, + Emitter emit, + ) async { + productsRequest[AppKeys.page] = productsRequest[AppKeys.page] + 1; + emit( + state.copyWith( + paginationLoading: true, + page: productsRequest[AppKeys.page], + ), + ); + final response = await ordersListUseCase(productsRequest); + response.fold( + (l) { + emit(state.copyWith(isLoading: false, paginationLoading: false)); + }, + (r) { + emit( + state.copyWith( + isLoading: false, + paginationLoading: false, + ordersList: OrdersListResponse( + totalCount: r.totalCount, + data: state.ordersList?.data?..addAll(r.data ?? []), + ), + page: productsRequest[AppKeys.page], + pageCount: ((r.totalCount ?? 1) / 20).ceil(), + ), + ); + }, + ); + } + + Future _getBanner( + GetBannersEvent event, + Emitter emit, + ) async { + final response = await bannersUseCase(const NoParams()); + response.fold((l) {}, (r) { + emit(state.copyWith(banners: r)); + }); + } + + void _refresh(RefreshEvent event, Emitter emit) { + productsRequest[AppKeys.page] = 1; + add(const GetOrdersEvent(isLoading: false)); + add(const GetBannersEvent()); + } +} diff --git a/lib/features/home/presentation/bloc/home_event.dart b/lib/features/home/presentation/bloc/home_event.dart new file mode 100644 index 0000000..459b792 --- /dev/null +++ b/lib/features/home/presentation/bloc/home_event.dart @@ -0,0 +1,51 @@ +part of 'home_bloc.dart'; + +sealed class HomeEvent extends Equatable { + const HomeEvent(); +} + +final class GetAllOrdersListEvent extends HomeEvent { + const GetAllOrdersListEvent(); + + @override + List get props => []; +} + +final class GetOrdersEvent extends HomeEvent { + const GetOrdersEvent({this.isLoading = true}); + + final bool isLoading; + + @override + List get props => [isLoading]; +} + +final class OrderStatusEvent extends HomeEvent { + final String status; + + const OrderStatusEvent({required this.status}); + + @override + List get props => [status]; +} + +final class PaginationEvent extends HomeEvent { + const PaginationEvent(); + + @override + List get props => []; +} + +final class GetBannersEvent extends HomeEvent { + const GetBannersEvent(); + + @override + List get props => []; +} + +final class RefreshEvent extends HomeEvent { + const RefreshEvent(); + + @override + List get props => []; +} diff --git a/lib/features/home/presentation/bloc/home_state.dart b/lib/features/home/presentation/bloc/home_state.dart new file mode 100644 index 0000000..e71d3cf --- /dev/null +++ b/lib/features/home/presentation/bloc/home_state.dart @@ -0,0 +1,52 @@ +part of 'home_bloc.dart'; + +class HomeState extends Equatable { + const HomeState({ + required this.isLoading, + required this.paginationLoading, + required this.page, + this.ordersList, + this.pageCount, + this.banners, + required this.selectedStatus, + }); + + final bool isLoading; + final bool paginationLoading; + final int page; + final int? pageCount; + final OrdersListResponse? ordersList; + final String selectedStatus; + final List? banners; + + HomeState copyWith({ + bool? isLoading, + bool? paginationLoading, + int? page, + int? pageCount, + OrdersListResponse? ordersList, + String? selectedStatus, + List? banners, + }) { + return HomeState( + isLoading: isLoading ?? this.isLoading, + page: page ?? this.page, + pageCount: pageCount ?? this.pageCount, + paginationLoading: paginationLoading ?? this.paginationLoading, + ordersList: ordersList ?? this.ordersList, + selectedStatus: selectedStatus ?? this.selectedStatus, + banners: banners ?? this.banners, + ); + } + + @override + List get props => [ + isLoading, + paginationLoading, + page, + ordersList, + pageCount, + selectedStatus, + banners, + ]; +} diff --git a/lib/features/home/presentation/bloc/notification/notification_bloc.dart b/lib/features/home/presentation/bloc/notification/notification_bloc.dart new file mode 100644 index 0000000..dbc0eea --- /dev/null +++ b/lib/features/home/presentation/bloc/notification/notification_bloc.dart @@ -0,0 +1,102 @@ +import 'package:cargocalculaterapp/constants/constants.dart'; +import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../data/models/notification_response.dart'; +import '../../../domain/usecase/notification_read_usecase.dart'; +import '../../../domain/usecase/notification_usecase.dart'; + +part 'notification_event.dart'; + +part 'notification_state.dart'; + +class NotificationBloc extends Bloc { + NotificationBloc(this.notificationUseCase, this.notificationReadUseCase) + : super( + const NotificationState( + isLoading: false, + currentPage: 1, + paginationLoading: false, + ), + ) { + on(_onGetNotifications); + on(_notificationPagination); + on(_notificationRead); + } + + final NotificationUseCase notificationUseCase; + final NotificationReadUseCase notificationReadUseCase; + final Map notificationRequest = {}; + + Future _onGetNotifications( + GetNotificationsEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true)); + notificationRequest[AppKeys.page] = 1; + notificationRequest[AppKeys.limit] = AppConst.limit; + + final response = await notificationUseCase(notificationRequest); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + }, + (r) { + add(ReadNotificationsEvent(notifications: r.notifications ?? [])); + emit( + state.copyWith( + isLoading: false, + currentPage: 1, + notifications: r.notifications, + pageCount: ((r.totalCount ?? 1) / 20).ceil(), + ), + ); + }, + ); + } + + Future _notificationPagination( + NotificationPaginationEvent event, + Emitter emit, + ) async { + notificationRequest[AppKeys.page] = event.page; + emit(state.copyWith(paginationLoading: true, currentPage: event.page)); + final response = await notificationUseCase(notificationRequest); + response.fold( + (l) { + emit(state.copyWith(paginationLoading: false)); + }, + (r) { + add(ReadNotificationsEvent(notifications: r.notifications ?? [])); + final List notifications = []; + notifications.addAll(List.of(state.notifications ?? [])); + notifications.addAll(List.of(r.notifications ?? [])); + + emit( + state.copyWith( + paginationLoading: false, + pageCount: ((r.totalCount ?? 1) / 20).ceil(), + notifications: notifications, + ), + ); + }, + ); + } + + Future _notificationRead( + ReadNotificationsEvent event, + Emitter emit, + ) async { + final List ids = []; + for (var element in event.notifications) { + if (!(element.isRead ?? false)) { + ids.add(element.id ?? ""); + } + } + if (ids.isNotEmpty) { + await notificationReadUseCase( + ReadNotificationRequest(notificationIds: ids), + ); + } + } +} diff --git a/lib/features/home/presentation/bloc/notification/notification_event.dart b/lib/features/home/presentation/bloc/notification/notification_event.dart new file mode 100644 index 0000000..0c8e404 --- /dev/null +++ b/lib/features/home/presentation/bloc/notification/notification_event.dart @@ -0,0 +1,30 @@ +part of 'notification_bloc.dart'; + +sealed class NotificationEvent extends Equatable { + const NotificationEvent(); +} + +final class GetNotificationsEvent extends NotificationEvent { + const GetNotificationsEvent(); + + @override + List get props => []; +} + +final class NotificationPaginationEvent extends NotificationEvent { + const NotificationPaginationEvent({required this.page}); + + final int page; + + @override + List get props => [page]; +} + +final class ReadNotificationsEvent extends NotificationEvent { + final List notifications; + + const ReadNotificationsEvent({required this.notifications}); + + @override + List get props => [notifications]; +} diff --git a/lib/features/home/presentation/bloc/notification/notification_state.dart b/lib/features/home/presentation/bloc/notification/notification_state.dart new file mode 100644 index 0000000..02ab214 --- /dev/null +++ b/lib/features/home/presentation/bloc/notification/notification_state.dart @@ -0,0 +1,42 @@ +part of 'notification_bloc.dart'; + +class NotificationState extends Equatable { + const NotificationState({ + required this.isLoading, + required this.paginationLoading, + required this.currentPage, + this.pageCount, + this.notifications, + }); + + final bool isLoading; + final bool paginationLoading; + final int currentPage; + final int? pageCount; + final List? notifications; + + NotificationState copyWith({ + bool? isLoading, + bool? paginationLoading, + int? currentPage, + int? pageCount, + List? notifications, + }) { + return NotificationState( + isLoading: isLoading ?? this.isLoading, + paginationLoading: paginationLoading ?? this.paginationLoading, + currentPage: currentPage ?? this.currentPage, + pageCount: pageCount ?? this.pageCount, + notifications: notifications ?? this.notifications, + ); + } + + @override + List get props => [ + isLoading, + paginationLoading, + currentPage, + pageCount, + notifications, + ]; +} diff --git a/lib/features/home/presentation/bloc/order_single/order_single_bloc.dart b/lib/features/home/presentation/bloc/order_single/order_single_bloc.dart new file mode 100644 index 0000000..dfb9170 --- /dev/null +++ b/lib/features/home/presentation/bloc/order_single/order_single_bloc.dart @@ -0,0 +1,34 @@ +import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../domain/usecase/order_single_usecase.dart'; + +part 'order_single_event.dart'; + +part 'order_single_state.dart'; + +class OrderSingleBloc extends Bloc { + OrderSingleBloc(this.orderSingleUseCase) + : super(const OrderSingleState(isLoading: false)) { + on(_getOrderInfo); + } + + final OrderSingleUseCase orderSingleUseCase; + + Future _getOrderInfo( + GetOrderSingleEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true)); + final response = await orderSingleUseCase(event.orderId); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + }, + (r) { + emit(state.copyWith(isLoading: false, orderSingleResponse: r)); + }, + ); + } +} diff --git a/lib/features/home/presentation/bloc/order_single/order_single_event.dart b/lib/features/home/presentation/bloc/order_single/order_single_event.dart new file mode 100644 index 0000000..6813b65 --- /dev/null +++ b/lib/features/home/presentation/bloc/order_single/order_single_event.dart @@ -0,0 +1,14 @@ +part of 'order_single_bloc.dart'; + +sealed class OrderSingleEvent extends Equatable { + const OrderSingleEvent(); +} + +class GetOrderSingleEvent extends OrderSingleEvent { + const GetOrderSingleEvent({required this.orderId}); + + final String orderId; + + @override + List get props => [orderId]; +} diff --git a/lib/features/home/presentation/bloc/order_single/order_single_state.dart b/lib/features/home/presentation/bloc/order_single/order_single_state.dart new file mode 100644 index 0000000..35b8890 --- /dev/null +++ b/lib/features/home/presentation/bloc/order_single/order_single_state.dart @@ -0,0 +1,21 @@ +part of 'order_single_bloc.dart'; + +class OrderSingleState extends Equatable { + const OrderSingleState({required this.isLoading, this.orderSingleResponse}); + + final bool isLoading; + final OrderSingleResponse? orderSingleResponse; + + OrderSingleState copyWith({ + bool? isLoading, + OrderSingleResponse? orderSingleResponse, + }) { + return OrderSingleState( + isLoading: isLoading ?? this.isLoading, + orderSingleResponse: orderSingleResponse ?? this.orderSingleResponse, + ); + } + + @override + List get props => [isLoading, orderSingleResponse]; +} diff --git a/lib/features/home/presentation/mixin/banner_mixin.dart b/lib/features/home/presentation/mixin/banner_mixin.dart new file mode 100644 index 0000000..ab6c24e --- /dev/null +++ b/lib/features/home/presentation/mixin/banner_mixin.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +mixin BannerMixin { + late PageController pageController; + + void initController() { + pageController = PageController(viewportFraction: 0.94); + } + + void disposeController() { + pageController.dispose(); + } +} diff --git a/lib/features/home/presentation/mixin/comment_mixin.dart b/lib/features/home/presentation/mixin/comment_mixin.dart new file mode 100644 index 0000000..f534bdd --- /dev/null +++ b/lib/features/home/presentation/mixin/comment_mixin.dart @@ -0,0 +1,13 @@ +import 'package:flutter/cupertino.dart'; + +mixin CommentMixin { + late TextEditingController commentController; + + void initControllers() { + commentController = TextEditingController(); + } + + void disposeControllers() { + commentController.dispose(); + } +} diff --git a/lib/features/home/presentation/mixin/home_mixin.dart b/lib/features/home/presentation/mixin/home_mixin.dart new file mode 100644 index 0000000..5df3d19 --- /dev/null +++ b/lib/features/home/presentation/mixin/home_mixin.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +mixin HomeMixin { + late ScrollController scrollController; + + void initControllers() { + scrollController = ScrollController(); + } + + void disposeControllers() { + scrollController.dispose(); + } +} diff --git a/lib/features/home/presentation/mixin/image_page_mixin.dart b/lib/features/home/presentation/mixin/image_page_mixin.dart new file mode 100644 index 0000000..4fbf34d --- /dev/null +++ b/lib/features/home/presentation/mixin/image_page_mixin.dart @@ -0,0 +1,13 @@ +import 'package:flutter/cupertino.dart'; + +mixin ImagePageMixin { + late PageController pageController; + + void initController(int initialPage) { + pageController = PageController(initialPage: initialPage + 1); + } + + void disposeController() { + pageController.dispose(); + } +} diff --git a/lib/features/home/presentation/mixin/notification_mixin.dart b/lib/features/home/presentation/mixin/notification_mixin.dart new file mode 100644 index 0000000..6ac9e77 --- /dev/null +++ b/lib/features/home/presentation/mixin/notification_mixin.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +mixin NotificationMixin { + late ScrollController scrollController; + + void initControllers() { + scrollController = ScrollController(); + } + + void disposeControllers() { + scrollController.dispose(); + } +} diff --git a/lib/features/home/presentation/pages/arguments/comment_argument.dart b/lib/features/home/presentation/pages/arguments/comment_argument.dart new file mode 100644 index 0000000..3ef47eb --- /dev/null +++ b/lib/features/home/presentation/pages/arguments/comment_argument.dart @@ -0,0 +1,5 @@ +class CommentArgument { + final String orderId; + + CommentArgument({required this.orderId}); +} diff --git a/lib/features/home/presentation/pages/arguments/image_page_argument.dart b/lib/features/home/presentation/pages/arguments/image_page_argument.dart new file mode 100644 index 0000000..493388e --- /dev/null +++ b/lib/features/home/presentation/pages/arguments/image_page_argument.dart @@ -0,0 +1,6 @@ +class ImagePageArgument { + final List? images; + final int? currentPage; + + ImagePageArgument({this.images, this.currentPage}); +} diff --git a/lib/features/home/presentation/pages/arguments/order_info_argument.dart b/lib/features/home/presentation/pages/arguments/order_info_argument.dart new file mode 100644 index 0000000..40e2e8d --- /dev/null +++ b/lib/features/home/presentation/pages/arguments/order_info_argument.dart @@ -0,0 +1,6 @@ +class OrderInfoArgument{ + final String orderId; + final String orderNumber; + + OrderInfoArgument({required this.orderId, required this.orderNumber}); +} \ No newline at end of file diff --git a/lib/features/home/presentation/pages/comment/comment_page.dart b/lib/features/home/presentation/pages/comment/comment_page.dart new file mode 100644 index 0000000..5bff19a --- /dev/null +++ b/lib/features/home/presentation/pages/comment/comment_page.dart @@ -0,0 +1,97 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/core/widgets/loading/progress_hud.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../core/widgets/rating/rating_bar_widget.dart'; +import '../../../../../core/widgets/text_filds/custom_text_field_name.dart'; +import '../../bloc/comment/comment_bloc.dart'; +import '../../mixin/comment_mixin.dart'; +import '../arguments/comment_argument.dart'; + +class CommentPage extends StatefulWidget { + const CommentPage({super.key, required this.argument}); + + final CommentArgument? argument; + + @override + State createState() => _CommentPageState(); +} + +class _CommentPageState extends State with CommentMixin { + @override + void initState() { + initControllers(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Scaffold( + appBar: AppBar(title: Text(AppLocalization.current.left_comment)), + body: ModalProgressHUD( + inAsyncCall: state.isLoading, + child: ListView( + padding: AppUtils.kPaddingAll16, + children: [ + Text( + AppLocalization.current.comment_desc, + style: context.text.authDesc, + ), + AppUtils.kBoxHeight16, + RatingBarWidget( + rating: state.rating, + separator: AppUtils.kBoxWith12, + iconsSize: 24, + onRatingChanged: (rating) { + context.read().add( + RatingChangedEvent(rating: rating), + ); + }, + ), + AppUtils.kBoxHeight24, + CustomTextFieldName( + name: AppLocalization.current.comment, + hint: AppLocalization.current.comment_text, + minLines: 4, + controller: commentController, + onchange: (comment) { + context.read().add( + CommentEnterEvent(comment: comment), + ); + }, + ), + AppUtils.kBoxHeight16, + Padding( + padding: AppUtils.kPaddingHor34, + child: ElevatedButton( + onPressed: + state.rating > 0 && (state.comment?.isNotEmpty ?? false) + ? () { + context.read().add( + SubmitEvent( + orderId: widget.argument?.orderId ?? "", + ), + ); + } + : null, + child: Text(AppLocalization.current.send), + ), + ), + ], + ), + ), + ); + }, + ); + } + + @override + void dispose() { + disposeControllers(); + super.dispose(); + } +} diff --git a/lib/features/home/presentation/pages/home_page.dart b/lib/features/home/presentation/pages/home_page.dart new file mode 100644 index 0000000..81c844a --- /dev/null +++ b/lib/features/home/presentation/pages/home_page.dart @@ -0,0 +1,139 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/local_source/local_source.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/core/widgets/loading/custom_loading.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/widgets/banners_widget.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/widgets/empty_order_widget.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/widgets/order_list_shimmer.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/widgets/orders_list_widget.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/widgets/status_list_widget.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:cargocalculaterapp/router/name_routes.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import '../../../../injector_container.dart'; +import '../bloc/home_bloc.dart'; +import '../mixin/home_mixin.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State with HomeMixin { + @override + void initState() { + context.read().add(const GetAllOrdersListEvent()); + context.read().add(const GetBannersEvent()); + initControllers(); + _scrollListener(); + super.initState(); + } + + void _scrollListener() { + scrollController.addListener(() { + final state = context.read().state; + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200 && + !state.paginationLoading && + state.page <= (state.pageCount ?? 1)) { + context.read().add(const PaginationEvent()); + } + }); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Scaffold( + backgroundColor: context.color.grayBackground, + appBar: AppBar( + title: Row( + children: [ + GestureDetector( + onTap: () { + Clipboard.setData( + ClipboardData(text: sl().getUCode()), + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.current.text_copied), + ), + ); + }, + child: Text( + sl().getUCode(), + style: context.text.secondaryText14, + ), + ), + const Spacer(), + Text(AppLocalization.current.orders), + const Spacer(), + ], + ), + automaticallyImplyLeading: false, + actions: [ + IconButton( + onPressed: () { + Navigator.pushNamed(context, Routes.notification); + }, + icon: SvgPicture.asset( + "assets/svg/ic_notification.svg", + colorFilter: ColorFilter.mode( + context.color.textColor, + BlendMode.srcIn, + ), + ), + ), + AppUtils.kBoxWith12, + ], + ), + body: CustomScrollView( + controller: scrollController, + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics(), + ), + slivers: [ + StatusListWidget(selectedStatus: state.selectedStatus), + CupertinoSliverRefreshControl( + onRefresh: () async { + context.read().add(const RefreshEvent()); + await Future.delayed( + const Duration(milliseconds: 1500), + ); + }, + ), + if (state.banners?.isNotEmpty ?? false) + BannersWidget(banners: state.banners), + state.isLoading + ? const OrderListShimmer() + : (state.ordersList?.data?.length ?? 0) > 0 + ? OrdersListWidget(data: state.ordersList?.data) + : const EmptyOrderWidget(), + if (state.paginationLoading) + const SliverPadding( + padding: AppUtils.kPaddingAll24, + sliver: SliverToBoxAdapter( + child: Center(child: CustomLoadingWidget()), + ), + ), + ], + ), + ); + }, + ); + } + + @override + void dispose() { + disposeControllers(); + scrollController.removeListener(_scrollListener); + super.dispose(); + } +} diff --git a/lib/features/home/presentation/pages/image/image_page.dart b/lib/features/home/presentation/pages/image/image_page.dart new file mode 100644 index 0000000..4763fc7 --- /dev/null +++ b/lib/features/home/presentation/pages/image/image_page.dart @@ -0,0 +1,46 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import '../../../../../constants/constants.dart'; +import '../../mixin/image_page_mixin.dart'; +import '../arguments/image_page_argument.dart'; + +class ImagePage extends StatefulWidget { + const ImagePage({super.key, required this.argument}); + + final ImagePageArgument? argument; + + @override + State createState() => _ImagePageState(); +} + +class _ImagePageState extends State with ImagePageMixin { + @override + void initState() { + initController(widget.argument?.currentPage ?? 0); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: PageView.builder( + controller: pageController, + + itemCount: widget.argument?.images?.length ?? 0, + itemBuilder: (_, index) => InteractiveViewer( + scaleEnabled: true, + panEnabled: true, + constrained: true, + maxScale: 14, + clipBehavior: Clip.antiAlias, + child: CachedNetworkImage( + imageUrl: "${Constants.baseUrl}${widget.argument?.images?[index]}", + height: double.infinity, + width: double.infinity, + ), + ), + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/notification/notification_page.dart b/lib/features/home/presentation/pages/notification/notification_page.dart new file mode 100644 index 0000000..d282a07 --- /dev/null +++ b/lib/features/home/presentation/pages/notification/notification_page.dart @@ -0,0 +1,120 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/functions/base_finctions.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/core/widgets/loading/progress_hud.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../../core/widgets/loading/custom_loading.dart'; +import '../../bloc/notification/notification_bloc.dart'; +import '../../mixin/notification_mixin.dart'; + +class NotificationPage extends StatefulWidget { + const NotificationPage({super.key}); + + @override + State createState() => _NotificationPageState(); +} + +class _NotificationPageState extends State + with NotificationMixin { + @override + void initState() { + context.read().add(const GetNotificationsEvent()); + initControllers(); + super.initState(); + _scrollListener(); + } + + void _scrollListener() { + scrollController.addListener(() { + final state = context.read().state; + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200 && + !state.paginationLoading && + state.currentPage <= (state.pageCount ?? 1)) { + context.read().add( + NotificationPaginationEvent(page: state.currentPage + 1), + ); + } + }); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalization.current.notification), + elevation: 0.2, + ), + body: ModalProgressHUD( + inAsyncCall: state.paginationLoading, + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverPadding( + padding: AppUtils.kPaddingAll16, + sliver: SliverList.separated( + itemBuilder: (context, index) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Ink( + width: 36, + height: 36, + decoration: BoxDecoration( + color: context.color.iconBackground, + borderRadius: AppUtils.kBorderRadius24, + ), + padding: AppUtils.kPaddingAll8, + child: SvgPicture.asset( + "assets/svg/ic_check_box.svg", + ), + ), + AppUtils.kBoxWith8, + Expanded( + child: Text( + Functions.getTranslatedNameModel( + state.notifications?[index].text, + context, + ), + style: context.text.authDesc, + ), + ), + ], + ), + separatorBuilder: (_, _) => Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Divider( + thickness: 1, + height: 1, + color: context.color.lightBorder, + ), + ), + itemCount: state.notifications?.length ?? 0, + ), + ), + if (state.paginationLoading) + const SliverPadding( + padding: AppUtils.kPaddingAll24, + sliver: SliverToBoxAdapter( + child: Center(child: CustomLoadingWidget()), + ), + ), + ], + ), + ), + ); + }, + ); + } + + @override + void dispose() { + disposeControllers(); + super.dispose(); + } +} diff --git a/lib/features/home/presentation/pages/order_info/order_info_page.dart b/lib/features/home/presentation/pages/order_info/order_info_page.dart new file mode 100644 index 0000000..27b306c --- /dev/null +++ b/lib/features/home/presentation/pages/order_info/order_info_page.dart @@ -0,0 +1,390 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/core/widgets/loading/custom_loading.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/arguments/image_page_argument.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/order_info/widgets/delivery_info_widget.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/order_info/widgets/order_info_widget.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/order_info/widgets/status_item_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import '../../../../../constants/constants.dart'; +import '../../../../../core/functions/base_finctions.dart'; +import '../../../../../generated/l10n.dart'; +import '../../../../../router/name_routes.dart'; +import '../../bloc/order_single/order_single_bloc.dart'; +import '../arguments/comment_argument.dart'; +import '../arguments/order_info_argument.dart'; + +class OrderInfoPage extends StatefulWidget { + const OrderInfoPage({super.key, required this.argument}); + + final OrderInfoArgument? argument; + + @override + State createState() => _OrderInfoPageState(); +} + +class _OrderInfoPageState extends State { + @override + void initState() { + context.read().add( + GetOrderSingleEvent(orderId: widget.argument?.orderId ?? ""), + ); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final status = _checkStatus( + state.orderSingleResponse?.status?.code ?? "", + ); + return Scaffold( + backgroundColor: context.color.grayBackground, + appBar: AppBar(title: Text("#${widget.argument?.orderNumber ?? ""}")), + body: state.isLoading + ? const Center(child: CustomLoadingWidget()) + : ListView( + padding: AppUtils.kPaddingVer24Hor16, + children: [ + Container( + padding: AppUtils.kPaddingAll16, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.statusBackground, + border: Border.all(color: context.color.lightBorder), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalization.current.order_delivery_info, + style: context.text.orderTitle, + ), + AppUtils.kBoxHeight16, + DeliveryInfoWidget( + title: AppLocalization.current.order_number, + name: state.orderSingleResponse?.orderNumber ?? "", + icon: "assets/svg/ic_box_gray.svg", + ), + DeliveryInfoWidget( + title: AppLocalization.current.warehouse, + name: + Localizations.localeOf(context).languageCode == + "uz" + ? (state.orderSingleResponse?.wharehouseUz ?? + "") + : Localizations.localeOf( + context, + ).languageCode == + "ru" + ? (state.orderSingleResponse?.wharehouseRu ?? + "") + : (state.orderSingleResponse?.wharehouseZh ?? + ""), + icon: "assets/svg/ic_building.svg", + ), + DeliveryInfoWidget( + title: AppLocalization.current.delivery_price, + name: + state.orderSingleResponse?.deliveryPrice ?? "", + icon: "assets/svg/ic_chek.svg", + ), + DeliveryInfoWidget( + title: + AppLocalization.current.delivery_time_estimated, + name: DateFormat('dd-MM-yyyy').format( + DateTime.tryParse( + state.orderSingleResponse?.arrivalDate ?? + "", + ) ?? + DateTime.now(), + ), + icon: "assets/svg/ic_calendar.svg", + ), + Divider( + height: 1, + thickness: 1, + color: Colors.black.withAlpha(20), + ), + AppUtils.kBoxHeight16, + StatusItemWidget( + isCompleted: status >= 1, + dividerCompleted: status > 1, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.waiting], + context, + ), + number: "1", + ), + StatusItemWidget( + isCompleted: status >= 2, + dividerCompleted: status > 2, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.partReceived], + context, + ), + number: "2", + ), + StatusItemWidget( + isCompleted: status >= 3, + dividerCompleted: status > 3, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.fullReceived], + context, + ), + number: "3", + ), + StatusItemWidget( + isCompleted: status >= 4, + dividerCompleted: status > 4, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.shipped], + context, + ), + number: "4", + ), + StatusItemWidget( + dividerCompleted: status > 5, + isCompleted: status >= 5, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.leftChine], + context, + ), + number: "5", + ), + StatusItemWidget( + isCompleted: status >= 6, + dividerCompleted: status > 6, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.enteredUzbekistan], + context, + ), + number: "6", + ), + StatusItemWidget( + isCompleted: status >= 7, + dividerCompleted: status > 7, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.tashkentWarehouse], + context, + ), + number: "7", + ), + StatusItemWidget( + isCompleted: status >= 8, + dividerCompleted: status > 8, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.customsClearance], + context, + ), + number: "8", + ), + StatusItemWidget( + isCompleted: status >= 9, + dividerCompleted: status > 9, + title: Functions.getTranslatedItem( + AppConst.statusList[AppConst.delivered], + context, + ), + showConnector: false, + number: "9", + ), + ], + ), + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 16), + padding: AppUtils.kPaddingAll16, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.statusBackground, + border: Border.all(color: context.color.lightBorder), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalization.current.order_details, + style: context.text.orderTitle, + ), + AppUtils.kBoxHeight12, + OrderInfoWidget( + title: AppLocalization.current.order_name, + name: + Localizations.localeOf(context).languageCode == + "uz" + ? (state.orderSingleResponse?.nameUz ?? "") + : Localizations.localeOf( + context, + ).languageCode == + "ru" + ? (state.orderSingleResponse?.nameRu ?? "") + : (state.orderSingleResponse?.nameZh ?? ""), + ), + OrderInfoWidget( + title: AppLocalization.current.weight, + name: state.orderSingleResponse?.weight ?? "", + ), + OrderInfoWidget( + title: AppLocalization.current.size, + name: state.orderSingleResponse?.m3 ?? "", + ), + OrderInfoWidget( + title: AppLocalization.current.average_weight, + name: + state.orderSingleResponse?.averageWeightKg ?? + "", + ), + AppUtils.kBoxHeight8, + if ((state.orderSingleResponse?.images?.length ?? 0) > + 0) + Text( + AppLocalization.current.order_photo, + style: context.text.orderTitle, + ), + if ((state.orderSingleResponse?.images?.length ?? 0) > + 0) + AppUtils.kBoxHeight8, + if ((state.orderSingleResponse?.images?.length ?? 0) > + 0) + SizedBox( + height: 72, + child: ListView.separated( + padding: EdgeInsets.zero, + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) => GestureDetector( + onTap: () { + Navigator.pushNamed( + context, + Routes.image, + arguments: ImagePageArgument( + currentPage: index, + images: + state.orderSingleResponse?.images, + ), + ); + }, + child: ClipRRect( + borderRadius: AppUtils.kBorderRadius16, + child: CachedNetworkImage( + imageUrl: + "${Constants.baseUrl}${state.orderSingleResponse?.images?[index]}", + width: 72, + height: 72, + fit: BoxFit.cover, + errorWidget: (context, _, _) { + return const SizedBox( + width: 72, + height: 72, + child: Center( + child: Icon(Icons.warning), + ), + ); + }, + ), + ), + ), + separatorBuilder: (_, _) => AppUtils.kBoxWidth8, + itemCount: + state.orderSingleResponse?.images?.length ?? + 0, + ), + ), + ], + ), + ), + if (status == 9) + Padding( + padding: AppUtils.kPaddingHor34, + child: InkWell( + onTap: () { + Navigator.pushNamed( + context, + Routes.comment, + arguments: CommentArgument( + orderId: widget.argument?.orderNumber ?? "", + ), + ); + }, + borderRadius: AppUtils.kBorderRadius24, + child: Ink( + decoration: BoxDecoration( + color: context.color.scaffoldBackgroundColor, + borderRadius: AppUtils.kBorderRadius24, + border: Border.all( + color: context.color.primaryColor, + ), + ), + height: 56, + padding: AppUtils.kPaddingHor16, + child: Center( + child: Text( + AppLocalization.current.left_comment, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: context.color.textColor, + ), + ), + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + int _checkStatus(String status) { + switch (status) { + case AppConst.waiting: + { + return 1; + } + case AppConst.partReceived: + { + return 2; + } + case AppConst.fullReceived: + { + return 3; + } + case AppConst.shipped: + { + return 4; + } + case AppConst.leftChine: + { + return 5; + } + case AppConst.enteredUzbekistan: + { + return 6; + } + case AppConst.tashkentWarehouse: + { + return 7; + } + case AppConst.customsClearance: + { + return 8; + } + case AppConst.delivered: + { + return 9; + } + default: + { + return 1; + } + } + } +} diff --git a/lib/features/home/presentation/pages/order_info/widgets/delivery_info_widget.dart b/lib/features/home/presentation/pages/order_info/widgets/delivery_info_widget.dart new file mode 100644 index 0000000..27cb040 --- /dev/null +++ b/lib/features/home/presentation/pages/order_info/widgets/delivery_info_widget.dart @@ -0,0 +1,32 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class DeliveryInfoWidget extends StatelessWidget { + const DeliveryInfoWidget({ + super.key, + required this.icon, + required this.title, + required this.name, + }); + + final String icon; + final String title; + final String name; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + children: [ + SvgPicture.asset(icon, width: 12, height: 12), + AppUtils.kBoxWidth4, + Expanded(child: Text(title, style: context.text.orderListTitle)), + Text(name, style: context.text.profileCategory), + ], + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/order_info/widgets/order_info_widget.dart b/lib/features/home/presentation/pages/order_info/widgets/order_info_widget.dart new file mode 100644 index 0000000..48a332f --- /dev/null +++ b/lib/features/home/presentation/pages/order_info/widgets/order_info_widget.dart @@ -0,0 +1,31 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:flutter/material.dart'; + +class OrderInfoWidget extends StatelessWidget { + const OrderInfoWidget({super.key, required this.title, required this.name}); + + final String title; + final String name; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: Text(title, style: context.text.orderListTitle)), + AppUtils.kBoxWidth8, + Expanded( + child: Text( + name, + style: context.text.profileCategory, + textAlign: TextAlign.end, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/order_info/widgets/status_item_widget.dart b/lib/features/home/presentation/pages/order_info/widgets/status_item_widget.dart new file mode 100644 index 0000000..99843f0 --- /dev/null +++ b/lib/features/home/presentation/pages/order_info/widgets/status_item_widget.dart @@ -0,0 +1,73 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:flutter/material.dart'; + +class StatusItemWidget extends StatelessWidget { + final bool showConnector; + final bool isCompleted; + final bool dividerCompleted; + final String title; + final String number; + + const StatusItemWidget({ + super.key, + + this.showConnector = true, + this.dividerCompleted = false, + required this.isCompleted, + required this.title, + required this.number, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Container( + margin: const EdgeInsets.only(top: 4, bottom: 4, right: 12), + width: 32, + height: 32, + decoration: BoxDecoration( + color: + isCompleted + ? context.color.primaryColor + : context.color.statusIconBackground, + border: Border.all(color: context.color.primaryColor), + borderRadius: AppUtils.kBorderRadius48, + ), + padding: AppUtils.kPaddingAll6, + child: + isCompleted + ? const Center( + child: Icon( + Icons.check_rounded, + color: Colors.white, + size: 16, + ), + ) + : Center( + child: Text(number, style: context.text.statusNumber), + ), + ), + Text( + title, + textAlign: TextAlign.start, + style: context.text.statusDesc, + ), + ], + ), + if (showConnector) + Container( + margin: const EdgeInsets.only(left: 15), + height: 16, + width: 2, + color: context.color.primaryColor, + ), + ], + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/banners_widget.dart b/lib/features/home/presentation/pages/widgets/banners_widget.dart new file mode 100644 index 0000000..735a9be --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/banners_widget.dart @@ -0,0 +1,101 @@ +import 'dart:async'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import '../../../../../constants/constants.dart'; +import '../../../../../core/utils/app_utils.dart'; +import '../../../data/models/banner_response.dart'; +import '../../mixin/banner_mixin.dart'; + +class BannersWidget extends StatefulWidget { + const BannersWidget({super.key, required this.banners}); + + final List? banners; + + @override + State createState() => _BannersWidgetState(); +} + +class _BannersWidgetState extends State with BannerMixin { + Timer? timer; + + @override + void initState() { + initController(); + if ((widget.banners?.length ?? 0) > 1) { + animationTimer(); + } + super.initState(); + } + + @override + void didUpdateWidget(covariant BannersWidget oldWidget) { + if ((widget.banners?.length ?? 0) > 1) { + animationTimer(); + } else if ((widget.banners?.length ?? 0) == 1) { + if (timer?.isActive ?? false) { + timer?.cancel(); + } + } + super.didUpdateWidget(oldWidget); + } + + void animationTimer() { + if (timer?.isActive ?? false) { + timer?.cancel(); + } + timer = Timer.periodic(const Duration(seconds: 4), (Timer timer) async { + pageController.nextPage( + duration: const Duration(milliseconds: 800), + curve: Curves.fastOutSlowIn, + ); + }); + } + + @override + void dispose() { + timer?.cancel(); + pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(bottom: 16), + child: SizedBox( + height: 180, + width: double.infinity, + child: PageView.builder( + controller: pageController, + itemCount: (widget.banners?.length ?? 0) > 1 + ? null + : widget.banners?.length, + scrollDirection: Axis.horizontal, + physics: widget.banners?.length != 1 + ? null + : const NeverScrollableScrollPhysics(), + itemBuilder: (_, index) => GestureDetector( + onTap: () {}, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: ClipRRect( + borderRadius: AppUtils.kBorderRadius16, + + child: CachedNetworkImage( + imageUrl: + "${Constants.baseUrl}${widget.banners?[index % (widget.banners?.length ?? 1)].image}", + height: 180, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/empty_order_widget.dart b/lib/features/home/presentation/pages/widgets/empty_order_widget.dart new file mode 100644 index 0000000..ac84539 --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/empty_order_widget.dart @@ -0,0 +1,55 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import '../../../../../core/utils/app_utils.dart'; + +class EmptyOrderWidget extends StatelessWidget { + const EmptyOrderWidget({super.key}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + width: double.infinity, + // padding: const EdgeInsets.symmetric(vertical: 42, horizontal: 32), + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.statusBackground, + border: Border.all(color: context.color.lightBorder), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: AppUtils.kBorderRadiusTop16, + child: SvgPicture.asset( + "assets/svg/ic_empty_box.svg", + width: MediaQuery.sizeOf(context).width - 34, + ), + ), + Text( + AppLocalization.current.no_order, + style: context.text.titleBig, + textAlign: TextAlign.center, + ), + AppUtils.kBoxHeight12, + Padding( + padding: AppUtils.kPaddingHor24, + child: Text( + AppLocalization.current.no_order_desc, + style: context.text.profileCategory.copyWith( + color: context.color.lightSecondary, + ), + textAlign: TextAlign.center, + ), + ), + AppUtils.kBoxHeight48, + ], + ), + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/order_list_shimmer.dart b/lib/features/home/presentation/pages/widgets/order_list_shimmer.dart new file mode 100644 index 0000000..58dfa2e --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/order_list_shimmer.dart @@ -0,0 +1,66 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/widgets/shimmer/shimmer_widget.dart'; +import 'package:flutter/material.dart'; +import '../../../../../core/utils/app_utils.dart'; + +class OrderListShimmer extends StatelessWidget { + const OrderListShimmer({super.key}); + + @override + Widget build(BuildContext context) { + return SliverPadding( + padding: AppUtils.kPaddingAll16, + sliver: SliverList.separated( + itemBuilder: (context, index) { + return Ink( + padding: AppUtils.kPaddingAll16, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius12, + color: context.color.scaffoldBackgroundColor, + ), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShimmerWidget(width: 60, height: 20), + ShimmerWidget(width: 100, height: 20), + ], + ), + AppUtils.kBoxHeight8, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShimmerWidget(width: 40, height: 20), + ShimmerWidget(width: 70, height: 20), + ], + ), + AppUtils.kBoxHeight4, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShimmerWidget(width: 100, height: 20), + ShimmerWidget(width: 60, height: 20), + ], + ), + AppUtils.kBoxHeight4, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShimmerWidget(width: 80, height: 20), + ShimmerWidget(width: 140, height: 20), + ], + ), + ], + ), + ); + }, + separatorBuilder: (_, __) => AppUtils.kBoxHeight12, + itemCount: 6, + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/order_text_widget.dart b/lib/features/home/presentation/pages/widgets/order_text_widget.dart new file mode 100644 index 0000000..bbb4c42 --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/order_text_widget.dart @@ -0,0 +1,31 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:flutter/material.dart'; + +class OrderTextWidget extends StatelessWidget { + const OrderTextWidget({ + super.key, + required this.title, + required this.name, + this.crossAxisAlignment = CrossAxisAlignment.start, + }); + + final String title; + final String name; + final CrossAxisAlignment crossAxisAlignment; + + @override + Widget build(BuildContext context) { + return Expanded( + child: Column( + crossAxisAlignment: crossAxisAlignment, + mainAxisSize: MainAxisSize.min, + children: [ + Text(title, style: context.text.orderListTitle), + AppUtils.kBoxHeight6, + Text(name, style: context.text.categoryName), + ], + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/orders_list_widget.dart b/lib/features/home/presentation/pages/widgets/orders_list_widget.dart new file mode 100644 index 0000000..a93e9f0 --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/orders_list_widget.dart @@ -0,0 +1,109 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/widgets/status_widget.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:cargocalculaterapp/router/name_routes.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import '../../../data/models/orders_list_response.dart'; +import '../arguments/order_info_argument.dart'; +import 'order_text_widget.dart'; + +class OrdersListWidget extends StatelessWidget { + const OrdersListWidget({super.key, required this.data}); + + final List? data; + + @override + Widget build(BuildContext context) { + return SliverPadding( + padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), + sliver: SliverList.separated( + itemBuilder: (context, index) { + return InkWell( + onTap: () { + Navigator.pushNamed( + context, + Routes.orderInfo, + arguments: OrderInfoArgument( + orderId: data?[index].id ?? "", + orderNumber: data?[index].orderNumber ?? "", + ), + ); + }, + borderRadius: AppUtils.kBorderRadius16, + child: Ink( + padding: AppUtils.kPaddingAll16, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.statusBackground, + border: Border.all(color: context.color.lightBorder), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + OrderTextWidget( + title: AppLocalization.current.order_number, + name: data?[index].orderNumber ?? "", + ), + StatusWidget(status: data?[index].status?.code ?? ""), + ], + ), + AppUtils.kBoxHeight16, + Row( + children: [ + OrderTextWidget( + title: AppLocalization.current.warehouse, + name: + Localizations.localeOf(context).languageCode == "uz" + ? (data?[index].wharehouseUz ?? "") + : Localizations.localeOf(context).languageCode == + "ru" + ? (data?[index].wharehouseRu ?? "") + : (data?[index].wharehouseZh ?? ""), + ), + OrderTextWidget( + crossAxisAlignment: CrossAxisAlignment.end, + title: AppLocalization.current.order_name, + name: + Localizations.localeOf(context).languageCode == "uz" + ? (data?[index].nameUz ?? "") + : Localizations.localeOf(context).languageCode == + "ru" + ? (data?[index].nameRu ?? "") + : (data?[index].nameZh ?? ""), + ), + ], + ), + AppUtils.kBoxHeight16, + Row( + children: [ + OrderTextWidget( + title: AppLocalization.current.delivery_price, + name: (data?[index].deliveryPrice ?? ""), + ), + OrderTextWidget( + crossAxisAlignment: CrossAxisAlignment.end, + title: AppLocalization.current.delivery_time, + name: DateFormat('dd-MM-yyyy').format( + DateTime.tryParse(data?[index].arrivalDate ?? "") ?? + DateTime.now(), + ), + ), + ], + ), + ], + ), + ), + ); + }, + separatorBuilder: (_, __) => AppUtils.kBoxHeight12, + itemCount: data?.length ?? 0, + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/status_list_widget.dart b/lib/features/home/presentation/pages/widgets/status_list_widget.dart new file mode 100644 index 0000000..fa8bd00 --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/status_list_widget.dart @@ -0,0 +1,98 @@ +import 'package:cargocalculaterapp/constants/constants.dart'; +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/functions/base_finctions.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/features/home/presentation/bloc/home_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class StatusListWidget extends StatelessWidget { + const StatusListWidget({super.key, required this.selectedStatus}); + + final String selectedStatus; + + @override + Widget build(BuildContext context) { + return SliverPersistentHeader( + pinned: true, + delegate: _StatusHeaderDelegate(selectedStatus: selectedStatus), + ); + } +} + +class _StatusHeaderDelegate extends SliverPersistentHeaderDelegate { + final String selectedStatus; + + _StatusHeaderDelegate({required this.selectedStatus}); + + @override + double get minExtent => 72; + + @override + double get maxExtent => 72; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return Material( + color: context.color.grayBackground, + child: SizedBox( + height: 72, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + final key = AppConst.statusList.keys.toList()[index]; + return InkWell( + borderRadius: AppUtils.kBorderRadius16, + onTap: () { + context.read().add(OrderStatusEvent(status: key)); + }, + child: Ink( + padding: AppUtils.kPaddingHor16, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: + key == selectedStatus + ? context.color.primaryColor + : context.color.statusBackground, + border: Border.all( + color: + key == selectedStatus + ? context.color.primaryColor + : context.color.lightSecondary, + ), + ), + child: Center( + child: Text( + Functions.getTranslatedItem( + AppConst.statusList.values.toList()[index], + context, + ), + textAlign: TextAlign.center, + style: + key == selectedStatus + ? context.text.statusText + : context.text.statusText.copyWith( + color: context.color.textColor, + ), + ), + ), + ), + ); + }, + separatorBuilder: (_, __) => AppUtils.kBoxWidth16, + itemCount: AppConst.statusList.length, + ), + ), + ); + } + + @override + bool shouldRebuild(covariant _StatusHeaderDelegate oldDelegate) { + return true; + } +} diff --git a/lib/features/home/presentation/pages/widgets/status_widget.dart b/lib/features/home/presentation/pages/widgets/status_widget.dart new file mode 100644 index 0000000..b7e05f4 --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/status_widget.dart @@ -0,0 +1,89 @@ +import 'package:cargocalculaterapp/core/functions/base_finctions.dart'; +import 'package:cargocalculaterapp/core/theme/app_text_styles.dart'; +import 'package:cargocalculaterapp/core/theme/colors/app_colors.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../constants/constants.dart'; + +class StatusWidget extends StatelessWidget { + const StatusWidget({super.key, required this.status}); + + final String status; + + @override + Widget build(BuildContext context) { + switch (status) { + case AppConst.waiting: + { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + decoration: const BoxDecoration( + borderRadius: AppUtils.kBorderRadius24, + color: ThemeColors.warningSurface, + ), + child: Text( + Functions.getTranslatedItem( + AppConst.statusList[AppConst.waiting], + context, + ), + style: AppTextStyles.status.copyWith(color: ThemeColors.warning), + ), + ); + } + case AppConst.shipped: + { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + decoration: const BoxDecoration( + borderRadius: AppUtils.kBorderRadius24, + color: ThemeColors.green173Light, + ), + child: Text( + Functions.getTranslatedItem( + AppConst.statusList[AppConst.shipped], + context, + ), + style: AppTextStyles.status, + ), + ); + } + case AppConst.leftChine: + { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + decoration: const BoxDecoration( + borderRadius: AppUtils.kBorderRadius24, + color: ThemeColors.green191Light, + ), + child: Text( + Functions.getTranslatedItem( + AppConst.statusList[AppConst.leftChine], + context, + ), + style: AppTextStyles.status.copyWith( + color: ThemeColors.green191text, + ), + ), + ); + } + default: + { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + decoration: const BoxDecoration( + borderRadius: AppUtils.kBorderRadius24, + color: ThemeColors.green173Light, + ), + child: Text( + Functions.getTranslatedItem( + AppConst.statusList[status], + context, + ), + style: AppTextStyles.status, + ), + ); + } + } + } +} diff --git a/lib/features/initial/pages/initial_page.dart b/lib/features/initial/pages/initial_page.dart new file mode 100644 index 0000000..78b2bb0 --- /dev/null +++ b/lib/features/initial/pages/initial_page.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_native_splash/flutter_native_splash.dart'; +import '../../../core/local_source/local_source.dart'; +import '../../../injector_container.dart'; +import '../../../router/name_routes.dart'; +import '../../../service/notification_service.dart'; + +class InitialPage extends StatefulWidget { + const InitialPage({super.key}); + + @override + State createState() => _InitialPageState(); +} + +class _InitialPageState extends State { + @override + void initState() { + _redirect(); + super.initState(); + } + + void _redirect() { + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (context.mounted) { + if (sl().getHasProfile()) { + Navigator.of(context).pushReplacementNamed(Routes.main); + } else { + Navigator.of(context).pushReplacementNamed(Routes.language); + } + await NotificationService.clearNotificationBadge(); + FlutterNativeSplash.remove(); + } + }); + } + + @override + Widget build(BuildContext context) { + return const SizedBox(); + } +} diff --git a/lib/features/language/presentation/argument/language_argument.dart b/lib/features/language/presentation/argument/language_argument.dart new file mode 100644 index 0000000..827670d --- /dev/null +++ b/lib/features/language/presentation/argument/language_argument.dart @@ -0,0 +1,5 @@ +class LanguageArgument{ + final bool fromProfile; + + LanguageArgument({required this.fromProfile}); +} \ No newline at end of file diff --git a/lib/features/language/presentation/pages/language/app_language_page.dart b/lib/features/language/presentation/pages/language/app_language_page.dart new file mode 100644 index 0000000..a5d1472 --- /dev/null +++ b/lib/features/language/presentation/pages/language/app_language_page.dart @@ -0,0 +1,92 @@ +import 'package:cargocalculaterapp/core/theme/app_text_styles.dart'; +import 'package:cargocalculaterapp/core/theme/colors/app_colors.dart'; +import 'package:cargocalculaterapp/features/language/presentation/pages/language/widgets/labguage_button_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import '../../../../../core/app_bloc/app_bloc.dart'; +import '../../../../../core/utils/app_utils.dart'; +import '../../../../../generated/l10n.dart'; +import '../../../../../router/name_routes.dart'; +import '../../argument/language_argument.dart'; + +class AppLanguagePage extends StatefulWidget { + const AppLanguagePage({super.key, required this.languageArgument}); + + final LanguageArgument? languageArgument; + + @override + State createState() => _AppLanguagePageState(); +} + +class _AppLanguagePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: LightThemeColors.primaryColor, + body: SafeArea( + minimum: AppUtils.kPaddingL16R16T16B24, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AppUtils.kBoxHeight36, + SvgPicture.asset( + "assets/svg/ic_logo_lang.svg", + height: 200, + colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), + ), + const Spacer(), + Text( + AppLocalization.current.select_language, + style: AppTextStyles.language.copyWith(color: Colors.white), + ), + AppUtils.kBoxHeight24, + Row( + children: [ + LanguageButtonWidget( + name: "O’zbek", + onTap: () { + context.read().add(const AppChangeLocale("uz")); + if (widget.languageArgument?.fromProfile ?? false) { + Navigator.pop(context); + } else { + Navigator.of(context).pushReplacementNamed(Routes.auth); + } + }, + image: "assets/png/ic_uz.png", + ), + AppUtils.kBoxWidth8, + LanguageButtonWidget( + name: "Pусский", + onTap: () { + context.read().add(const AppChangeLocale("ru")); + if (widget.languageArgument?.fromProfile ?? false) { + Navigator.pop(context); + } else { + Navigator.of(context).pushReplacementNamed(Routes.auth); + } + }, + image: "assets/png/ic_ru.png", + ), + AppUtils.kBoxWidth8, + LanguageButtonWidget( + name: "Chinese", + onTap: () { + context.read().add(const AppChangeLocale("zh")); + if (widget.languageArgument?.fromProfile ?? false) { + Navigator.pop(context); + } else { + Navigator.of(context).pushReplacementNamed(Routes.auth); + } + }, + image: "assets/png/ic_china.jpg", + ), + ], + ), + AppUtils.kBoxHeight24, + ], + ), + ), + ); + } +} diff --git a/lib/features/language/presentation/pages/language/widgets/labguage_button_widget.dart b/lib/features/language/presentation/pages/language/widgets/labguage_button_widget.dart new file mode 100644 index 0000000..27cd519 --- /dev/null +++ b/lib/features/language/presentation/pages/language/widgets/labguage_button_widget.dart @@ -0,0 +1,48 @@ +import 'package:cargocalculaterapp/core/theme/colors/app_colors.dart'; +import 'package:flutter/material.dart'; +import '../../../../../../core/theme/app_text_styles.dart'; +import '../../../../../../core/utils/app_utils.dart'; + +class LanguageButtonWidget extends StatelessWidget { + const LanguageButtonWidget({ + super.key, + + required this.name, + required this.onTap, + required this.image, + }); + + final String image; + final String name; + final Function() onTap; + + @override + Widget build(BuildContext context) { + return Expanded( + child: InkWell( + borderRadius: AppUtils.kBorderRadius16, + onTap: onTap, + child: Ink( + padding: AppUtils.kPaddingVer16, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius12, + color: LightThemeColors.white20Transparent, + border: Border.all(color: LightThemeColors.white40Transparent), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + maxRadius: 10, + minRadius: 10, + child: Image.asset(image, width: 20, height: 20), + ), + AppUtils.kBoxWidth8, + Text(name, style: AppTextStyles.languageBig), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/main/presentation/bloc/main_bloc.dart b/lib/features/main/presentation/bloc/main_bloc.dart new file mode 100644 index 0000000..90d3f5f --- /dev/null +++ b/lib/features/main/presentation/bloc/main_bloc.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'main_event.dart'; + +part 'main_state.dart'; + +class MainBloc extends Bloc { + MainBloc() : super(const MainState(tab: MainTab.home)) { + on(_changeTab); + } + + void _changeTab(ChangeTabEvent event, Emitter emit) { + // if (state.tab != event.tab) { + // changeTap(event.tab); + // } + emit(state.copyWith(tab: event.tab)); + } + + // void changeTap(MainTab tab) { + // switch (tab) { + // case MainTab.home: + // Navigator.of( + // shellRootNavigatorKey.currentContext!, + // ).pushNamedAndRemoveUntil(Routes.home, (route) => false); + // break; + // case MainTab.calculation: + // Navigator.of( + // shellRootNavigatorKey.currentContext!, + // ).pushNamedAndRemoveUntil(Routes.calculation, (route) => false); + // break; + // case MainTab.profile: + // Navigator.of( + // shellRootNavigatorKey.currentContext!, + // ).pushNamedAndRemoveUntil(Routes.profile, (route) => false); + // break; + // } + // } +} + +enum MainTab { home, calculation, profile } diff --git a/lib/features/main/presentation/bloc/main_event.dart b/lib/features/main/presentation/bloc/main_event.dart new file mode 100644 index 0000000..9130f33 --- /dev/null +++ b/lib/features/main/presentation/bloc/main_event.dart @@ -0,0 +1,14 @@ +part of 'main_bloc.dart'; + +sealed class MainEvent extends Equatable { + const MainEvent(); +} + +final class ChangeTabEvent extends MainEvent { + final MainTab tab; + + const ChangeTabEvent({required this.tab}); + + @override + List get props => [tab]; +} diff --git a/lib/features/main/presentation/bloc/main_state.dart b/lib/features/main/presentation/bloc/main_state.dart new file mode 100644 index 0000000..3af2383 --- /dev/null +++ b/lib/features/main/presentation/bloc/main_state.dart @@ -0,0 +1,18 @@ +part of 'main_bloc.dart'; + +class MainState extends Equatable { + final MainTab tab; + + const MainState({ + required this.tab, + }); + + MainState copyWith({ + MainTab? tab, + }) { + return MainState(tab: tab ?? this.tab); + } + + @override + List get props => [tab]; +} diff --git a/lib/features/main/presentation/pages/main_page.dart b/lib/features/main/presentation/pages/main_page.dart new file mode 100644 index 0000000..6d9aa82 --- /dev/null +++ b/lib/features/main/presentation/pages/main_page.dart @@ -0,0 +1,123 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/features/calculator/presentation/bloc/calculator_bloc.dart'; +import 'package:cargocalculaterapp/features/calculator/presentation/pages/calculator_page.dart'; +import 'package:cargocalculaterapp/features/home/presentation/bloc/home_bloc.dart'; +import 'package:cargocalculaterapp/features/profile/presentation/bloc/profile_bloc.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import '../../../../injector_container.dart'; +import '../../../home/presentation/pages/home_page.dart'; +import '../../../profile/presentation/pages/profile_page.dart'; +import '../bloc/main_bloc.dart'; + +class MainPage extends StatefulWidget { + const MainPage({super.key}); + + @override + State createState() => _MainPageState(); +} + +class _MainPageState extends State { + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider(create: (_) => sl()), + BlocProvider(create: (_) => sl()), + BlocProvider(create: (_) => sl()), + BlocProvider(create: (_) => sl()), + ], + child: BlocBuilder( + builder: (context, state) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (value, result) { + // if (Navigator.canPop(shellRootNavigatorKey.currentContext!)) { + // Navigator.pop(shellRootNavigatorKey.currentContext!); + // } else + if (state.tab.index != 0) { + context.read().add( + ChangeTabEvent(tab: MainTab.values[0]), + ); + } else { + SystemNavigator.pop(); + } + }, + child: Scaffold( + body: IndexedStack( + index: state.tab.index, + children: const [HomePage(), CalculatorPage(), ProfilePage()], + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: state.tab.index, + onTap: (index) { + context.read().add( + ChangeTabEvent(tab: MainTab.values.elementAt(index)), + ); + if (index == 0) { + context.read().add(const RefreshEvent()); + } + }, + elevation: 1, + iconSize: 24, + selectedLabelStyle: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: context.color.primaryColor, + ), + unselectedLabelStyle: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: context.color.lightSecondary, + ), + showSelectedLabels: true, + showUnselectedLabels: true, + items: [ + BottomNavigationBarItem( + icon: SvgPicture.asset( + "assets/svg/ic_box.svg", + colorFilter: ColorFilter.mode( + state.tab.index == 0 + ? context.color.primaryColor + : context.color.lightSecondary, + BlendMode.srcIn, + ), + ), + label: AppLocalization.current.orders, + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + "assets/svg/ic_calculator.svg", + colorFilter: ColorFilter.mode( + state.tab.index == 1 + ? context.color.primaryColor + : context.color.lightSecondary, + BlendMode.srcIn, + ), + ), + label: AppLocalization.current.calculator, + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + "assets/svg/ic_profile.svg", + colorFilter: ColorFilter.mode( + state.tab.index == 2 + ? context.color.primaryColor + : context.color.lightSecondary, + BlendMode.srcIn, + ), + ), + label: AppLocalization.current.profile, + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/features/profile/data/data_source/local/profile_local_data_source.dart b/lib/features/profile/data/data_source/local/profile_local_data_source.dart new file mode 100644 index 0000000..2218136 --- /dev/null +++ b/lib/features/profile/data/data_source/local/profile_local_data_source.dart @@ -0,0 +1,7 @@ +import 'package:cargocalculaterapp/features/profile/data/models/profile_response.dart'; + +abstract class ProfileLocalDataSource { + Future getProfile(); + + Future setProfile(ProfileResponse response); +} diff --git a/lib/features/profile/data/data_source/local/profile_local_data_source_impl.dart b/lib/features/profile/data/data_source/local/profile_local_data_source_impl.dart new file mode 100644 index 0000000..c6a0008 --- /dev/null +++ b/lib/features/profile/data/data_source/local/profile_local_data_source_impl.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +import 'package:cargocalculaterapp/features/profile/data/data_source/local/profile_local_data_source.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_response.dart'; +import 'package:hive/hive.dart'; +import '../../../../../constants/constants.dart'; +import '../../../../../core/error/exceptions.dart'; + +class ProfileLocalDataSourceImpl extends ProfileLocalDataSource { + final Box box; + + ProfileLocalDataSourceImpl(this.box); + + @override + Future getProfile() async { + final json = await box.get(CacheKeys.profile, defaultValue: null); + if (json != null) { + return ProfileResponse.fromJson(jsonDecode(json)); + } else { + throw CacheException(message: Validations.someThingWentWrong); + } + } + + @override + Future setProfile(ProfileResponse response) async { + final json = jsonEncode(response.toJson()); + await box.put(CacheKeys.profile, json); + } +} diff --git a/lib/features/profile/data/data_source/remote/profile_remote_data_source.dart b/lib/features/profile/data/data_source/remote/profile_remote_data_source.dart new file mode 100644 index 0000000..20dc72e --- /dev/null +++ b/lib/features/profile/data/data_source/remote/profile_remote_data_source.dart @@ -0,0 +1,11 @@ +import 'package:cargocalculaterapp/features/profile/data/models/delete_account_response.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_response.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_update_request.dart'; + +abstract class ProfileRemoteDataSource { + Future getProfile(); + + Future updateProfile(ProfileUpdateRequest request); + + Future deleteProfile(); +} diff --git a/lib/features/profile/data/data_source/remote/profile_remote_data_source_impl.dart b/lib/features/profile/data/data_source/remote/profile_remote_data_source_impl.dart new file mode 100644 index 0000000..a6bbc73 --- /dev/null +++ b/lib/features/profile/data/data_source/remote/profile_remote_data_source_impl.dart @@ -0,0 +1,80 @@ +import 'package:cargocalculaterapp/core/local_source/local_source.dart'; +import 'package:cargocalculaterapp/features/profile/data/data_source/remote/profile_remote_data_source.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/delete_account_response.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_response.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_update_request.dart'; +import 'package:dio/dio.dart'; + +import '../../../../../constants/constants.dart'; +import '../../../../../core/error/exceptions.dart'; +import '../../../../../injector_container.dart'; + +class ProfileRemoteDataSourceImpl extends ProfileRemoteDataSource { + final Dio dio; + + ProfileRemoteDataSourceImpl(this.dio); + + @override + Future getProfile() async { + try { + final Response response = await dio.get( + Constants.baseUrl + Urls.getProfile, + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return ProfileResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future updateProfile(ProfileUpdateRequest request) async { + try { + final Response response = await dio.put( + Constants.baseUrl + Urls.updateProfile, + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + data: request, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return ProfileResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } + + @override + Future deleteProfile() async { + try { + final Response response = await dio.delete( + Constants.baseUrl + Urls.delete, + options: DioConstants.options + ..headers = { + "Authorization": "Bearer ${sl().getAccessToken()}", + }, + ); + if (response.statusCode == 200 || response.statusCode == 201) { + return DeleteAccountResponse.fromJson(response.data); + } + throw ServerException.fromJson(response.data); + } on DioException catch (e) { + throw ServerException.fromJson(e.response?.data); + } on FormatException { + throw ServerException(message: Validations.someThingWentWrong); + } + } +} diff --git a/lib/features/profile/data/models/delete_account_response.dart b/lib/features/profile/data/models/delete_account_response.dart new file mode 100644 index 0000000..91e55cb --- /dev/null +++ b/lib/features/profile/data/models/delete_account_response.dart @@ -0,0 +1,16 @@ +class DeleteAccountResponse { + DeleteAccountResponse({ + this.message,}); + + DeleteAccountResponse.fromJson(dynamic json) { + message = json['message']; + } + String? message; + + Map toJson() { + final map = {}; + map['message'] = message; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/profile/data/models/profile_response.dart b/lib/features/profile/data/models/profile_response.dart new file mode 100644 index 0000000..aa21ce1 --- /dev/null +++ b/lib/features/profile/data/models/profile_response.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; + +class ProfileResponse extends Equatable { + const ProfileResponse({ + this.fullname, + this.phoneNumber, + this.email, + this.ucode, + }); + + factory ProfileResponse.fromJson(dynamic json) { + return ProfileResponse( + fullname: json['fullname'], + phoneNumber: json['phone_number'], + email: json['email'], + ucode: json['ucode'], + ); + } + + final String? fullname; + final String? phoneNumber; + final String? email; + final String? ucode; + + Map toJson() { + final map = {}; + map['fullname'] = fullname; + map['phone_number'] = phoneNumber; + map['email'] = email; + map['ucode'] = ucode; + return map; + } + + @override + List get props => [fullname, phoneNumber, email, ucode]; +} diff --git a/lib/features/profile/data/models/profile_update_request.dart b/lib/features/profile/data/models/profile_update_request.dart new file mode 100644 index 0000000..2b9cf8f --- /dev/null +++ b/lib/features/profile/data/models/profile_update_request.dart @@ -0,0 +1,24 @@ +class ProfileUpdateRequest { + ProfileUpdateRequest({ + this.fullname, + this.phoneNumber, + this.email,}); + + ProfileUpdateRequest.fromJson(dynamic json) { + fullname = json['fullname']; + phoneNumber = json['phone_number']; + email = json['email']; + } + String? fullname; + String? phoneNumber; + String? email; + + Map toJson() { + final map = {}; + map['fullname'] = fullname; + map['phone_number'] = phoneNumber; + map['email'] = email; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/profile/data/repository/profile_repository_impl.dart b/lib/features/profile/data/repository/profile_repository_impl.dart new file mode 100644 index 0000000..6d220b1 --- /dev/null +++ b/lib/features/profile/data/repository/profile_repository_impl.dart @@ -0,0 +1,66 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/delete_account_response.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_update_request.dart'; +import 'package:cargocalculaterapp/features/profile/domain/repository/profile_repository.dart'; +import 'package:dartz/dartz.dart'; +import '../../../../constants/constants.dart'; +import '../../../../core/error/exceptions.dart'; +import '../data_source/local/profile_local_data_source.dart'; +import '../data_source/remote/profile_remote_data_source.dart'; +import '../models/profile_response.dart'; + +class ProfileRepositoryImpl extends ProfileRepository { + final ProfileRemoteDataSource remoteDataSource; + final ProfileLocalDataSource localDataSource; + + ProfileRepositoryImpl(this.remoteDataSource, this.localDataSource); + + @override + Future> getProfile(bool isCache) async { + if (!isCache) { + try { + final response = await remoteDataSource.getProfile(); + localDataSource.setProfile(response); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } else { + try { + final response = await localDataSource.getProfile(); + return Right(response); + } catch (e) { + return Left( + CacheFailure( + message: (e is CacheException) + ? e.message + : Warnings.someThingWentWrong, + ), + ); + } + } + } + + @override + Future> updateProfile( + ProfileUpdateRequest request, + ) async { + try { + final response = await remoteDataSource.updateProfile(request); + localDataSource.setProfile(response); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } + + @override + Future> deleteProfile() async { + try { + final response = await remoteDataSource.deleteProfile(); + return Right(response); + } catch (e) { + return Left(ServerFailure(message: e.toString())); + } + } +} diff --git a/lib/features/profile/domain/repository/profile_repository.dart b/lib/features/profile/domain/repository/profile_repository.dart new file mode 100644 index 0000000..7ae5270 --- /dev/null +++ b/lib/features/profile/domain/repository/profile_repository.dart @@ -0,0 +1,15 @@ +import 'package:cargocalculaterapp/features/profile/data/models/profile_response.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_update_request.dart'; +import 'package:dartz/dartz.dart'; +import '../../../../core/error/failure.dart'; +import '../../data/models/delete_account_response.dart'; + +abstract class ProfileRepository { + Future> getProfile(bool isCache); + + Future> updateProfile( + ProfileUpdateRequest request, + ); + + Future> deleteProfile(); +} diff --git a/lib/features/profile/domain/usecase/delete_profile_usecase.dart b/lib/features/profile/domain/usecase/delete_profile_usecase.dart new file mode 100644 index 0000000..af60b31 --- /dev/null +++ b/lib/features/profile/domain/usecase/delete_profile_usecase.dart @@ -0,0 +1,17 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/delete_account_response.dart'; +import 'package:dartz/dartz.dart'; + +import '../../../../core/usecase/usecase.dart'; +import '../repository/profile_repository.dart'; + +class DeleteProfileUseCase extends UseCase { + final ProfileRepository repository; + + DeleteProfileUseCase(this.repository); + + @override + Future> call(NoParams params) async { + return await repository.deleteProfile(); + } +} diff --git a/lib/features/profile/domain/usecase/profile_update_usecase.dart b/lib/features/profile/domain/usecase/profile_update_usecase.dart new file mode 100644 index 0000000..a444baf --- /dev/null +++ b/lib/features/profile/domain/usecase/profile_update_usecase.dart @@ -0,0 +1,22 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_update_request.dart'; +import 'package:dartz/dartz.dart'; + +import '../../data/models/profile_response.dart'; +import '../repository/profile_repository.dart'; + +class ProfileUpdateUseCase + extends UseCase { + final ProfileRepository repository; + + ProfileUpdateUseCase(this.repository); + + @override + Future> call( + ProfileUpdateRequest params, + ) async { + final response = await repository.updateProfile(params); + return response; + } +} diff --git a/lib/features/profile/domain/usecase/profile_usecase.dart b/lib/features/profile/domain/usecase/profile_usecase.dart new file mode 100644 index 0000000..2facba3 --- /dev/null +++ b/lib/features/profile/domain/usecase/profile_usecase.dart @@ -0,0 +1,17 @@ +import 'package:cargocalculaterapp/core/error/failure.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/features/profile/data/models/profile_response.dart'; +import 'package:dartz/dartz.dart'; +import '../repository/profile_repository.dart'; + +class ProfileUseCase extends UseCase { + final ProfileRepository repository; + + ProfileUseCase(this.repository); + + @override + Future> call(bool params) async { + final response = await repository.getProfile(params); + return response; + } +} diff --git a/lib/features/profile/presentation/bloc/profile_bloc.dart b/lib/features/profile/presentation/bloc/profile_bloc.dart new file mode 100644 index 0000000..29982c6 --- /dev/null +++ b/lib/features/profile/presentation/bloc/profile_bloc.dart @@ -0,0 +1,118 @@ +import 'package:cargocalculaterapp/core/local_source/local_source.dart'; +import 'package:cargocalculaterapp/core/usecase/usecase.dart'; +import 'package:cargocalculaterapp/router/app_routes.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../constants/constants.dart'; +import '../../../../injector_container.dart'; +import '../../../../router/name_routes.dart'; +import '../../data/models/profile_response.dart'; +import '../../data/models/profile_update_request.dart'; +import '../../domain/usecase/delete_profile_usecase.dart'; +import '../../domain/usecase/profile_update_usecase.dart'; +import '../../domain/usecase/profile_usecase.dart'; + +part 'profile_event.dart'; + +part 'profile_state.dart'; + +class ProfileBloc extends Bloc { + ProfileBloc( + this.profileUseCase, + this.profileUpdateUseCase, + this.deleteProfileUseCase, + ) : super(const ProfileState(readOnly: true, isLoading: false)) { + on(_getProfileData); + on(_editProfile); + on(_updateUserInfo); + on(_deleteProfile); + } + + final ProfileUseCase profileUseCase; + final ProfileUpdateUseCase profileUpdateUseCase; + final DeleteProfileUseCase deleteProfileUseCase; + + Future _getProfileData( + GetProfileDataEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true)); + final cacheResponse = await profileUseCase(true); + cacheResponse.fold((l) {}, (r) { + emit(state.copyWith(isLoading: false, profileData: r)); + }); + final response = await profileUseCase(false); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + }, + (r) { + sl().setUCode(r.ucode ?? ""); + emit(state.copyWith(isLoading: false, profileData: r)); + }, + ); + } + + void _editProfile(EditProfileEvent event, Emitter emit) { + emit(state.copyWith(readOnly: false)); + } + + Future _updateUserInfo( + UpdateProfileEvent event, + Emitter emit, + ) async { + bool hasError = false; + if (event.fullName.isEmpty) { + hasError = true; + } else if ((event.phone.isNotEmpty) && + !RegExConst.phoneRegex.hasMatch(event.phone)) { + hasError = true; + } else if ((event.email.isNotEmpty) && + !RegExConst.emailRegex.hasMatch(event.email)) { + hasError = true; + } + if (hasError) { + emit(state.copyWith(hasError: true)); + return; + } else { + emit(state.copyWith(hasError: false, isLoading: true)); + } + final response = await profileUpdateUseCase( + ProfileUpdateRequest( + email: event.email, + phoneNumber: event.phone.replaceAll("+", ""), + fullname: event.fullName, + ), + ); + response.fold( + (l) { + emit(state.copyWith(isLoading: false)); + }, + (r) { + emit(state.copyWith(isLoading: false, readOnly: true)); + }, + ); + } + + Future _deleteProfile( + DeleteProfileEvent event, + Emitter emit, + ) async { + final response = await deleteProfileUseCase(const NoParams()); + await response.fold((l) {}, (r) async { + final language = sl().getLocale(); + await sl().clear().then((value) { + sl().setLocale(language); + sl().setIsFirstEnter(false); + if (rootNavigatorKey.currentContext?.mounted ?? false) { + Navigator.pushNamedAndRemoveUntil( + rootNavigatorKey.currentContext!, + Routes.auth, + (route) => false, + ); + } + }); + }); + } +} diff --git a/lib/features/profile/presentation/bloc/profile_event.dart b/lib/features/profile/presentation/bloc/profile_event.dart new file mode 100644 index 0000000..efa57dc --- /dev/null +++ b/lib/features/profile/presentation/bloc/profile_event.dart @@ -0,0 +1,41 @@ +part of 'profile_bloc.dart'; + +sealed class ProfileEvent extends Equatable { + const ProfileEvent(); +} + +final class GetProfileDataEvent extends ProfileEvent { + const GetProfileDataEvent(); + + @override + List get props => []; +} + +final class EditProfileEvent extends ProfileEvent { + const EditProfileEvent(); + + @override + List get props => []; +} + +final class UpdateProfileEvent extends ProfileEvent { + const UpdateProfileEvent({ + required this.email, + required this.fullName, + required this.phone, + }); + + final String email; + final String fullName; + final String phone; + + @override + List get props => [email, fullName, phone]; +} + +final class DeleteProfileEvent extends ProfileEvent { + const DeleteProfileEvent(); + + @override + List get props => []; +} diff --git a/lib/features/profile/presentation/bloc/profile_state.dart b/lib/features/profile/presentation/bloc/profile_state.dart new file mode 100644 index 0000000..caad465 --- /dev/null +++ b/lib/features/profile/presentation/bloc/profile_state.dart @@ -0,0 +1,32 @@ +part of 'profile_bloc.dart'; + +class ProfileState extends Equatable { + const ProfileState({ + required this.isLoading, + required this.readOnly, + this.profileData, + this.hasError, + }); + + final bool isLoading; + final bool readOnly; + final ProfileResponse? profileData; + final bool? hasError; + + ProfileState copyWith({ + bool? isLoading, + bool? readOnly, + ProfileResponse? profileData, + bool? hasError, + }) { + return ProfileState( + isLoading: isLoading ?? this.isLoading, + readOnly: readOnly ?? this.readOnly, + profileData: profileData ?? this.profileData, + hasError: hasError ?? this.hasError, + ); + } + + @override + List get props => [isLoading, readOnly, profileData, hasError]; +} diff --git a/lib/features/profile/presentation/mixins/profile_mixin.dart b/lib/features/profile/presentation/mixins/profile_mixin.dart new file mode 100644 index 0000000..b0bbfc6 --- /dev/null +++ b/lib/features/profile/presentation/mixins/profile_mixin.dart @@ -0,0 +1,19 @@ +import 'package:flutter/cupertino.dart'; + +mixin ProfileMixin { + late TextEditingController fullNameController; + late TextEditingController phoneController; + late TextEditingController emailController; + + void initControllers() { + fullNameController = TextEditingController(); + phoneController = TextEditingController(); + emailController = TextEditingController(); + } + + void disposeControllers() { + fullNameController.dispose(); + phoneController.dispose(); + emailController.dispose(); + } +} diff --git a/lib/features/profile/presentation/pages/dialog/delete_profile_dialog.dart b/lib/features/profile/presentation/pages/dialog/delete_profile_dialog.dart new file mode 100644 index 0000000..2a1ce47 --- /dev/null +++ b/lib/features/profile/presentation/pages/dialog/delete_profile_dialog.dart @@ -0,0 +1,87 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/theme/colors/app_colors.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../../core/utils/app_utils.dart'; + +class DeleteProfileDialog extends StatelessWidget { + const DeleteProfileDialog({super.key}); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: context.color.scaffoldBackgroundColor, + insetPadding: AppUtils.kPaddingAll16, + child: Padding( + padding: const EdgeInsets.only( + top: 32, + left: 16, + right: 16, + bottom: 24, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + "assets/svg/ic_warning.svg", + height: 48, + width: 48, + ), + AppUtils.kBoxHeight16, + Text( + AppLocalization.current.delete_account_desc, + style: context.text.secondaryText14, + textAlign: TextAlign.center, + ), + AppUtils.kBoxHeight16, + Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.pop(context); + }, + borderRadius: AppUtils.kBorderRadius24, + child: Ink( + decoration: BoxDecoration( + color: context.color.scaffoldBackgroundColor, + borderRadius: AppUtils.kBorderRadius24, + border: Border.all(color: context.color.primaryColor), + ), + height: 56, + padding: AppUtils.kPaddingHor16, + child: Center( + child: Text( + AppLocalization.current.cancel, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: context.color.textColor, + ), + ), + ), + ), + ), + ), + AppUtils.kBoxWidth16, + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: LightThemeColors.errorColor, + ), + onPressed: () { + Navigator.pop(context, true); + }, + child: Text(AppLocalization.current.delete), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/profile/presentation/pages/dialog/language_dialog.dart b/lib/features/profile/presentation/pages/dialog/language_dialog.dart new file mode 100644 index 0000000..b438dc6 --- /dev/null +++ b/lib/features/profile/presentation/pages/dialog/language_dialog.dart @@ -0,0 +1,96 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../core/app_bloc/app_bloc.dart'; + +class LanguageDialog extends StatelessWidget { + const LanguageDialog({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: context.color.scaffoldBackgroundColor, + borderRadius: AppUtils.kBorderRadiusTop24, + ), + child: SafeArea( + minimum: AppUtils.kPaddingL16R16T16B24, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalization.current.change_language, + style: context.text.orderTitle, + ), + AppUtils.kBoxHeight32, + InkWell( + onTap: () { + context.read().add(const AppChangeLocale("uz")); + Navigator.pop(context); + }, + child: Ink( + child: Row( + children: [ + const Image( + width: 20, + height: 20, + image: AssetImage("assets/png/ic_uz.png"), + ), + AppUtils.kBoxWidth16, + Text("O'zbek", style: context.text.profileCategory), + ], + ), + ), + ), + AppUtils.kBoxHeight32, + InkWell( + onTap: () { + context.read().add(const AppChangeLocale("ru")); + Navigator.pop(context); + }, + child: Ink( + child: Row( + children: [ + const Image( + width: 20, + height: 20, + image: AssetImage("assets/png/ic_ru.png"), + ), + AppUtils.kBoxWidth16, + Text("Русский", style: context.text.profileCategory), + ], + ), + ), + ), + AppUtils.kBoxHeight32, + InkWell( + onTap: () { + context.read().add(const AppChangeLocale("zh")); + Navigator.pop(context); + }, + child: Ink( + child: Row( + children: [ + const Image( + width: 20, + height: 20, + image: AssetImage("assets/png/ic_china.png"), + ), + AppUtils.kBoxWidth16, + Text("Chinese", style: context.text.profileCategory), + ], + ), + ), + ), + AppUtils.kBoxHeight16, + ], + ), + ), + ); + } +} diff --git a/lib/features/profile/presentation/pages/profile_page.dart b/lib/features/profile/presentation/pages/profile_page.dart new file mode 100644 index 0000000..1ffe817 --- /dev/null +++ b/lib/features/profile/presentation/pages/profile_page.dart @@ -0,0 +1,486 @@ +import 'dart:io'; +import 'package:cargocalculaterapp/core/app_bloc/app_bloc.dart'; +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:cargocalculaterapp/core/theme/app_text_styles.dart'; +import 'package:cargocalculaterapp/core/theme/colors/app_colors.dart'; +import 'package:cargocalculaterapp/core/theme/theme_data.dart'; +import 'package:cargocalculaterapp/core/utils/app_utils.dart'; +import 'package:cargocalculaterapp/core/widgets/text_filds/custom_text_field_name.dart'; +import 'package:cargocalculaterapp/features/profile/presentation/pages/widgets/user_info_widget.dart'; +import 'package:cargocalculaterapp/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import '../../../../constants/constants.dart'; +import '../../../../core/local_source/local_source.dart'; +import '../../../../core/widgets/loading/progress_hud.dart'; +import '../../../../injector_container.dart'; +import '../../../../router/name_routes.dart'; +import '../bloc/profile_bloc.dart'; +import '../mixins/profile_mixin.dart'; +import 'dialog/delete_profile_dialog.dart'; +import 'dialog/language_dialog.dart'; + +class ProfilePage extends StatefulWidget { + const ProfilePage({super.key}); + + @override + State createState() => _ProfilePageState(); +} + +class _ProfilePageState extends State with ProfileMixin { + @override + void initState() { + initControllers(); + context.read().add(const GetProfileDataEvent()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listenWhen: (previous, current) => + previous.profileData != current.profileData, + listener: (context, state) { + fullNameController.text = state.profileData?.fullname ?? ""; + emailController.text = state.profileData?.email ?? ""; + phoneController.text = state.profileData?.phoneNumber ?? ""; + }, + builder: (context, state) { + return ModalProgressHUD( + inAsyncCall: state.isLoading, + child: Scaffold( + appBar: AppBar( + elevation: 0.5, + automaticallyImplyLeading: false, + title: Text(AppLocalization.current.profile), + ), + body: ListView( + padding: AppUtils.kPaddingAll16, + children: [ + state.readOnly + ? Container( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 16, + ), + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.statusBackground, + border: Border.all(color: context.color.lightBorder), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + AppLocalization.current.personal_info, + style: context.text.orderTitle, + ), + ), + if (state.readOnly) + GestureDetector( + onTap: () { + context.read().add( + const EditProfileEvent(), + ); + }, + child: SvgPicture.asset( + "assets/svg/ic_edit.svg", + ), + ), + ], + ), + AppUtils.kBoxHeight16, + UserInfoWidget( + title: AppLocalization.current.full_name, + name: state.profileData?.fullname ?? "-", + ), + UserInfoWidget( + title: AppLocalization.current.phone_number, + name: state.profileData?.phoneNumber ?? "-", + ), + UserInfoWidget( + title: AppLocalization.current.email, + name: state.profileData?.email ?? "-", + ), + ], + ), + ) + : Container( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 16, + ), + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.statusBackground, + border: Border.all(color: context.color.lightBorder), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalization.current.personal_info, + style: context.text.orderTitle, + ), + AppUtils.kBoxHeight16, + CustomTextFieldName( + hint: AppLocalization.current.full_name_hint, + name: AppLocalization.current.full_name, + controller: fullNameController, + inputType: TextInputType.name, + errorText: + AppLocalization.current.error_full_name, + isError: + (state.hasError ?? false) && + (fullNameController.text.isNotEmpty), + ), + AppUtils.kBoxHeight16, + CustomTextFieldName( + hint: AppLocalization.current.phone_number_text, + name: AppLocalization.current.phone_number, + controller: phoneController, + inputType: TextInputType.phone, + errorText: AppLocalization.current.error_in_phone, + isError: + (state.hasError ?? false) && + !RegExConst.phoneRegex.hasMatch( + phoneController.text, + ), + ), + AppUtils.kBoxHeight16, + CustomTextFieldName( + hint: AppLocalization.current.email_address, + name: AppLocalization.current.email, + controller: emailController, + inputType: TextInputType.emailAddress, + errorText: AppLocalization.current.error_email, + isError: + (state.hasError ?? false) && + !RegExConst.emailRegex.hasMatch( + emailController.text, + ), + ), + AppUtils.kBoxHeight16, + ElevatedButton( + onPressed: () { + context.read().add( + UpdateProfileEvent( + fullName: fullNameController.text, + email: emailController.text, + phone: phoneController.text, + ), + ); + }, + child: Text(AppLocalization.current.save), + ), + AppUtils.kBoxHeight16, + ], + ), + ), + AppUtils.kBoxHeight16, + Material( + color: Colors.transparent, + child: InkWell( + borderRadius: AppUtils.kBorderRadius16, + onTap: () { + Clipboard.setData( + ClipboardData(text: sl().getUCode()), + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.current.text_copied), + ), + ); + }, + child: Ink( + padding: AppUtils.kPaddingHor16, + height: 56, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.scaffoldBackgroundColor, + border: Border.all(color: context.color.borderColor), + ), + child: Row( + children: [ + Icon( + Icons.numbers_outlined, + size: 24, + color: context.color.textColor, + ), + AppUtils.kBoxWidth8, + Text( + AppLocalization.current.user_id, + style: context.text.profileCategory, + ), + const Spacer(), + Text( + sl().getUCode(), + style: context.text.statusNumber, + ), + AppUtils.kBoxWidth8, + Icon( + Icons.copy_outlined, + color: context.color.textColor, + size: 16, + ), + ], + ), + ), + ), + ), + AppUtils.kBoxHeight16, + Material( + color: Colors.transparent, + child: InkWell( + borderRadius: AppUtils.kBorderRadius16, + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: true, + builder: (context) => const LanguageDialog(), + ); + }, + child: Ink( + padding: AppUtils.kPaddingHor16, + height: 56, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.scaffoldBackgroundColor, + border: Border.all(color: context.color.borderColor), + ), + child: Row( + children: [ + SvgPicture.asset( + "assets/svg/ic_world.svg", + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + context.color.textColor, + BlendMode.srcIn, + ), + ), + AppUtils.kBoxWidth8, + Text( + AppLocalization.current.change_language, + style: context.text.profileCategory, + ), + const Spacer(), + Text( + sl().getLocale() == "uz" + ? "O'zbek" + : sl().getLocale() == "ru" + ? "Русский" + : "Chinese", + style: context.text.statusNumber, + ), + AppUtils.kBoxWidth8, + Icon( + Icons.arrow_forward_ios_rounded, + color: context.color.textColor, + size: 16, + ), + ], + ), + ), + ), + ), + AppUtils.kBoxHeight16, + Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + if (sl().getThemeMode() == + ThemeMode.light.name) { + context.read().add( + AppThemeSwitchDark(darkTheme: darkTheme), + ); + } else { + context.read().add( + AppThemeSwitchLight(lightTheme: lightTheme), + ); + } + }, + borderRadius: AppUtils.kBorderRadius16, + child: Ink( + padding: AppUtils.kPaddingHor16, + height: 56, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius16, + color: context.color.scaffoldBackgroundColor, + border: Border.all(color: context.color.borderColor), + ), + child: Row( + children: [ + SvgPicture.asset( + "assets/svg/ic_sun.svg", + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + context.color.textColor, + BlendMode.srcIn, + ), + ), + AppUtils.kBoxWidth8, + Text( + AppLocalization.current.theme_mode, + style: context.text.profileCategory, + ), + const Spacer(), + Text( + sl().getThemeMode() == + ThemeMode.light.name + ? AppLocalization.current.light + : AppLocalization.current.dark, + style: context.text.statusNumber, + ), + AppUtils.kBoxWidth8, + AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 24, + height: 16, + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.black, width: 2), + ), + alignment: + sl().getThemeMode() == + ThemeMode.light.name + ? Alignment.centerRight + : Alignment.centerLeft, + child: Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.black, + ), + ), + ), + ], + ), + ), + ), + ), + AppUtils.kBoxHeight16, + Material( + color: Colors.transparent, + child: Padding( + padding: AppUtils.kPaddingHor24, + child: InkWell( + borderRadius: AppUtils.kBorderRadius24, + onTap: () async { + final language = sl().getLocale(); + await sl().clear().then((value) { + sl().setLocale(language); + sl().setIsFirstEnter(false); + if (context.mounted) { + Navigator.pushNamedAndRemoveUntil( + context, + Routes.auth, + (route) => false, + ); + } + }); + }, + child: Ink( + padding: AppUtils.kPaddingHor16, + height: 56, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius24, + color: context.color.scaffoldBackgroundColor, + border: Border.all(color: context.color.borderColor), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + "assets/svg/ic_log_out.svg", + width: 24, + height: 24, + colorFilter: const ColorFilter.mode( + ThemeColors.timerRed, + BlendMode.srcIn, + ), + ), + AppUtils.kBoxWidth8, + Text( + AppLocalization.current.logout, + style: AppTextStyles.saleRed, + ), + ], + ), + ), + ), + ), + ), + AppUtils.kBoxHeight24, + //if (Platform.isIOS) + SizedBox( + height: + MediaQuery.of(context).size.height - + MediaQuery.of(context).padding.top - + MediaQuery.of(context).padding.bottom - + kBottomNavigationBarHeight-600 + ), + if (Platform.isIOS) + Center( + child: TextButton.icon( + onPressed: () { + showDialog( + context: context, + builder: (context) => const DeleteProfileDialog(), + ).then((value) { + if (value is bool && context.mounted) { + context.read().add( + const DeleteProfileEvent(), + ); + } + }); + }, + label: Text( + AppLocalization.current.delete_account, + style: AppTextStyles.orderTitle, + ), + icon: SvgPicture.asset( + "assets/svg/ic_trash.svg", + width: 18, + height: 18, + colorFilter: ColorFilter.mode( + context.color.textColor, + BlendMode.srcIn, + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + + @override + void dispose() { + disposeControllers(); + super.dispose(); + } +} diff --git a/lib/features/profile/presentation/pages/widgets/profil_info_field_widget.dart b/lib/features/profile/presentation/pages/widgets/profil_info_field_widget.dart new file mode 100644 index 0000000..790a660 --- /dev/null +++ b/lib/features/profile/presentation/pages/widgets/profil_info_field_widget.dart @@ -0,0 +1,65 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:flutter/cupertino.dart'; + +class ProfileInfoFieldWidget extends StatelessWidget { + const ProfileInfoFieldWidget({ + super.key, + required this.title, + this.inputType, + this.controller, + this.readOnly = false, + this.hasError, + this.errorText, + required this.isValid, + }); + + final String title; + final TextInputType? inputType; + final TextEditingController? controller; + final bool readOnly; + final bool? hasError; + final bool isValid; + final String? errorText; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CupertinoTextFormFieldRow( + readOnly: readOnly, + cursorColor: context.color.accentColor, + prefix: SizedBox( + width: 90, + child: Text( + title, + style: context.text.profileCategory.copyWith( + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), + ), + controller: controller, + style: context.text.profileCategory, + textAlign: TextAlign.left, + keyboardType: inputType, + ), + if ((controller?.text.isNotEmpty ?? false) && + (hasError ?? false) && + (!isValid)) + Padding( + padding: const EdgeInsets.only(left: 16.0, top: 0), + child: Text( + errorText ?? "", + style: const TextStyle( + color: CupertinoColors.systemRed, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ); + } +} diff --git a/lib/features/profile/presentation/pages/widgets/user_info_widget.dart b/lib/features/profile/presentation/pages/widgets/user_info_widget.dart new file mode 100644 index 0000000..c5ee644 --- /dev/null +++ b/lib/features/profile/presentation/pages/widgets/user_info_widget.dart @@ -0,0 +1,32 @@ +import 'package:cargocalculaterapp/core/extension/build_context_extension.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../core/utils/app_utils.dart'; + +class UserInfoWidget extends StatelessWidget { + const UserInfoWidget({super.key, required this.title, required this.name}); + + final String title; + final String name; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: Text(title, style: context.text.orderListTitle)), + AppUtils.kBoxWidth8, + Expanded( + child: Text( + name, + style: context.text.profileCategory, + textAlign: TextAlign.end, + ), + ), + ], + ), + ); + } +} diff --git a/lib/generated/intl/messages_all.dart b/lib/generated/intl/messages_all.dart new file mode 100644 index 0000000..480458e --- /dev/null +++ b/lib/generated/intl/messages_all.dart @@ -0,0 +1,80 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that looks up messages for specific locales by +// delegating to the appropriate library. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:implementation_imports, file_names, unnecessary_new +// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering +// ignore_for_file:argument_type_not_assignable, invalid_assignment +// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases +// ignore_for_file:comment_references + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; +import 'package:intl/src/intl_helpers.dart'; + +import 'messages_en.dart' as messages_en; +import 'messages_ru.dart' as messages_ru; +import 'messages_uz.dart' as messages_uz; +import 'messages_zh.dart' as messages_zh; + +typedef Future LibraryLoader(); +Map _deferredLibraries = { + 'en': () => new SynchronousFuture(null), + 'ru': () => new SynchronousFuture(null), + 'uz': () => new SynchronousFuture(null), + 'zh': () => new SynchronousFuture(null), +}; + +MessageLookupByLibrary? _findExact(String localeName) { + switch (localeName) { + case 'en': + return messages_en.messages; + case 'ru': + return messages_ru.messages; + case 'uz': + return messages_uz.messages; + case 'zh': + return messages_zh.messages; + default: + return null; + } +} + +/// User programs should call this before using [localeName] for messages. +Future initializeMessages(String localeName) { + var availableLocale = Intl.verifiedLocale( + localeName, + (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null, + ); + if (availableLocale == null) { + return new SynchronousFuture(false); + } + var lib = _deferredLibraries[availableLocale]; + lib == null ? new SynchronousFuture(false) : lib(); + initializeInternalMessageLookup(() => new CompositeMessageLookup()); + messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); + return new SynchronousFuture(true); +} + +bool _messagesExistFor(String locale) { + try { + return _findExact(locale) != null; + } catch (e) { + return false; + } +} + +MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { + var actualLocale = Intl.verifiedLocale( + locale, + _messagesExistFor, + onFailure: (_) => null, + ); + if (actualLocale == null) return null; + return _findExact(actualLocale); +} diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart new file mode 100644 index 0000000..52d0383 --- /dev/null +++ b/lib/generated/intl/messages_en.dart @@ -0,0 +1,156 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a en locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'en'; + + static String m0(mail) => "${mail} mailga yuborilgan maxfiy kodni kiriting"; + + static String m1(phone) => + "${phone} raqamingizga yuborilgan maxfiy kodni kiriting"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "after": MessageLookupByLibrary.simpleMessage("so\'ng"), + "auth": MessageLookupByLibrary.simpleMessage("Kirish"), + "auth_login": MessageLookupByLibrary.simpleMessage("Tizimga kirish"), + "average_weight": MessageLookupByLibrary.simpleMessage("O\'rtacha vazn"), + "average_weight_in": MessageLookupByLibrary.simpleMessage( + "O\'rtacha vazn (kg/m³)", + ), + "calculate": MessageLookupByLibrary.simpleMessage("Hisoblash"), + "calculator": MessageLookupByLibrary.simpleMessage("Kalkulyator"), + "cancel": MessageLookupByLibrary.simpleMessage("Отмена"), + "change_language": MessageLookupByLibrary.simpleMessage( + "Tilni o\'zgartirish", + ), + "comment": MessageLookupByLibrary.simpleMessage("Izoh"), + "comment_desc": MessageLookupByLibrary.simpleMessage( + "Iltimos, o\'zaro aloqa haqida sharh qoldiring. Bu bizga xizmatni yaxshilashga yordam beradi.", + ), + "comment_text": MessageLookupByLibrary.simpleMessage("Izoh matni"), + "confirm": MessageLookupByLibrary.simpleMessage("Tasdiqlash"), + "confirm_email": MessageLookupByLibrary.simpleMessage( + "E-pochta manzilini tasdiqlash", + ), + "confirm_phone_text": MessageLookupByLibrary.simpleMessage( + "Telefon raqamini tasdiqlang", + ), + "continue_next": MessageLookupByLibrary.simpleMessage("Davom etish"), + "dark": MessageLookupByLibrary.simpleMessage("Dark"), + "delete": MessageLookupByLibrary.simpleMessage("Удалить"), + "delete_account": MessageLookupByLibrary.simpleMessage("Удалить аккаунт"), + "delete_account_desc": MessageLookupByLibrary.simpleMessage( + "Нажав кнопку «Удалить», вы удалите свою учётную запись навсегда!\nВы уверены, что хотите удалить свою учётную запись?", + ), + "delivery_price": MessageLookupByLibrary.simpleMessage( + "Yetkazib berish narxi", + ), + "delivery_time": MessageLookupByLibrary.simpleMessage( + "Yetkazib berish vaqti", + ), + "delivery_time_estimated": MessageLookupByLibrary.simpleMessage( + "Tahminiy yetkazish vaqti", + ), + "done_order": MessageLookupByLibrary.simpleMessage( + "Sizning arizangiz muvaffaqiyatli qabul qilindi! Xizmatlarimizdan foydalanishingizdan hursandmiz", + ), + "done_phone": MessageLookupByLibrary.simpleMessage( + "Xizmatimizdan foydalanishingizdan mamnunmiz! Sizga tez orada aloqaga chiqamiz!", + ), + "done_ready": MessageLookupByLibrary.simpleMessage("Tushundim!"), + "email": MessageLookupByLibrary.simpleMessage("Email"), + "email_address": MessageLookupByLibrary.simpleMessage("Elektron pochta"), + "enter_code_mail": m0, + "enter_code_phone": m1, + "enter_phone_or_mail": MessageLookupByLibrary.simpleMessage( + "Telefon raqam yoki email kiriting", + ), + "error_email": MessageLookupByLibrary.simpleMessage( + "Elektron pochta manzilida xato", + ), + "error_full_name": MessageLookupByLibrary.simpleMessage( + "To\'liq ism noto\'g\'ri", + ), + "error_in_phone": MessageLookupByLibrary.simpleMessage( + "Telefon raqamida xato", + ), + "full_name": MessageLookupByLibrary.simpleMessage("To\'liq ism"), + "full_name_hint": MessageLookupByLibrary.simpleMessage( + "To‘liq ism kiriting", + ), + "has_account": MessageLookupByLibrary.simpleMessage( + "Allaqachon akkauntingiz bormi?", + ), + "incorrect_code": MessageLookupByLibrary.simpleMessage("Noto\'g\'ri kod"), + "left_comment": MessageLookupByLibrary.simpleMessage("Izoh qoldirish"), + "light": MessageLookupByLibrary.simpleMessage("Light"), + "login_error": MessageLookupByLibrary.simpleMessage("登入資料不正確"), + "logout": MessageLookupByLibrary.simpleMessage("Chiqish"), + "name": MessageLookupByLibrary.simpleMessage("Nomi"), + "no_account": MessageLookupByLibrary.simpleMessage("Akkauntingiz yo’qmi?"), + "no_order": MessageLookupByLibrary.simpleMessage( + "Hozircha zakazlaringiz yoʻq", + ), + "no_order_desc": MessageLookupByLibrary.simpleMessage( + "У вас пока нет загрузок.", + ), + "notification": MessageLookupByLibrary.simpleMessage("Bildirishnomalar"), + "or": MessageLookupByLibrary.simpleMessage("Yoki"), + "order_delivery_info": MessageLookupByLibrary.simpleMessage( + "Buyurtma yetkazish ma’lumotlari", + ), + "order_details": MessageLookupByLibrary.simpleMessage( + "Buyurtma tafsilotlari", + ), + "order_name": MessageLookupByLibrary.simpleMessage("Buyurtma nomi"), + "order_number": MessageLookupByLibrary.simpleMessage("Buyurtma raqami"), + "order_photo": MessageLookupByLibrary.simpleMessage("Buyurtma rasmlari"), + "order_status": MessageLookupByLibrary.simpleMessage("Status:"), + "orders": MessageLookupByLibrary.simpleMessage("Buyurtmalar"), + "password": MessageLookupByLibrary.simpleMessage("Identifikator"), + "personal_info": MessageLookupByLibrary.simpleMessage( + "Shaxsiy ma\'lumotlar", + ), + "phone": MessageLookupByLibrary.simpleMessage("Telefon raqami yoki email"), + "phone_call": MessageLookupByLibrary.simpleMessage("Aloqa"), + "phone_number": MessageLookupByLibrary.simpleMessage("Telefon"), + "phone_number_text": MessageLookupByLibrary.simpleMessage("Telefon raqami"), + "phone_registered": MessageLookupByLibrary.simpleMessage( + "Есть номер телефона.", + ), + "price": MessageLookupByLibrary.simpleMessage("Narxi"), + "product_name": MessageLookupByLibrary.simpleMessage("Mahsulot nomi"), + "profile": MessageLookupByLibrary.simpleMessage("Profil"), + "save": MessageLookupByLibrary.simpleMessage("Saqlash"), + "select_language": MessageLookupByLibrary.simpleMessage("Tilni tanlang "), + "select_warehouse": MessageLookupByLibrary.simpleMessage("Omborni tanlang"), + "send": MessageLookupByLibrary.simpleMessage("Yuborish"), + "send_sms": MessageLookupByLibrary.simpleMessage("Kodni qayta yuborish"), + "sign_up": MessageLookupByLibrary.simpleMessage("Ro\'yxatdan o\'tish"), + "size": MessageLookupByLibrary.simpleMessage("Hajmi"), + "size_in_m3": MessageLookupByLibrary.simpleMessage("Hajmi m³"), + "text_copied": MessageLookupByLibrary.simpleMessage("Текст скопирован."), + "theme_mode": MessageLookupByLibrary.simpleMessage("Ilova ko’rinishi"), + "user_id": MessageLookupByLibrary.simpleMessage("ID пользователя"), + "warehouse": MessageLookupByLibrary.simpleMessage("Ombor"), + "weight": MessageLookupByLibrary.simpleMessage("Og\'irligi"), + "weight_in_kg": MessageLookupByLibrary.simpleMessage("Og\'irligi kg"), + }; +} diff --git a/lib/generated/intl/messages_ru.dart b/lib/generated/intl/messages_ru.dart new file mode 100644 index 0000000..5615ef3 --- /dev/null +++ b/lib/generated/intl/messages_ru.dart @@ -0,0 +1,145 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a ru locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'ru'; + + static String m0(mail) => "Введите код, отправленный на ${mail}"; + + static String m1(phone) => "Введите код, отправленный на номер ${phone}"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "after": MessageLookupByLibrary.simpleMessage("после"), + "auth": MessageLookupByLibrary.simpleMessage("Вход"), + "auth_login": MessageLookupByLibrary.simpleMessage("Войти в систему"), + "average_weight": MessageLookupByLibrary.simpleMessage("Средний вес"), + "average_weight_in": MessageLookupByLibrary.simpleMessage( + "Средний вес (кг/м³)", + ), + "calculate": MessageLookupByLibrary.simpleMessage("Рассчитать"), + "calculator": MessageLookupByLibrary.simpleMessage("Калькулятор"), + "cancel": MessageLookupByLibrary.simpleMessage("Отмена"), + "change_language": MessageLookupByLibrary.simpleMessage("Изменить язык"), + "comment": MessageLookupByLibrary.simpleMessage("Комментарий"), + "comment_desc": MessageLookupByLibrary.simpleMessage( + "Пожалуйста, оставьте отзыв о взаимодействии. Это поможет нам улучшить сервис.", + ), + "comment_text": MessageLookupByLibrary.simpleMessage("Текст комментария"), + "confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"), + "confirm_email": MessageLookupByLibrary.simpleMessage( + "Подтвердите адрес электронной почты", + ), + "confirm_phone_text": MessageLookupByLibrary.simpleMessage( + "Подтвердите номер телефона", + ), + "continue_next": MessageLookupByLibrary.simpleMessage("Продолжить"), + "dark": MessageLookupByLibrary.simpleMessage("Темная"), + "delete": MessageLookupByLibrary.simpleMessage("Удалить"), + "delete_account": MessageLookupByLibrary.simpleMessage("Удалить аккаунт"), + "delete_account_desc": MessageLookupByLibrary.simpleMessage( + "Нажав кнопку «Удалить», вы удалите свою учётную запись навсегда!\nВы уверены, что хотите удалить свою учётную запись?", + ), + "delivery_price": MessageLookupByLibrary.simpleMessage("Цена доставки"), + "delivery_time": MessageLookupByLibrary.simpleMessage("Время доставки"), + "delivery_time_estimated": MessageLookupByLibrary.simpleMessage( + "Оценочное время доставки", + ), + "done_order": MessageLookupByLibrary.simpleMessage( + "Ваша заявка успешно принята! Спасибо за использование наших услуг.", + ), + "done_phone": MessageLookupByLibrary.simpleMessage( + "Спасибо за использование нашего сервиса! Мы скоро с вами свяжемся!", + ), + "done_ready": MessageLookupByLibrary.simpleMessage("Понятно!"), + "email": MessageLookupByLibrary.simpleMessage("Email"), + "email_address": MessageLookupByLibrary.simpleMessage("Электронная почта"), + "enter_code_mail": m0, + "enter_code_phone": m1, + "enter_phone_or_mail": MessageLookupByLibrary.simpleMessage( + "Введите номер телефона или email", + ), + "error_email": MessageLookupByLibrary.simpleMessage( + "Ошибка в адресе электронной почты", + ), + "error_full_name": MessageLookupByLibrary.simpleMessage( + "Неправильное полное имя", + ), + "error_in_phone": MessageLookupByLibrary.simpleMessage( + "Ошибка в номере телефона", + ), + "full_name": MessageLookupByLibrary.simpleMessage("Полное имя"), + "full_name_hint": MessageLookupByLibrary.simpleMessage( + "Введите полное имя", + ), + "has_account": MessageLookupByLibrary.simpleMessage("Уже есть аккаунт?"), + "incorrect_code": MessageLookupByLibrary.simpleMessage("Неверный код"), + "left_comment": MessageLookupByLibrary.simpleMessage("Оставить отзыв"), + "light": MessageLookupByLibrary.simpleMessage("Светлая"), + "login_error": MessageLookupByLibrary.simpleMessage( + "Данные для входа неверны", + ), + "logout": MessageLookupByLibrary.simpleMessage("Выход"), + "name": MessageLookupByLibrary.simpleMessage("Имя"), + "no_account": MessageLookupByLibrary.simpleMessage("Нет аккаунта?"), + "no_order": MessageLookupByLibrary.simpleMessage("Никакого багажа!"), + "no_order_desc": MessageLookupByLibrary.simpleMessage( + "У вас пока нет загрузок.", + ), + "notification": MessageLookupByLibrary.simpleMessage("Уведомления"), + "or": MessageLookupByLibrary.simpleMessage("Или"), + "order_delivery_info": MessageLookupByLibrary.simpleMessage( + "Информация о доставке заказа", + ), + "order_details": MessageLookupByLibrary.simpleMessage("Детали заказа"), + "order_name": MessageLookupByLibrary.simpleMessage("Название заказа"), + "order_number": MessageLookupByLibrary.simpleMessage("Номер заказа"), + "order_photo": MessageLookupByLibrary.simpleMessage("Фотографии заказа"), + "order_status": MessageLookupByLibrary.simpleMessage("Статус:"), + "orders": MessageLookupByLibrary.simpleMessage("Заказы"), + "password": MessageLookupByLibrary.simpleMessage("Идентификатор"), + "personal_info": MessageLookupByLibrary.simpleMessage("Личная информация"), + "phone": MessageLookupByLibrary.simpleMessage("Номер телефона или email"), + "phone_call": MessageLookupByLibrary.simpleMessage("Связь"), + "phone_number": MessageLookupByLibrary.simpleMessage("Телефон"), + "phone_number_text": MessageLookupByLibrary.simpleMessage("Номер телефона"), + "phone_registered": MessageLookupByLibrary.simpleMessage( + "Пользователь существует.", + ), + "price": MessageLookupByLibrary.simpleMessage("Цена"), + "product_name": MessageLookupByLibrary.simpleMessage("Название продукта"), + "profile": MessageLookupByLibrary.simpleMessage("Профиль"), + "save": MessageLookupByLibrary.simpleMessage("Сохранить"), + "select_language": MessageLookupByLibrary.simpleMessage( + "Выберите удобный язык", + ), + "select_warehouse": MessageLookupByLibrary.simpleMessage("Выберите склад"), + "send": MessageLookupByLibrary.simpleMessage("Отправить"), + "send_sms": MessageLookupByLibrary.simpleMessage("Отправить код повторно"), + "sign_up": MessageLookupByLibrary.simpleMessage("Зарегистрироваться"), + "size": MessageLookupByLibrary.simpleMessage("Размер"), + "size_in_m3": MessageLookupByLibrary.simpleMessage("Объем в м³"), + "text_copied": MessageLookupByLibrary.simpleMessage("Текст скопирован."), + "theme_mode": MessageLookupByLibrary.simpleMessage("Тема приложения"), + "user_id": MessageLookupByLibrary.simpleMessage("ID пользователя"), + "warehouse": MessageLookupByLibrary.simpleMessage("Склад"), + "weight": MessageLookupByLibrary.simpleMessage("Вес"), + "weight_in_kg": MessageLookupByLibrary.simpleMessage("Вес в кг"), + }; +} diff --git a/lib/generated/intl/messages_uz.dart b/lib/generated/intl/messages_uz.dart new file mode 100644 index 0000000..c8c5001 --- /dev/null +++ b/lib/generated/intl/messages_uz.dart @@ -0,0 +1,160 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a uz locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'uz'; + + static String m0(mail) => "${mail} mailga yuborilgan maxfiy kodni kiriting"; + + static String m1(phone) => + "${phone} raqamingizga yuborilgan maxfiy kodni kiriting"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "after": MessageLookupByLibrary.simpleMessage("so\'ng"), + "auth": MessageLookupByLibrary.simpleMessage("Kirish"), + "auth_login": MessageLookupByLibrary.simpleMessage("Tizimga kirish"), + "average_weight": MessageLookupByLibrary.simpleMessage("O\'rtacha vazn"), + "average_weight_in": MessageLookupByLibrary.simpleMessage( + "O\'rtacha vazn (kg/m³)", + ), + "calculate": MessageLookupByLibrary.simpleMessage("Hisoblash"), + "calculator": MessageLookupByLibrary.simpleMessage("Kalkulyator"), + "cancel": MessageLookupByLibrary.simpleMessage("Bekor qilish"), + "change_language": MessageLookupByLibrary.simpleMessage( + "Tilni o\'zgartirish", + ), + "comment": MessageLookupByLibrary.simpleMessage("Izoh"), + "comment_desc": MessageLookupByLibrary.simpleMessage( + "Iltimos, o\'zaro aloqa haqida sharh qoldiring. Bu bizga xizmatni yaxshilashga yordam beradi.", + ), + "comment_text": MessageLookupByLibrary.simpleMessage("Izoh matni"), + "confirm": MessageLookupByLibrary.simpleMessage("Tasdiqlash"), + "confirm_email": MessageLookupByLibrary.simpleMessage( + "E-pochta manzilini tasdiqlash", + ), + "confirm_phone_text": MessageLookupByLibrary.simpleMessage( + "Telefon raqamini tasdiqlang", + ), + "continue_next": MessageLookupByLibrary.simpleMessage("Davom etish"), + "dark": MessageLookupByLibrary.simpleMessage("Dark"), + "delete": MessageLookupByLibrary.simpleMessage("0‘chirish"), + "delete_account": MessageLookupByLibrary.simpleMessage( + "Akkauntni o’chirish", + ), + "delete_account_desc": MessageLookupByLibrary.simpleMessage( + "O’chirish tugmasini bosish orqali sizning akkountingiz butunlay o’chib ketiadi!\nHaqiqatdanam akkountingizni o’chirmoqchimisiz?", + ), + "delivery_price": MessageLookupByLibrary.simpleMessage( + "Yetkazib berish narxi", + ), + "delivery_time": MessageLookupByLibrary.simpleMessage( + "Yetkazib berish vaqti", + ), + "delivery_time_estimated": MessageLookupByLibrary.simpleMessage( + "Tahminiy yetkazish vaqti", + ), + "done_order": MessageLookupByLibrary.simpleMessage( + "Sizning arizangiz muvaffaqiyatli qabul qilindi! Xizmatlarimizdan foydalanishingizdan hursandmiz", + ), + "done_phone": MessageLookupByLibrary.simpleMessage( + "Xizmatimizdan foydalanishingizdan mamnunmiz! Sizga tez orada aloqaga chiqamiz!", + ), + "done_ready": MessageLookupByLibrary.simpleMessage("Tasdiqlash!"), + "email": MessageLookupByLibrary.simpleMessage("Email"), + "email_address": MessageLookupByLibrary.simpleMessage("Elektron pochta"), + "enter_code_mail": m0, + "enter_code_phone": m1, + "enter_phone_or_mail": MessageLookupByLibrary.simpleMessage( + "Telefon raqam yoki email kiriting", + ), + "error_email": MessageLookupByLibrary.simpleMessage( + "Elektron pochta manzilida xato", + ), + "error_full_name": MessageLookupByLibrary.simpleMessage( + "To\'liq ism noto\'g\'ri", + ), + "error_in_phone": MessageLookupByLibrary.simpleMessage( + "Telefon raqamida xato", + ), + "full_name": MessageLookupByLibrary.simpleMessage("To\'liq ism"), + "full_name_hint": MessageLookupByLibrary.simpleMessage( + "To‘liq ism kiriting", + ), + "has_account": MessageLookupByLibrary.simpleMessage( + "Allaqachon akkauntingiz bormi?", + ), + "incorrect_code": MessageLookupByLibrary.simpleMessage("Noto\'g\'ri kod"), + "left_comment": MessageLookupByLibrary.simpleMessage("Izoh qoldirish"), + "light": MessageLookupByLibrary.simpleMessage("Light"), + "login_error": MessageLookupByLibrary.simpleMessage( + "Kirish maʼlumotlari notoʻgʻri", + ), + "logout": MessageLookupByLibrary.simpleMessage("Chiqish"), + "name": MessageLookupByLibrary.simpleMessage("Nomi"), + "no_account": MessageLookupByLibrary.simpleMessage("Akkauntingiz yo’qmi?"), + "no_order": MessageLookupByLibrary.simpleMessage("Yuklar yo‘q!"), + "no_order_desc": MessageLookupByLibrary.simpleMessage( + "Sizda hali yuklar mavjud emas.", + ), + "notification": MessageLookupByLibrary.simpleMessage("Bildirishnomalar"), + "or": MessageLookupByLibrary.simpleMessage("Yoki"), + "order_delivery_info": MessageLookupByLibrary.simpleMessage( + "Buyurtma yetkazish ma’lumotlari", + ), + "order_details": MessageLookupByLibrary.simpleMessage( + "Buyurtma tafsilotlari", + ), + "order_name": MessageLookupByLibrary.simpleMessage("Buyurtma nomi"), + "order_number": MessageLookupByLibrary.simpleMessage("Buyurtma raqami"), + "order_photo": MessageLookupByLibrary.simpleMessage("Buyurtma rasmlari"), + "order_status": MessageLookupByLibrary.simpleMessage("Status:"), + "orders": MessageLookupByLibrary.simpleMessage("Buyurtmalar"), + "password": MessageLookupByLibrary.simpleMessage("Identifikator"), + "personal_info": MessageLookupByLibrary.simpleMessage( + "Shaxsiy ma\'lumotlar", + ), + "phone": MessageLookupByLibrary.simpleMessage("Telefon raqami yoki email"), + "phone_call": MessageLookupByLibrary.simpleMessage("Aloqa"), + "phone_number": MessageLookupByLibrary.simpleMessage("Telefon"), + "phone_number_text": MessageLookupByLibrary.simpleMessage("Telefon raqami"), + "phone_registered": MessageLookupByLibrary.simpleMessage( + "Foydalanuvchi mavjud.", + ), + "price": MessageLookupByLibrary.simpleMessage("Narxi"), + "product_name": MessageLookupByLibrary.simpleMessage("Mahsulot nomi"), + "profile": MessageLookupByLibrary.simpleMessage("Profil"), + "save": MessageLookupByLibrary.simpleMessage("Saqlash"), + "select_language": MessageLookupByLibrary.simpleMessage( + "O’zingizga qulay tilni tanlang", + ), + "select_warehouse": MessageLookupByLibrary.simpleMessage("Omborni tanlang"), + "send": MessageLookupByLibrary.simpleMessage("Yuborish"), + "send_sms": MessageLookupByLibrary.simpleMessage("Kodni qayta yuborish"), + "sign_up": MessageLookupByLibrary.simpleMessage("Ro\'yxatdan o\'tish"), + "size": MessageLookupByLibrary.simpleMessage("Hajmi"), + "size_in_m3": MessageLookupByLibrary.simpleMessage("Hajmi m³"), + "text_copied": MessageLookupByLibrary.simpleMessage("Matn nusxalandi"), + "theme_mode": MessageLookupByLibrary.simpleMessage("Ilova ko’rinishi"), + "user_id": MessageLookupByLibrary.simpleMessage("Foydalanuvchi IDsi"), + "warehouse": MessageLookupByLibrary.simpleMessage("Ombor"), + "weight": MessageLookupByLibrary.simpleMessage("Og\'irligi"), + "weight_in_kg": MessageLookupByLibrary.simpleMessage("Og\'irligi kg"), + }; +} diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart new file mode 100644 index 0000000..cfb72eb --- /dev/null +++ b/lib/generated/intl/messages_zh.dart @@ -0,0 +1,113 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a zh locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'zh'; + + static String m0(mail) => "请输入发送到 ${mail} 的验证码"; + + static String m1(phone) => "请输入发送到 ${phone} 的验证码"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "after": MessageLookupByLibrary.simpleMessage("之后"), + "auth": MessageLookupByLibrary.simpleMessage("登录"), + "auth_login": MessageLookupByLibrary.simpleMessage("登录系统"), + "average_weight": MessageLookupByLibrary.simpleMessage("平均重量"), + "average_weight_in": MessageLookupByLibrary.simpleMessage("平均重量(千克/立方米)"), + "calculate": MessageLookupByLibrary.simpleMessage("计算"), + "calculator": MessageLookupByLibrary.simpleMessage("计算器"), + "cancel": MessageLookupByLibrary.simpleMessage("取消"), + "change_language": MessageLookupByLibrary.simpleMessage("更改语言"), + "comment": MessageLookupByLibrary.simpleMessage("评论"), + "comment_desc": MessageLookupByLibrary.simpleMessage( + "请留下关于交流的评论。这将帮助我们改进服务。", + ), + "comment_text": MessageLookupByLibrary.simpleMessage("评论内容"), + "confirm": MessageLookupByLibrary.simpleMessage("确认"), + "confirm_email": MessageLookupByLibrary.simpleMessage("确认电子邮件地址"), + "confirm_phone_text": MessageLookupByLibrary.simpleMessage("确认电话号码"), + "continue_next": MessageLookupByLibrary.simpleMessage("继续"), + "dark": MessageLookupByLibrary.simpleMessage("深色"), + "delete": MessageLookupByLibrary.simpleMessage("刪除"), + "delete_account": MessageLookupByLibrary.simpleMessage("刪除帳戶"), + "delete_account_desc": MessageLookupByLibrary.simpleMessage( + "點擊刪除按鈕後,您的帳戶將永久刪除!\n您確定要刪除您的帳戶嗎?", + ), + "delivery_price": MessageLookupByLibrary.simpleMessage("配送费用"), + "delivery_time": MessageLookupByLibrary.simpleMessage("配送时间"), + "delivery_time_estimated": MessageLookupByLibrary.simpleMessage("预计配送时间"), + "done_order": MessageLookupByLibrary.simpleMessage("您的申请已成功提交!感谢您使用我们的服务。"), + "done_phone": MessageLookupByLibrary.simpleMessage("感谢您使用我们的服务!我们会尽快与您联系!"), + "done_ready": MessageLookupByLibrary.simpleMessage("知道了!"), + "email": MessageLookupByLibrary.simpleMessage("电子邮件"), + "email_address": MessageLookupByLibrary.simpleMessage("电子邮件"), + "enter_code_mail": m0, + "enter_code_phone": m1, + "enter_phone_or_mail": MessageLookupByLibrary.simpleMessage("请输入电话号码或电子邮件"), + "error_email": MessageLookupByLibrary.simpleMessage("电子邮件地址错误"), + "error_full_name": MessageLookupByLibrary.simpleMessage("全名错误"), + "error_in_phone": MessageLookupByLibrary.simpleMessage("电话号码错误"), + "full_name": MessageLookupByLibrary.simpleMessage("全名"), + "full_name_hint": MessageLookupByLibrary.simpleMessage("请输入全名"), + "has_account": MessageLookupByLibrary.simpleMessage("已有账户?"), + "incorrect_code": MessageLookupByLibrary.simpleMessage("验证码错误"), + "left_comment": MessageLookupByLibrary.simpleMessage("留下评论"), + "light": MessageLookupByLibrary.simpleMessage("浅色"), + "login_error": MessageLookupByLibrary.simpleMessage("登入資料不正確"), + "logout": MessageLookupByLibrary.simpleMessage("登出"), + "name": MessageLookupByLibrary.simpleMessage("名称"), + "no_account": MessageLookupByLibrary.simpleMessage("还没有账户?"), + "no_order": MessageLookupByLibrary.simpleMessage("沒有行李!"), + "no_order_desc": MessageLookupByLibrary.simpleMessage("您還沒有任何負載。"), + "notification": MessageLookupByLibrary.simpleMessage("通知"), + "or": MessageLookupByLibrary.simpleMessage("或者"), + "order_delivery_info": MessageLookupByLibrary.simpleMessage("订单配送信息"), + "order_details": MessageLookupByLibrary.simpleMessage("订单详情"), + "order_name": MessageLookupByLibrary.simpleMessage("订单名称"), + "order_number": MessageLookupByLibrary.simpleMessage("订单号"), + "order_photo": MessageLookupByLibrary.simpleMessage("订单照片"), + "order_status": MessageLookupByLibrary.simpleMessage("状态:"), + "orders": MessageLookupByLibrary.simpleMessage("订单"), + "password": MessageLookupByLibrary.simpleMessage("标识符"), + "personal_info": MessageLookupByLibrary.simpleMessage("个人信息"), + "phone": MessageLookupByLibrary.simpleMessage("电话号码或电子邮件"), + "phone_call": MessageLookupByLibrary.simpleMessage("联系"), + "phone_number": MessageLookupByLibrary.simpleMessage("电话"), + "phone_number_text": MessageLookupByLibrary.simpleMessage("电话号码"), + "phone_registered": MessageLookupByLibrary.simpleMessage("用戶已存在。"), + "price": MessageLookupByLibrary.simpleMessage("价格"), + "product_name": MessageLookupByLibrary.simpleMessage("产品名称"), + "profile": MessageLookupByLibrary.simpleMessage("个人资料"), + "save": MessageLookupByLibrary.simpleMessage("保存"), + "select_language": MessageLookupByLibrary.simpleMessage("选择您方便的语言"), + "select_warehouse": MessageLookupByLibrary.simpleMessage("选择仓库"), + "send": MessageLookupByLibrary.simpleMessage("发送"), + "send_sms": MessageLookupByLibrary.simpleMessage("重新发送验证码"), + "sign_up": MessageLookupByLibrary.simpleMessage("注册"), + "size": MessageLookupByLibrary.simpleMessage("体积"), + "size_in_m3": MessageLookupByLibrary.simpleMessage("体积(立方米)"), + "text_copied": MessageLookupByLibrary.simpleMessage("文字已複製"), + "theme_mode": MessageLookupByLibrary.simpleMessage("应用外观"), + "user_id": MessageLookupByLibrary.simpleMessage("使用者身分"), + "warehouse": MessageLookupByLibrary.simpleMessage("仓库"), + "weight": MessageLookupByLibrary.simpleMessage("重量"), + "weight_in_kg": MessageLookupByLibrary.simpleMessage("重量(千克)"), + }; +} diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart new file mode 100644 index 0000000..e71393f --- /dev/null +++ b/lib/generated/l10n.dart @@ -0,0 +1,716 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'intl/messages_all.dart'; + +// ************************************************************************** +// Generator: Flutter Intl IDE plugin +// Made by Localizely +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars +// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each +// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes + +class AppLocalization { + AppLocalization(); + + static AppLocalization? _current; + + static AppLocalization get current { + assert( + _current != null, + 'No instance of AppLocalization was loaded. Try to initialize the AppLocalization delegate before accessing AppLocalization.current.', + ); + return _current!; + } + + static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); + + static Future load(Locale locale) { + final name = + (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); + final localeName = Intl.canonicalizedLocale(name); + return initializeMessages(localeName).then((_) { + Intl.defaultLocale = localeName; + final instance = AppLocalization(); + AppLocalization._current = instance; + + return instance; + }); + } + + static AppLocalization of(BuildContext context) { + final instance = AppLocalization.maybeOf(context); + assert( + instance != null, + 'No instance of AppLocalization present in the widget tree. Did you add AppLocalization.delegate in localizationsDelegates?', + ); + return instance!; + } + + static AppLocalization? maybeOf(BuildContext context) { + return Localizations.of(context, AppLocalization); + } + + /// `Tilni tanlang ` + String get select_language { + return Intl.message( + 'Tilni tanlang ', + name: 'select_language', + desc: '', + args: [], + ); + } + + /// `Davom etish` + String get continue_next { + return Intl.message( + 'Davom etish', + name: 'continue_next', + desc: '', + args: [], + ); + } + + /// `Kirish` + String get auth { + return Intl.message('Kirish', name: 'auth', desc: '', args: []); + } + + /// `Telefon raqami yoki email` + String get phone { + return Intl.message( + 'Telefon raqami yoki email', + name: 'phone', + desc: '', + args: [], + ); + } + + /// `Identifikator` + String get password { + return Intl.message('Identifikator', name: 'password', desc: '', args: []); + } + + /// `Yoki` + String get or { + return Intl.message('Yoki', name: 'or', desc: '', args: []); + } + + /// `Ro'yxatdan o'tish` + String get sign_up { + return Intl.message( + 'Ro\'yxatdan o\'tish', + name: 'sign_up', + desc: '', + args: [], + ); + } + + /// `To'liq ism` + String get full_name { + return Intl.message('To\'liq ism', name: 'full_name', desc: '', args: []); + } + + /// `Telefon raqamini tasdiqlang` + String get confirm_phone_text { + return Intl.message( + 'Telefon raqamini tasdiqlang', + name: 'confirm_phone_text', + desc: '', + args: [], + ); + } + + /// `E-pochta manzilini tasdiqlash` + String get confirm_email { + return Intl.message( + 'E-pochta manzilini tasdiqlash', + name: 'confirm_email', + desc: '', + args: [], + ); + } + + /// `Noto'g'ri kod` + String get incorrect_code { + return Intl.message( + 'Noto\'g\'ri kod', + name: 'incorrect_code', + desc: '', + args: [], + ); + } + + /// `Kodni qayta yuborish` + String get send_sms { + return Intl.message( + 'Kodni qayta yuborish', + name: 'send_sms', + desc: '', + args: [], + ); + } + + /// `so'ng` + String get after { + return Intl.message('so\'ng', name: 'after', desc: '', args: []); + } + + /// `Tasdiqlash` + String get confirm { + return Intl.message('Tasdiqlash', name: 'confirm', desc: '', args: []); + } + + /// `Profil` + String get profile { + return Intl.message('Profil', name: 'profile', desc: '', args: []); + } + + /// `Shaxsiy ma'lumotlar` + String get personal_info { + return Intl.message( + 'Shaxsiy ma\'lumotlar', + name: 'personal_info', + desc: '', + args: [], + ); + } + + /// `Telefon` + String get phone_number { + return Intl.message('Telefon', name: 'phone_number', desc: '', args: []); + } + + /// `Email` + String get email { + return Intl.message('Email', name: 'email', desc: '', args: []); + } + + /// `Elektron pochta manzilida xato` + String get error_email { + return Intl.message( + 'Elektron pochta manzilida xato', + name: 'error_email', + desc: '', + args: [], + ); + } + + /// `Telefon raqamida xato` + String get error_in_phone { + return Intl.message( + 'Telefon raqamida xato', + name: 'error_in_phone', + desc: '', + args: [], + ); + } + + /// `To'liq ism noto'g'ri` + String get error_full_name { + return Intl.message( + 'To\'liq ism noto\'g\'ri', + name: 'error_full_name', + desc: '', + args: [], + ); + } + + /// `Tilni o'zgartirish` + String get change_language { + return Intl.message( + 'Tilni o\'zgartirish', + name: 'change_language', + desc: '', + args: [], + ); + } + + /// `Chiqish` + String get logout { + return Intl.message('Chiqish', name: 'logout', desc: '', args: []); + } + + /// `Buyurtmalar` + String get orders { + return Intl.message('Buyurtmalar', name: 'orders', desc: '', args: []); + } + + /// `Status:` + String get order_status { + return Intl.message('Status:', name: 'order_status', desc: '', args: []); + } + + /// `Buyurtma raqami` + String get order_number { + return Intl.message( + 'Buyurtma raqami', + name: 'order_number', + desc: '', + args: [], + ); + } + + /// `Ombor` + String get warehouse { + return Intl.message('Ombor', name: 'warehouse', desc: '', args: []); + } + + /// `Nomi` + String get name { + return Intl.message('Nomi', name: 'name', desc: '', args: []); + } + + /// `Telefon raqami` + String get phone_number_text { + return Intl.message( + 'Telefon raqami', + name: 'phone_number_text', + desc: '', + args: [], + ); + } + + /// `Elektron pochta` + String get email_address { + return Intl.message( + 'Elektron pochta', + name: 'email_address', + desc: '', + args: [], + ); + } + + /// `Hajmi` + String get size { + return Intl.message('Hajmi', name: 'size', desc: '', args: []); + } + + /// `Og'irligi` + String get weight { + return Intl.message('Og\'irligi', name: 'weight', desc: '', args: []); + } + + /// `O'rtacha vazn` + String get average_weight { + return Intl.message( + 'O\'rtacha vazn', + name: 'average_weight', + desc: '', + args: [], + ); + } + + /// `Yetkazib berish narxi` + String get delivery_price { + return Intl.message( + 'Yetkazib berish narxi', + name: 'delivery_price', + desc: '', + args: [], + ); + } + + /// `Kalkulyator` + String get calculator { + return Intl.message('Kalkulyator', name: 'calculator', desc: '', args: []); + } + + /// `Og'irligi kg` + String get weight_in_kg { + return Intl.message( + 'Og\'irligi kg', + name: 'weight_in_kg', + desc: '', + args: [], + ); + } + + /// `Hajmi m³` + String get size_in_m3 { + return Intl.message('Hajmi m³', name: 'size_in_m3', desc: '', args: []); + } + + /// `O'rtacha vazn (kg/m³)` + String get average_weight_in { + return Intl.message( + 'O\'rtacha vazn (kg/m³)', + name: 'average_weight_in', + desc: '', + args: [], + ); + } + + /// `Mahsulot nomi` + String get product_name { + return Intl.message( + 'Mahsulot nomi', + name: 'product_name', + desc: '', + args: [], + ); + } + + /// `Omborni tanlang` + String get select_warehouse { + return Intl.message( + 'Omborni tanlang', + name: 'select_warehouse', + desc: '', + args: [], + ); + } + + /// `Hisoblash` + String get calculate { + return Intl.message('Hisoblash', name: 'calculate', desc: '', args: []); + } + + /// `Narxi` + String get price { + return Intl.message('Narxi', name: 'price', desc: '', args: []); + } + + /// `Telefon raqam yoki email kiriting` + String get enter_phone_or_mail { + return Intl.message( + 'Telefon raqam yoki email kiriting', + name: 'enter_phone_or_mail', + desc: '', + args: [], + ); + } + + /// `Akkauntingiz yo’qmi?` + String get no_account { + return Intl.message( + 'Akkauntingiz yo’qmi?', + name: 'no_account', + desc: '', + args: [], + ); + } + + /// `To‘liq ism kiriting` + String get full_name_hint { + return Intl.message( + 'To‘liq ism kiriting', + name: 'full_name_hint', + desc: '', + args: [], + ); + } + + /// `Allaqachon akkauntingiz bormi?` + String get has_account { + return Intl.message( + 'Allaqachon akkauntingiz bormi?', + name: 'has_account', + desc: '', + args: [], + ); + } + + /// `Tizimga kirish` + String get auth_login { + return Intl.message( + 'Tizimga kirish', + name: 'auth_login', + desc: '', + args: [], + ); + } + + /// `{phone} raqamingizga yuborilgan maxfiy kodni kiriting` + String enter_code_phone(Object phone) { + return Intl.message( + '$phone raqamingizga yuborilgan maxfiy kodni kiriting', + name: 'enter_code_phone', + desc: '', + args: [phone], + ); + } + + /// `{mail} mailga yuborilgan maxfiy kodni kiriting` + String enter_code_mail(Object mail) { + return Intl.message( + '$mail mailga yuborilgan maxfiy kodni kiriting', + name: 'enter_code_mail', + desc: '', + args: [mail], + ); + } + + /// `Buyurtma nomi` + String get order_name { + return Intl.message( + 'Buyurtma nomi', + name: 'order_name', + desc: '', + args: [], + ); + } + + /// `Yetkazib berish vaqti` + String get delivery_time { + return Intl.message( + 'Yetkazib berish vaqti', + name: 'delivery_time', + desc: '', + args: [], + ); + } + + /// `Buyurtma yetkazish ma’lumotlari` + String get order_delivery_info { + return Intl.message( + 'Buyurtma yetkazish ma’lumotlari', + name: 'order_delivery_info', + desc: '', + args: [], + ); + } + + /// `Tahminiy yetkazish vaqti` + String get delivery_time_estimated { + return Intl.message( + 'Tahminiy yetkazish vaqti', + name: 'delivery_time_estimated', + desc: '', + args: [], + ); + } + + /// `Buyurtma tafsilotlari` + String get order_details { + return Intl.message( + 'Buyurtma tafsilotlari', + name: 'order_details', + desc: '', + args: [], + ); + } + + /// `Buyurtma rasmlari` + String get order_photo { + return Intl.message( + 'Buyurtma rasmlari', + name: 'order_photo', + desc: '', + args: [], + ); + } + + /// `Aloqa` + String get phone_call { + return Intl.message('Aloqa', name: 'phone_call', desc: '', args: []); + } + + /// `Yuborish` + String get send { + return Intl.message('Yuborish', name: 'send', desc: '', args: []); + } + + /// `Xizmatimizdan foydalanishingizdan mamnunmiz! Sizga tez orada aloqaga chiqamiz!` + String get done_phone { + return Intl.message( + 'Xizmatimizdan foydalanishingizdan mamnunmiz! Sizga tez orada aloqaga chiqamiz!', + name: 'done_phone', + desc: '', + args: [], + ); + } + + /// `Sizning arizangiz muvaffaqiyatli qabul qilindi! Xizmatlarimizdan foydalanishingizdan hursandmiz` + String get done_order { + return Intl.message( + 'Sizning arizangiz muvaffaqiyatli qabul qilindi! Xizmatlarimizdan foydalanishingizdan hursandmiz', + name: 'done_order', + desc: '', + args: [], + ); + } + + /// `Tushundim!` + String get done_ready { + return Intl.message('Tushundim!', name: 'done_ready', desc: '', args: []); + } + + /// `Saqlash` + String get save { + return Intl.message('Saqlash', name: 'save', desc: '', args: []); + } + + /// `Ilova ko’rinishi` + String get theme_mode { + return Intl.message( + 'Ilova ko’rinishi', + name: 'theme_mode', + desc: '', + args: [], + ); + } + + /// `Light` + String get light { + return Intl.message('Light', name: 'light', desc: '', args: []); + } + + /// `Dark` + String get dark { + return Intl.message('Dark', name: 'dark', desc: '', args: []); + } + + /// `Izoh qoldirish` + String get left_comment { + return Intl.message( + 'Izoh qoldirish', + name: 'left_comment', + desc: '', + args: [], + ); + } + + /// `Iltimos, o'zaro aloqa haqida sharh qoldiring. Bu bizga xizmatni yaxshilashga yordam beradi.` + String get comment_desc { + return Intl.message( + 'Iltimos, o\'zaro aloqa haqida sharh qoldiring. Bu bizga xizmatni yaxshilashga yordam beradi.', + name: 'comment_desc', + desc: '', + args: [], + ); + } + + /// `Izoh` + String get comment { + return Intl.message('Izoh', name: 'comment', desc: '', args: []); + } + + /// `Izoh matni` + String get comment_text { + return Intl.message('Izoh matni', name: 'comment_text', desc: '', args: []); + } + + /// `Bildirishnomalar` + String get notification { + return Intl.message( + 'Bildirishnomalar', + name: 'notification', + desc: '', + args: [], + ); + } + + /// `ID пользователя` + String get user_id { + return Intl.message('ID пользователя', name: 'user_id', desc: '', args: []); + } + + /// `Текст скопирован.` + String get text_copied { + return Intl.message( + 'Текст скопирован.', + name: 'text_copied', + desc: '', + args: [], + ); + } + + /// `Отмена` + String get cancel { + return Intl.message('Отмена', name: 'cancel', desc: '', args: []); + } + + /// `登入資料不正確` + String get login_error { + return Intl.message('登入資料不正確', name: 'login_error', desc: '', args: []); + } + + /// `Hozircha zakazlaringiz yoʻq` + String get no_order { + return Intl.message( + 'Hozircha zakazlaringiz yoʻq', + name: 'no_order', + desc: '', + args: [], + ); + } + + /// `У вас пока нет загрузок.` + String get no_order_desc { + return Intl.message( + 'У вас пока нет загрузок.', + name: 'no_order_desc', + desc: '', + args: [], + ); + } + + /// `Есть номер телефона.` + String get phone_registered { + return Intl.message( + 'Есть номер телефона.', + name: 'phone_registered', + desc: '', + args: [], + ); + } + + /// `Удалить аккаунт` + String get delete_account { + return Intl.message( + 'Удалить аккаунт', + name: 'delete_account', + desc: '', + args: [], + ); + } + + /// `Нажав кнопку «Удалить», вы удалите свою учётную запись навсегда!\nВы уверены, что хотите удалить свою учётную запись?` + String get delete_account_desc { + return Intl.message( + 'Нажав кнопку «Удалить», вы удалите свою учётную запись навсегда!\nВы уверены, что хотите удалить свою учётную запись?', + name: 'delete_account_desc', + desc: '', + args: [], + ); + } + + /// `Удалить` + String get delete { + return Intl.message('Удалить', name: 'delete', desc: '', args: []); + } +} + +class AppLocalizationDelegate extends LocalizationsDelegate { + const AppLocalizationDelegate(); + + List get supportedLocales { + return const [ + Locale.fromSubtags(languageCode: 'en'), + Locale.fromSubtags(languageCode: 'ru'), + Locale.fromSubtags(languageCode: 'uz'), + Locale.fromSubtags(languageCode: 'zh'), + ]; + } + + @override + bool isSupported(Locale locale) => _isSupported(locale); + @override + Future load(Locale locale) => AppLocalization.load(locale); + @override + bool shouldReload(AppLocalizationDelegate old) => false; + + bool _isSupported(Locale locale) { + for (var supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale.languageCode) { + return true; + } + } + return false; + } +} diff --git a/lib/injector_container.dart b/lib/injector_container.dart new file mode 100644 index 0000000..93e9a55 --- /dev/null +++ b/lib/injector_container.dart @@ -0,0 +1,282 @@ +import 'dart:io'; +import 'package:cargocalculaterapp/features/auth/presentation/bloc/auth/auth_bloc.dart'; +import 'package:cargocalculaterapp/features/auth/presentation/bloc/sign_up/sign_up_bloc.dart'; +import 'package:cargocalculaterapp/features/calculator/presentation/bloc/calculator_bloc.dart'; +import 'package:cargocalculaterapp/features/home/domain/usecase/order_single_usecase.dart'; +import 'package:cargocalculaterapp/features/home/presentation/bloc/home_bloc.dart'; +import 'package:cargocalculaterapp/features/home/presentation/bloc/notification/notification_bloc.dart'; +import 'package:cargocalculaterapp/features/profile/domain/repository/profile_repository.dart'; +import 'package:cargocalculaterapp/features/profile/presentation/bloc/profile_bloc.dart'; +import 'package:cargocalculaterapp/router/app_routes.dart'; +import 'package:cargocalculaterapp/router/name_routes.dart'; +import 'package:dio/dio.dart'; +import 'package:dio_retry_plus/dio_retry_plus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:hive/hive.dart'; +import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; +import 'package:path_provider/path_provider.dart'; +import 'core/app_bloc/app_bloc.dart'; +import 'core/local_source/local_source.dart'; +import 'core/platform/network_info.dart'; +import 'features/auth/data/data_source/remote/auth_remote_data_source.dart'; +import 'features/auth/data/data_source/remote/auth_remote_data_source_impl.dart'; +import 'features/auth/data/repository/auth_repository_impl.dart'; +import 'features/auth/domain/repository/auth_repository.dart'; +import 'features/auth/domain/usecases/fcm_add_usecase.dart'; +import 'features/auth/domain/usecases/login_usecase.dart'; +import 'features/auth/domain/usecases/sign_up_usecase.dart'; +import 'features/auth/domain/usecases/verify_phone_usecase.dart'; +import 'features/auth/presentation/bloc/auth_confirm/auth_confirm_bloc.dart'; +import 'features/calculator/data/data_source/calculator_remote_data_source.dart'; +import 'features/calculator/data/data_source/calculator_remote_data_source_impl.dart'; +import 'features/calculator/data/repository/calculator_repository_impl.dart'; +import 'features/calculator/domain/repository/calculator_repository.dart'; +import 'features/calculator/domain/usecase/calculate_price_usecase.dart'; +import 'features/calculator/domain/usecase/create_lead_usecase.dart'; +import 'features/calculator/presentation/bloc/calculator_info/calculator_info_bloc.dart'; +import 'features/home/data/data_source/remote/home_remote_data_source.dart'; +import 'features/home/data/data_source/remote/home_remote_data_source_impl.dart'; +import 'features/home/data/repository/home_repository_impl.dart'; +import 'features/home/domain/repository/home_repository.dart'; +import 'features/home/domain/usecase/banners_usecase.dart'; +import 'features/home/domain/usecase/comment_usecase.dart'; +import 'features/home/domain/usecase/notification_read_usecase.dart'; +import 'features/home/domain/usecase/notification_usecase.dart'; +import 'features/home/domain/usecase/orders_list_usecase.dart'; +import 'features/home/presentation/bloc/comment/comment_bloc.dart'; +import 'features/home/presentation/bloc/order_single/order_single_bloc.dart'; +import 'features/main/presentation/bloc/main_bloc.dart'; +import 'features/profile/data/data_source/local/profile_local_data_source.dart'; +import 'features/profile/data/data_source/local/profile_local_data_source_impl.dart'; +import 'features/profile/data/data_source/remote/profile_remote_data_source.dart'; +import 'features/profile/data/data_source/remote/profile_remote_data_source_impl.dart'; +import 'features/profile/data/repository/profile_repository_impl.dart'; +import 'features/profile/domain/usecase/delete_profile_usecase.dart'; +import 'features/profile/domain/usecase/profile_update_usecase.dart'; +import 'features/profile/domain/usecase/profile_usecase.dart'; + +final sl = GetIt.instance; +late Box _box; + +Future init() async { + //External + await initHive(); + sl.registerSingleton(LocalSource(_box)); + sl.registerLazySingleton( + () => Dio() + ..interceptors.addAll([ + LogInterceptor( + + request: kDebugMode, + responseBody: kDebugMode, + error: kDebugMode, + requestBody: kDebugMode, + ), + // chuck.dioInterceptor, + ]), + ); + sl.registerLazySingleton(() => InternetConnection()); + //Core + sl.registerLazySingleton(() => NetworkInfoImpl(sl())); + sl.registerSingleton(AppBloc()); + sl().interceptors.add( + RetryInterceptor( + dio: sl(), + retryDelays: const [Duration(seconds: 3), Duration(seconds: 2)], + toNoInternetPageNavigator: () async { + // Navigator.pushNamed( + // rootNavigatorKey.currentContext!, + // Routes.noConnection,"email": "umarforsiy@gmail.com", + // ); + }, + accessTokenGetter: () => "Bearer ${sl().getAccessToken()}", + refreshTokenFunction: () async { + final language = sl().getLocale(); + await sl().clear().then((value) { + sl().setLocale(language); + sl().setIsFirstEnter(false); + if (rootNavigatorKey.currentContext?.mounted ?? false) { + Navigator.pushNamedAndRemoveUntil( + rootNavigatorKey.currentContext!, + Routes.auth, + (route) => false, + ); + } + }); + }, + forbiddenFunction: () async {}, + logPrint: (message) { + if (kDebugMode) { + print(message); + } + }, + ), + ); + + // Features + _mainFeature(); + _auth(); + _profile(); + _home(); + _calculator(); +} + +void _auth() { + sl.registerFactory(() => AuthBloc(sl())); + sl.registerFactory(() => SignUpBloc(sl())); + sl.registerFactory(() => AuthConfirmBloc(sl(), sl(), sl(), sl())); + + /// UseCases + sl.registerLazySingleton(() => LoginUseCase(sl())); + sl.registerLazySingleton(() => SignUpUseCase(sl())); + sl.registerLazySingleton(() => VerifyPhoneUseCase(sl())); + sl.registerLazySingleton(() => FcmAddUseCase(sl())); + + ///Repositories + sl.registerLazySingleton(() => AuthRepositoryImpl(sl())); + + /// Data and Network + sl.registerLazySingleton( + () => AuthRemoteDataSourceImpl(sl()), + ); +} + +void _mainFeature() { + sl.registerFactory(() => MainBloc()); +} + +void _profile() { + sl.registerFactory(() => ProfileBloc(sl(), sl(), sl())); + + /// UseCases + sl.registerLazySingleton(() => ProfileUseCase(sl())); + sl.registerLazySingleton( + () => DeleteProfileUseCase(sl()), + ); + sl.registerLazySingleton( + () => ProfileUpdateUseCase(sl()), + ); + + ///Repositories + sl.registerLazySingleton( + () => ProfileRepositoryImpl(sl(), sl()), + ); + + /// Data and Network + sl.registerLazySingleton( + () => ProfileRemoteDataSourceImpl(sl()), + ); + sl.registerLazySingleton( + () => ProfileLocalDataSourceImpl(_box), + ); +} + +void _home() { + sl.registerFactory(() => HomeBloc(sl(), sl())); + sl.registerFactory(() => OrderSingleBloc(sl())); + sl.registerFactory(() => CommentBloc(sl())); + sl.registerFactory(() => NotificationBloc(sl(), sl())); + + /// UseCases + sl.registerLazySingleton(() => OrdersListUseCase(sl())); + sl.registerLazySingleton(() => OrderSingleUseCase(sl())); + sl.registerLazySingleton(() => CommentUseCase(sl())); + sl.registerLazySingleton( + () => NotificationUseCase(sl()), + ); + sl.registerLazySingleton( + () => NotificationReadUseCase(sl()), + ); + sl.registerLazySingleton(() => BannersUseCase(sl())); + + ///Repositories + sl.registerLazySingleton(() => HomeRepositoryImpl(sl())); + + /// Data and Network + sl.registerLazySingleton( + () => HomeRemoteDataSourceImpl(sl()), + ); +} + +void _calculator() { + sl.registerFactory(() => CalculatorBloc(sl())); + sl.registerFactory(() => CalculatorInfoBloc(sl())); + + /// UseCases + sl.registerLazySingleton( + () => CalculatePriceUseCase(sl()), + ); + sl.registerLazySingleton(() => CreateLeadUseCase(sl())); + + ///Repositories + sl.registerLazySingleton( + () => CalculatorRepositoryImpl(sl()), + ); + + /// Data and Network + sl.registerLazySingleton( + () => CalculatorRemoteDataSourceImpl(sl()), + ); +} + +Future initHive({bool isTest = false}) async { + const boxName = 'cargocalculator'; + + final Directory directory = await getApplicationDocumentsDirectory(); + Hive.init(directory.path); + + _box = await Hive.openBox(boxName); +} + +class LogBlocObserver extends BlocObserver { + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + if (kDebugMode) { + print(change); + } + } + + @override + void onClose(BlocBase bloc) { + super.onClose(bloc); + if (kDebugMode) { + print("$bloc closed"); + } + } + + @override + void onCreate(BlocBase bloc) { + super.onCreate(bloc); + if (kDebugMode) { + print("$bloc created"); + } + } + + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + if (kDebugMode) { + print('${bloc.runtimeType} $event'); + } + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + super.onError(bloc, error, stackTrace); + if (kDebugMode) { + print('${bloc.runtimeType} $error'); + } + } + + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + if (kDebugMode) { + print(transition.toString()); + } + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb new file mode 100644 index 0000000..9f914f4 --- /dev/null +++ b/lib/l10n/intl_en.arb @@ -0,0 +1,81 @@ +{ + "select_language": "Tilni tanlang ", + "continue_next": "Davom etish", + "auth": "Kirish", + "phone": "Telefon raqami yoki email", + "password": "Identifikator", + "or": "Yoki", + "sign_up": "Ro'yxatdan o'tish", + "full_name": "To'liq ism", + "confirm_phone_text": "Telefon raqamini tasdiqlang", + "confirm_email": "E-pochta manzilini tasdiqlash", + "incorrect_code": "Noto'g'ri kod", + "send_sms": "Kodni qayta yuborish", + "after": "so'ng", + "confirm": "Tasdiqlash", + "profile": "Profil", + "personal_info": "Shaxsiy ma'lumotlar", + "phone_number": "Telefon", + "email": "Email", + "error_email": "Elektron pochta manzilida xato", + "error_in_phone": "Telefon raqamida xato", + "error_full_name": "To'liq ism noto'g'ri", + "change_language": "Tilni o'zgartirish", + "logout": "Chiqish", + "orders": "Buyurtmalar", + "order_status": "Status:", + "order_number": "Buyurtma raqami", + "warehouse": "Ombor", + "name": "Nomi", + "phone_number_text": "Telefon raqami", + "email_address": "Elektron pochta", + "size": "Hajmi", + "weight": "Og'irligi", + "average_weight": "O'rtacha vazn", + "delivery_price": "Yetkazib berish narxi", + "calculator": "Kalkulyator", + "weight_in_kg": "Og'irligi kg", + "size_in_m3": "Hajmi m³", + "average_weight_in": "O'rtacha vazn (kg/m³)", + "product_name": "Mahsulot nomi", + "select_warehouse": "Omborni tanlang", + "calculate": "Hisoblash", + "price": "Narxi", + "enter_phone_or_mail": "Telefon raqam yoki email kiriting", + "no_account": "Akkauntingiz yo’qmi?", + "full_name_hint": "To‘liq ism kiriting", + "has_account": "Allaqachon akkauntingiz bormi?", + "auth_login": "Tizimga kirish", + "enter_code_phone": "{phone} raqamingizga yuborilgan maxfiy kodni kiriting", + "enter_code_mail": "{mail} mailga yuborilgan maxfiy kodni kiriting", + "order_name": "Buyurtma nomi", + "delivery_time": "Yetkazib berish vaqti", + "order_delivery_info": "Buyurtma yetkazish ma’lumotlari", + "delivery_time_estimated": "Tahminiy yetkazish vaqti", + "order_details": "Buyurtma tafsilotlari", + "order_photo": "Buyurtma rasmlari", + "phone_call": "Aloqa", + "send": "Yuborish", + "done_phone": "Xizmatimizdan foydalanishingizdan mamnunmiz! Sizga tez orada aloqaga chiqamiz!", + "done_order": "Sizning arizangiz muvaffaqiyatli qabul qilindi! Xizmatlarimizdan foydalanishingizdan hursandmiz", + "done_ready": "Tushundim!", + "save": "Saqlash", + "theme_mode": "Ilova ko’rinishi", + "light": "Light", + "dark": "Dark", + "left_comment": "Izoh qoldirish", + "comment_desc": "Iltimos, o'zaro aloqa haqida sharh qoldiring. Bu bizga xizmatni yaxshilashga yordam beradi.", + "comment": "Izoh", + "comment_text": "Izoh matni", + "notification": "Bildirishnomalar", + "user_id": "ID пользователя", + "text_copied": "Текст скопирован.", + "cancel": "Отмена", + "login_error": "登入資料不正確", + "no_order": "Hozircha zakazlaringiz yoʻq", + "no_order_desc": "У вас пока нет загрузок.", + "phone_registered": "Есть номер телефона.", + "delete_account": "Удалить аккаунт", + "delete_account_desc": "Нажав кнопку «Удалить», вы удалите свою учётную запись навсегда!\nВы уверены, что хотите удалить свою учётную запись?", + "delete": "Удалить" +} \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb new file mode 100644 index 0000000..5ebe51f --- /dev/null +++ b/lib/l10n/intl_ru.arb @@ -0,0 +1,81 @@ +{ + "select_language": "Выберите удобный язык", + "continue_next": "Продолжить", + "auth": "Вход", + "phone": "Номер телефона или email", + "password": "Идентификатор", + "or": "Или", + "sign_up": "Зарегистрироваться", + "full_name": "Полное имя", + "confirm_phone_text": "Подтвердите номер телефона", + "confirm_email": "Подтвердите адрес электронной почты", + "incorrect_code": "Неверный код", + "send_sms": "Отправить код повторно", + "after": "после", + "confirm": "Подтвердить", + "profile": "Профиль", + "personal_info": "Личная информация", + "phone_number": "Телефон", + "email": "Email", + "error_email": "Ошибка в адресе электронной почты", + "error_in_phone": "Ошибка в номере телефона", + "error_full_name": "Неправильное полное имя", + "change_language": "Изменить язык", + "logout": "Выход", + "orders": "Заказы", + "order_status": "Статус:", + "order_number": "Номер заказа", + "warehouse": "Склад", + "name": "Имя", + "phone_number_text": "Номер телефона", + "email_address": "Электронная почта", + "size": "Размер", + "weight": "Вес", + "average_weight": "Средний вес", + "delivery_price": "Цена доставки", + "calculator": "Калькулятор", + "weight_in_kg": "Вес в кг", + "size_in_m3": "Объем в м³", + "average_weight_in": "Средний вес (кг/м³)", + "product_name": "Название продукта", + "select_warehouse": "Выберите склад", + "calculate": "Рассчитать", + "price": "Цена", + "enter_phone_or_mail": "Введите номер телефона или email", + "no_account": "Нет аккаунта?", + "full_name_hint": "Введите полное имя", + "has_account": "Уже есть аккаунт?", + "auth_login": "Войти в систему", + "enter_code_phone": "Введите код, отправленный на номер {phone}", + "enter_code_mail": "Введите код, отправленный на {mail}", + "order_name": "Название заказа", + "delivery_time": "Время доставки", + "order_delivery_info": "Информация о доставке заказа", + "delivery_time_estimated": "Оценочное время доставки", + "order_details": "Детали заказа", + "order_photo": "Фотографии заказа", + "phone_call": "Связь", + "send": "Отправить", + "done_phone": "Спасибо за использование нашего сервиса! Мы скоро с вами свяжемся!", + "done_order": "Ваша заявка успешно принята! Спасибо за использование наших услуг.", + "done_ready": "Понятно!", + "save": "Сохранить", + "theme_mode": "Тема приложения", + "light": "Светлая", + "dark": "Темная", + "left_comment": "Оставить отзыв", + "comment_desc": "Пожалуйста, оставьте отзыв о взаимодействии. Это поможет нам улучшить сервис.", + "comment": "Комментарий", + "comment_text": "Текст комментария", + "notification": "Уведомления", + "user_id": "ID пользователя", + "text_copied": "Текст скопирован.", + "cancel": "Отмена", + "login_error": "Данные для входа неверны", + "no_order": "Никакого багажа!", + "no_order_desc": "У вас пока нет загрузок.", + "phone_registered": "Пользователь существует.", + "delete_account": "Удалить аккаунт", + "delete_account_desc": "Нажав кнопку «Удалить», вы удалите свою учётную запись навсегда!\nВы уверены, что хотите удалить свою учётную запись?", + "delete": "Удалить" +} \ No newline at end of file diff --git a/lib/l10n/intl_uz.arb b/lib/l10n/intl_uz.arb new file mode 100644 index 0000000..550703a --- /dev/null +++ b/lib/l10n/intl_uz.arb @@ -0,0 +1,81 @@ +{ + "select_language": "O’zingizga qulay tilni tanlang", + "continue_next": "Davom etish", + "auth": "Kirish", + "phone": "Telefon raqami yoki email", + "password": "Identifikator", + "or": "Yoki", + "sign_up": "Ro'yxatdan o'tish", + "full_name": "To'liq ism", + "confirm_phone_text": "Telefon raqamini tasdiqlang", + "confirm_email": "E-pochta manzilini tasdiqlash", + "incorrect_code": "Noto'g'ri kod", + "send_sms": "Kodni qayta yuborish", + "after": "so'ng", + "confirm": "Tasdiqlash", + "profile": "Profil", + "personal_info": "Shaxsiy ma'lumotlar", + "phone_number": "Telefon", + "email": "Email", + "error_email": "Elektron pochta manzilida xato", + "error_in_phone": "Telefon raqamida xato", + "error_full_name": "To'liq ism noto'g'ri", + "change_language": "Tilni o'zgartirish", + "logout": "Chiqish", + "orders": "Buyurtmalar", + "order_status": "Status:", + "order_number": "Buyurtma raqami", + "warehouse": "Ombor", + "name": "Nomi", + "phone_number_text": "Telefon raqami", + "email_address": "Elektron pochta", + "size": "Hajmi", + "weight": "Og'irligi", + "average_weight": "O'rtacha vazn", + "delivery_price": "Yetkazib berish narxi", + "calculator": "Kalkulyator", + "weight_in_kg": "Og'irligi kg", + "size_in_m3": "Hajmi m³", + "average_weight_in": "O'rtacha vazn (kg/m³)", + "product_name": "Mahsulot nomi", + "select_warehouse": "Omborni tanlang", + "calculate": "Hisoblash", + "price": "Narxi", + "enter_phone_or_mail": "Telefon raqam yoki email kiriting", + "no_account": "Akkauntingiz yo’qmi?", + "full_name_hint": "To‘liq ism kiriting", + "has_account": "Allaqachon akkauntingiz bormi?", + "auth_login": "Tizimga kirish", + "enter_code_phone": "{phone} raqamingizga yuborilgan maxfiy kodni kiriting", + "enter_code_mail":"{mail} mailga yuborilgan maxfiy kodni kiriting", + "order_name": "Buyurtma nomi", + "delivery_time": "Yetkazib berish vaqti", + "order_delivery_info": "Buyurtma yetkazish ma’lumotlari", + "delivery_time_estimated": "Tahminiy yetkazish vaqti", + "order_details": "Buyurtma tafsilotlari", + "order_photo": "Buyurtma rasmlari", + "phone_call": "Aloqa", + "send": "Yuborish", + "done_phone": "Xizmatimizdan foydalanishingizdan mamnunmiz! Sizga tez orada aloqaga chiqamiz!", + "done_order": "Sizning arizangiz muvaffaqiyatli qabul qilindi! Xizmatlarimizdan foydalanishingizdan hursandmiz", + "done_ready": "Tasdiqlash!", + "save": "Saqlash", + "theme_mode": "Ilova ko’rinishi", + "light": "Light", + "dark": "Dark", + "left_comment": "Izoh qoldirish", + "comment_desc": "Iltimos, o'zaro aloqa haqida sharh qoldiring. Bu bizga xizmatni yaxshilashga yordam beradi.", + "comment": "Izoh", + "comment_text": "Izoh matni", + "notification": "Bildirishnomalar", + "user_id": "Foydalanuvchi IDsi", + "text_copied": "Matn nusxalandi", + "cancel": "Bekor qilish", + "login_error": "Kirish maʼlumotlari notoʻgʻri", + "no_order": "Yuklar yo‘q!", + "no_order_desc": "Sizda hali yuklar mavjud emas.", + "phone_registered": "Foydalanuvchi mavjud.", + "delete_account": "Akkauntni o’chirish", + "delete_account_desc": "O’chirish tugmasini bosish orqali sizning akkountingiz butunlay o’chib ketiadi!\nHaqiqatdanam akkountingizni o’chirmoqchimisiz?", + "delete": "0‘chirish" +} \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb new file mode 100644 index 0000000..c1ab47c --- /dev/null +++ b/lib/l10n/intl_zh.arb @@ -0,0 +1,81 @@ +{ + "select_language": "选择您方便的语言", + "continue_next": "继续", + "auth": "登录", + "phone": "电话号码或电子邮件", + "password": "标识符", + "or": "或者", + "sign_up": "注册", + "full_name": "全名", + "confirm_phone_text": "确认电话号码", + "confirm_email": "确认电子邮件地址", + "incorrect_code": "验证码错误", + "send_sms": "重新发送验证码", + "after": "之后", + "confirm": "确认", + "profile": "个人资料", + "personal_info": "个人信息", + "phone_number": "电话", + "email": "电子邮件", + "error_email": "电子邮件地址错误", + "error_in_phone": "电话号码错误", + "error_full_name": "全名错误", + "change_language": "更改语言", + "logout": "登出", + "orders": "订单", + "order_status": "状态:", + "order_number": "订单号", + "warehouse": "仓库", + "name": "名称", + "phone_number_text": "电话号码", + "email_address": "电子邮件", + "size": "体积", + "weight": "重量", + "average_weight": "平均重量", + "delivery_price": "配送费用", + "calculator": "计算器", + "weight_in_kg": "重量(千克)", + "size_in_m3": "体积(立方米)", + "average_weight_in": "平均重量(千克/立方米)", + "product_name": "产品名称", + "select_warehouse": "选择仓库", + "calculate": "计算", + "price": "价格", + "enter_phone_or_mail": "请输入电话号码或电子邮件", + "no_account": "还没有账户?", + "full_name_hint": "请输入全名", + "has_account": "已有账户?", + "auth_login": "登录系统", + "enter_code_phone": "请输入发送到 {phone} 的验证码", + "enter_code_mail": "请输入发送到 {mail} 的验证码", + "order_name": "订单名称", + "delivery_time": "配送时间", + "order_delivery_info": "订单配送信息", + "delivery_time_estimated": "预计配送时间", + "order_details": "订单详情", + "order_photo": "订单照片", + "phone_call": "联系", + "send": "发送", + "done_phone": "感谢您使用我们的服务!我们会尽快与您联系!", + "done_order": "您的申请已成功提交!感谢您使用我们的服务。", + "done_ready": "知道了!", + "save": "保存", + "theme_mode": "应用外观", + "light": "浅色", + "dark": "深色", + "left_comment": "留下评论", + "comment_desc": "请留下关于交流的评论。这将帮助我们改进服务。", + "comment": "评论", + "comment_text": "评论内容", + "notification": "通知", + "user_id": "使用者身分", + "text_copied": "文字已複製", + "cancel": "取消", + "login_error": "登入資料不正確", + "no_order": "沒有行李!", + "no_order_desc": "您還沒有任何負載。", + "phone_registered": "用戶已存在。", + "delete_account": "刪除帳戶", + "delete_account_desc": "點擊刪除按鈕後,您的帳戶將永久刪除!\n您確定要刪除您的帳戶嗎?", + "delete": "刪除" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..b7f831d --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,51 @@ +import 'package:cargocalculaterapp/service/notification_service.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_native_splash/flutter_native_splash.dart'; +import 'app.dart'; +import 'core/app_bloc/app_bloc.dart'; +import 'injector_container.dart' as di; +import 'injector_container.dart'; + +void main() async { + WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + await NotificationService.initialize(); + + /// init crashlytics + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; + PlatformDispatcher.instance.onError = (error, stack) { + FirebaseCrashlytics.instance.recordError(error, stack); + return true; + }; + + /// bloc logger + if (kDebugMode) { + Bloc.observer = LogBlocObserver(); + } + await di.init(); + + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [BlocProvider(create: (_) => sl())], + child: const App(), + ); + } +} + +// flutter pub run flutter_launcher_icons:main +// flutter run -d windows --no-sound-null-safety +// flutter build apk --release --no-sound-null-safety +// flutter build apk --split-per-abi --no-sound-null-safety +// flutter build appbundle --release --no-sound-null-safety +// flutter pub run build_runner watch --delete-conflicting-outputs +// flutter pub run flutter_native_splash:create diff --git a/lib/router/app_routes.dart b/lib/router/app_routes.dart new file mode 100644 index 0000000..ee56615 --- /dev/null +++ b/lib/router/app_routes.dart @@ -0,0 +1,176 @@ +// import 'package:chuck_interceptor/chuck_interceptor.dart'; +import 'package:cargocalculaterapp/features/auth/presentation/bloc/auth_confirm/auth_confirm_bloc.dart'; +import 'package:cargocalculaterapp/features/auth/presentation/bloc/sign_up/sign_up_bloc.dart'; +import 'package:cargocalculaterapp/features/auth/presentation/pages/auth_confirm/auth_confirm_page.dart'; +import 'package:cargocalculaterapp/features/auth/presentation/pages/sign_up/sign_up_page.dart'; +import 'package:cargocalculaterapp/features/calculator/presentation/bloc/calculator_info/calculator_info_bloc.dart'; +import 'package:cargocalculaterapp/features/calculator/presentation/pages/calculator_info/calculator_info_page.dart'; +import 'package:cargocalculaterapp/features/home/presentation/bloc/notification/notification_bloc.dart'; +import 'package:cargocalculaterapp/features/home/presentation/pages/comment/comment_page.dart'; +import 'package:cargocalculaterapp/features/main/presentation/pages/main_page.dart'; +import 'package:cargocalculaterapp/features/profile/presentation/pages/profile_page.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../core/local_source/local_source.dart'; +import '../features/auth/presentation/bloc/auth/auth_bloc.dart'; +import '../features/auth/presentation/pages/auth/auth_page.dart'; +import '../features/auth/presentation/pages/auth_confirm/argument/auth_confirm_argument.dart'; +import '../features/calculator/presentation/arguments/calculator_info_argument.dart'; +import '../features/home/presentation/bloc/comment/comment_bloc.dart'; +import '../features/home/presentation/bloc/order_single/order_single_bloc.dart'; +import '../features/home/presentation/pages/arguments/comment_argument.dart'; +import '../features/home/presentation/pages/arguments/image_page_argument.dart'; +import '../features/home/presentation/pages/arguments/order_info_argument.dart'; +import '../features/home/presentation/pages/home_page.dart'; +import '../features/home/presentation/pages/image/image_page.dart'; +import '../features/home/presentation/pages/notification/notification_page.dart'; +import '../features/home/presentation/pages/order_info/order_info_page.dart'; +import '../features/initial/pages/initial_page.dart'; +import '../features/language/presentation/argument/language_argument.dart'; +import '../features/language/presentation/pages/language/app_language_page.dart'; +import '../injector_container.dart'; +import 'name_routes.dart'; + +final rootNavigatorKey = GlobalKey(); +final localSource = sl(); + +// final Chuck chuck = Chuck( +// showNotification: true, +// showInspectorOnShake: false, +// darkTheme: false, +// navigatorKey: rootNavigatorKey, +// ); + +class AppRoutes { + AppRoutes._(); + + static Route onGenerateRoute(RouteSettings settings) { + if (kDebugMode) { + print("route : ${settings.name}"); + } + switch (settings.name) { + case Routes.initial: + return buildPageWithNoTransition(child: const InitialPage()); + case Routes.language: + return buildPageWithNoTransition( + child: AppLanguagePage( + languageArgument: settings.arguments is LanguageArgument + ? settings.arguments as LanguageArgument + : null, + ), + ); + case Routes.auth: + return MaterialPageRoute( + builder: (_) => BlocProvider( + create: (_) => sl(), + child: const AuthPage(), + ), + ); + case Routes.signUp: + return MaterialPageRoute( + builder: (_) => BlocProvider( + create: (_) => sl(), + child: const SignUpPage(), + ), + ); + case Routes.authConfirm: + return MaterialPageRoute( + builder: (_) => BlocProvider( + create: (_) => sl(), + child: AuthConfirmPage( + argument: settings.arguments is AuthConfirmArgument + ? settings.arguments as AuthConfirmArgument + : null, + ), + ), + ); + case Routes.orderInfo: + return MaterialPageRoute( + builder: (_) => BlocProvider( + create: (_) => sl(), + child: OrderInfoPage( + argument: settings.arguments is OrderInfoArgument + ? settings.arguments as OrderInfoArgument + : null, + ), + ), + ); + case Routes.main: + return MaterialPageRoute(builder: (_) => const MainPage()); + case Routes.calculationInfo: + return MaterialPageRoute( + builder: (_) => BlocProvider( + create: (context) => sl(), + child: CalculatorInfoPage( + argument: settings.arguments is CalculatorInfoArgument + ? settings.arguments as CalculatorInfoArgument + : null, + ), + ), + ); + case Routes.comment: + return MaterialPageRoute( + builder: (_) => BlocProvider( + create: (context) => sl(), + child: CommentPage( + argument: settings.arguments is CommentArgument + ? settings.arguments as CommentArgument + : null, + ), + ), + ); + case Routes.notification: + return MaterialPageRoute( + builder: (_) => BlocProvider( + create: (context) => sl(), + child: const NotificationPage(), + ), + ); + case Routes.image: + return MaterialPageRoute( + builder: (_) => ImagePage( + argument: settings.arguments is ImagePageArgument + ? settings.arguments as ImagePageArgument + : null, + ), + ); + default: + return buildPageWithNoTransition(child: const InitialPage()); + } + } + + static Route onShellGenerateRoute(RouteSettings settings) { + if (kDebugMode) { + print("route shell : ${settings.name}"); + } + switch (settings.name) { + case Routes.initial: + return buildPageWithDefaultTransition(child: const HomePage()); + case Routes.home: + return buildPageWithDefaultTransition(child: const HomePage()); + case Routes.profile: + return buildPageWithDefaultTransition(child: const ProfilePage()); + default: + return buildPageWithDefaultTransition(child: const HomePage()); + } + } +} + +PageRouteBuilder buildPageWithDefaultTransition({required Widget child}) { + return PageRouteBuilder( + pageBuilder: (_, __, ___) => child, + transitionsBuilder: (context, animation, secondaryAnimation, child) => + FadeTransition(opacity: animation, child: child), + ); +} + +PageRouteBuilder buildPageWithNoTransition({required Widget child}) { + return PageRouteBuilder( + reverseTransitionDuration: Duration.zero, + transitionDuration: Duration.zero, + pageBuilder: (_, __, ___) => child, + transitionsBuilder: (context, animation, secondaryAnimation, child) => + FadeTransition(opacity: animation, child: child), + ); +} diff --git a/lib/router/name_routes.dart b/lib/router/name_routes.dart new file mode 100644 index 0000000..2b94b28 --- /dev/null +++ b/lib/router/name_routes.dart @@ -0,0 +1,16 @@ +class Routes { + static const String initial = '/'; + static const String language = '/language'; + static const String auth = '/auth'; + static const String signUp = '/signUp'; + static const String authConfirm = '/authConfirm'; + static const String home = '/home'; + static const String profile = '/profile'; + static const String main = '/main'; + static const String calculation = '/calculation'; + static const String calculationInfo = '/calculationInfo'; + static const String orderInfo = '/orderInfo'; + static const String comment = '/comment'; + static const String notification = '/notification'; + static const String image = '/image'; +} diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart new file mode 100644 index 0000000..22881e8 --- /dev/null +++ b/lib/service/notification_service.dart @@ -0,0 +1,146 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import '../constants/constants.dart'; + +late AndroidNotificationChannel channel; +late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; +InitializationSettings initializationSettings = const InitializationSettings( + android: AndroidInitializationSettings('@mipmap/ic_launcher'), + iOS: DarwinInitializationSettings( + defaultPresentAlert: true, + defaultPresentBadge: true, + defaultPresentSound: true, + requestAlertPermission: true, + requestBadgePermission: true, + requestSoundPermission: true, + ), +); + +class NotificationService { + static Future initialize() async { + await Firebase.initializeApp(); + await setupFlutterNotifications(); + foregroundNotification(); + backgroundNotification(); + flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin + >() + ?.requestNotificationsPermission(); + } + + static Future clearNotificationBadge() async { + await flutterLocalNotificationsPlugin + .cancelAll(); // Cancels all notifications// Clears badge count + } + + static Future getFcmToken() async { + try { + final fcmToken = await FirebaseMessaging.instance.getToken(); + return fcmToken ?? ""; + } catch (e) { + debugPrint("ERROR: Cannot get FCM token"); + return ""; + } + } + + static Future setupFlutterNotifications() async { + if (Platform.isIOS) { + await FirebaseMessaging.instance.requestPermission( + announcement: true, + provisional: true, + alert: true, + badge: true, + criticalAlert: true, + sound: true, + ); + } + channel = const AndroidNotificationChannel( + 'high_importance_channel', // id + 'High Importance Notifications', // title + description: + 'This channel is used for important notifications.', // description + importance: Importance.max, + playSound: true, + ); + + flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin + >() + ?.createNotificationChannel(channel); + await FirebaseMessaging.instance + .setForegroundNotificationPresentationOptions( + alert: true, + badge: true, + sound: true, + ); + } + + static void showFlutterNotification(RemoteMessage message) { + if (message.notification != null && + !kIsWeb && + message.notification?.android != null) { + final bigTextStyleInformation = BigTextStyleInformation( + message.notification?.body ?? "", + ); + flutterLocalNotificationsPlugin.show( + message.hashCode, + message.notification?.title, + message.notification?.body, + NotificationDetails( + android: AndroidNotificationDetails( + channel.id, + channel.name, + channelDescription: channel.description, + icon: '@mipmap/ic_launcher', + priority: Priority.high, + importance: Importance.high, + visibility: NotificationVisibility.public, + playSound: true, + styleInformation: bigTextStyleInformation, + ), + iOS: const DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + sound: 'default', + ), + ), + payload: message.data[AppConst.clickAction] ?? "", + ); + } + } + + static void foregroundNotification() { + FirebaseMessaging.onMessage.listen(showFlutterNotification); + + ///When tapped + flutterLocalNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: (response) async { + debugPrint('foreground notification tapped'); + debugPrint("$response"); + }, + ); + } + + static void backgroundNotification() { + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + debugPrint('A new onMessageOpenedApp event was published! noootttt'); + debugPrint("$message"); + }); + } +} + +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await Firebase.initializeApp(); + await NotificationService.setupFlutterNotifications(); + //NotificationService.showFlutterNotification(message); +} diff --git a/mobile.zip b/mobile.zip new file mode 100644 index 0000000..48e23e0 Binary files /dev/null and b/mobile.zip differ diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..86cbbbb --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1047 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: bb84ee51e527053dd8e25ecc9f97a6abfdc19130fb4d883e4e8585e23e7e6dd8 + url: "https://pub.dev" + source: hosted + version: "1.3.60" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189" + url: "https://pub.dev" + source: hosted + version: "9.0.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + connectivity_plus: + dependency: transitive + description: + name: connectivity_plus + sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dartz: + dependency: "direct main" + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_retry_plus: + dependency: "direct main" + description: + name: dio_retry_plus + sha256: "1e94acc930e3fb58d81e2002f60bb0eed3c6a9844f5fce83a870ecf6229ebe97" + url: "https://pub.dev" + source: hosted + version: "2.0.8" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + sha256: "07146e89e11302c6b07e3465c2c556ebcdd0053a3c5b1aa9bfd3203b778e5b4c" + url: "https://pub.dev" + source: hosted + version: "12.0.0" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + sha256: "27e81a0efc821bec6cba64abc1083b91c8ddbad28eeb4c6f6b7c78a59d06f259" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + sha256: "7d87f47462042a7d9125e3123db2783bc72917d85e2719d4cb6aeaec209605e1" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "6b343e6f7b72a4f32d7ce8df8c9a28d8f54b4ac20d7c6500f3e8b3969afca457" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "5d28b14dd32282fb7ce2b22b897362453755b6b8541d491127dc72b755bb7b16" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + firebase_crashlytics: + dependency: "direct main" + description: + name: firebase_crashlytics + sha256: "95b6871850b1a7e3b09c284c59a0c71fafcad3eee8ac1b6f06aaf8979290cbb8" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + firebase_crashlytics_platform_interface: + dependency: transitive + description: + name: firebase_crashlytics_platform_interface + sha256: ba5b7a916f1ebedc6db35b33abdc618f202fc25e0792088dfba698e19fec9c09 + url: "https://pub.dev" + source: hosted + version: "3.8.11" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "10272b553a49c13a6cedfd00121047157521f82a5d3f2a1706b9dd28342cc482" + url: "https://pub.dev" + source: hosted + version: "16.0.0" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: b846a305feb3f74ee3f0aace447f65a4696bc6550bc828ecf5a84a1b77473d16 + url: "https://pub.dev" + source: hosted + version: "4.7.0" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "28714749880f7242c5fb3b1ee6c66b41f61453f02ae348b43c82957df80b87ae" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_keyboard_visibility: + dependency: "direct main" + description: + name: flutter_keyboard_visibility + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "20ca0a9c82ce0c855ac62a2e580ab867f3fbea82680a90647f7953832d0850ae" + url: "https://pub.dev" + source: hosted + version: "19.4.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" + url: "https://pub.dev" + source: hosted + version: "9.1.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_native_splash: + dependency: "direct main" + description: + name: flutter_native_splash + sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc" + url: "https://pub.dev" + source: hosted + version: "2.4.6" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 + url: "https://pub.dev" + source: hosted + version: "2.2.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + formz: + dependency: "direct main" + description: + name: formz + sha256: "382c7be452ff76833f9efa0b2333fec3a576393f6d2c7801725bed502f3d40c3" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b + url: "https://pub.dev" + source: hosted + version: "8.2.0" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" + internet_connection_checker_plus: + dependency: "direct main" + description: + name: internet_connection_checker_plus + sha256: "5aea4a1ee0fcca736980a7d04d96fe8c0b53dea330690053305a5c5392230112" + url: "https://pub.dev" + source: hosted + version: "2.7.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + mask_text_input_formatter: + dependency: "direct main" + description: + name: mask_text_input_formatter + sha256: "978c58ec721c25621ceb468e633f4eef64b64d45424ac4540e0565d4f7c800cd" + url: "https://pub.dev" + source: hosted + version: "2.9.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + pin_code_fields: + dependency: "direct main" + description: + name: pin_code_fields + sha256: "4c0db7fbc889e622e7c71ea54b9ee624bb70c7365b532abea0271b17ea75b729" + url: "https://pub.dev" + source: hosted + version: "8.0.1" + pin_input_text_field: + dependency: transitive + description: + name: pin_input_text_field + sha256: f45683032283d30b670ec343781660655e3e1953438b281a0bc6e2d358486236 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + provider: + dependency: transitive + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sms_autofill: + dependency: "direct main" + description: + name: sms_autofill + sha256: c65836abe9c1f62ce411bb78d5546a09ece4297558070b1bd871db1db283aaf9 + url: "https://pub.dev" + source: hosted + version: "2.4.1" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.dev" + source: hosted + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + url: "https://pub.dev" + source: hosted + version: "6.3.16" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" + url: "https://pub.dev" + source: hosted + version: "1.1.18" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + url: "https://pub.dev" + source: hosted + version: "1.1.16" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..ba9b7cd --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,95 @@ +name: cargocalculaterapp +description: "A new Flutter project." + +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +version: 1.0.0+1 + +environment: + sdk: ^3.8.0 +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + cupertino_icons: ^1.0.8 + # di + get_it: ^8.2.0 + dartz: ^0.10.1 + formz: ^0.8.0 + # keyboard dismiss + flutter_keyboard_visibility: ^6.0.0 + # state manager bloc + flutter_bloc: ^9.1.0 + # equations + equatable: ^2.0.7 + # cache hive + hive: ^2.2.3 + path_provider: ^2.1.5 + # native splash + flutter_native_splash: ^2.4.6 + # rest api + dio: ^5.9.0 + dio_retry_plus: ^2.0.8 + # connection check + internet_connection_checker_plus: ^2.7.2 + # firebase + firebase_core: ^4.0.0 + firebase_analytics: ^12.0.0 + firebase_messaging: ^16.0.0 + firebase_crashlytics: ^5.0.0 + flutter_local_notifications: ^19.4.0 + # chuck + #chuck_interceptor: ^2.2.5 + # mask input + mask_text_input_formatter: ^2.9.0 + sms_autofill: ^2.4.1 + pin_code_fields: ^8.0.1 + intl: ^0.20.2 + #svg + flutter_svg: ^2.2.0 + #shimmer + shimmer: ^3.0.0 + cached_network_image: ^3.4.1 + url_launcher: ^6.3.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: + uses-material-design: true + + assets: + - assets/svg/ + - assets/png/ + - assets/fonts/ + fonts: + - family: Inter + fonts: + - asset: assets/fonts/Inter-Regular.ttf + weight: 400 + - asset: assets/fonts/Inter-Medium.ttf + weight: 500 + - asset: assets/fonts/Inter-SemiBold.ttf + weight: 600 + - asset: assets/fonts/Inter-Bold.ttf + weight: 700 + +flutter_native_splash: + color: "#ffffff" + color_dark: "#ffffff" + image: "assets/png/ic_logo.png" + android_12: + color: "#ffffff" + color_dark: "#ffffff" + image: "assets/png/ic_logo.png" + +flutter_intl: + enabled: true + class_name: AppLocalization + arb-dir: lib/localization/l10n + preferred-supported-locales: + - uz + use-deferred-loading: true \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..415c3e1 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cargocalculaterapp/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}