commit 2d2e22fa11cc4b9e834100a69a229ce31ece7bf2 Author: MaximusAshraf <50173497+MaximusAshraf@users.noreply.github.com> Date: Sun Jan 1 15:38:58 2023 +0200 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8e938c --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +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 +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# 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..bbf096b --- /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. + +version: + revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: android + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: ios + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + + # 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..f7134ec --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# test_sa + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,29 @@ +# 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-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..e17c83e --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,62 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'com.google.gms.google-services' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 33 + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.test_sa" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion 21 + targetSdkVersion 33 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + proguardFiles getDefaultProguardFile('proguard-android.txt'), + 'proguard-rules.pro' + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..1ecaf89 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,39 @@ +{ + "project_info": { + "project_number": "176272925817", + "project_id": "opencis-sa", + "storage_bucket": "opencis-sa.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:176272925817:android:ba70bca496bc5bf2a9e0a8", + "android_client_info": { + "package_name": "com.example.test_sa" + } + }, + "oauth_client": [ + { + "client_id": "176272925817-e6gb9lhsteiisnat8r4mm3kne1g79m36.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAuZo6KixrW7nYOFM9An900tio0vA0fzS4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "176272925817-e6gb9lhsteiisnat8r4mm3kne1g79m36.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..f8768e4 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,27 @@ +## Gson rules +# Gson uses generic type information stored in a class file when working with fields. Proguard +# removes such information by default, so configure it to keep all of it. +-keepattributes Signature + +# For using GSON @Expose annotation +-keepattributes *Annotation* + +# Gson specific classes +-dontwarn sun.misc.** +#-keep class com.google.gson.stream.** { *; } + +# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, +# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) +-keep class * extends com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer + +# Prevent R8 from leaving Data object members always null +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + +# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. +-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken +-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken \ 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..76481ae --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5473353 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/com/example/test_sa/MainActivity.java b/android/app/src/main/java/com/example/test_sa/MainActivity.java new file mode 100644 index 0000000..eba0c1b --- /dev/null +++ b/android/app/src/main/java/com/example/test_sa/MainActivity.java @@ -0,0 +1,6 @@ +package com.example.test_sa; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} 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..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/app_icon.png b/android/app/src/main/res/drawable/app_icon.png new file mode 100644 index 0000000..d3741e2 Binary files /dev/null and b/android/app/src/main/res/drawable/app_icon.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..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..b553aa4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..992ab23 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6847696 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..83e1907 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..5ec59a9 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ 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..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + 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..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..76481ae --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..93d8238 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,32 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.google.gms:google-services:4.3.13' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +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..cc5527d --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/assets/fonts/Gotham_Rounded_Bold.otf b/assets/fonts/Gotham_Rounded_Bold.otf new file mode 100644 index 0000000..b2e80ac Binary files /dev/null and b/assets/fonts/Gotham_Rounded_Bold.otf differ diff --git a/assets/fonts/Gotham_Rounded_Book.otf b/assets/fonts/Gotham_Rounded_Book.otf new file mode 100644 index 0000000..0293008 Binary files /dev/null and b/assets/fonts/Gotham_Rounded_Book.otf differ diff --git a/assets/fonts/Gotham_Rounded_Light.otf b/assets/fonts/Gotham_Rounded_Light.otf new file mode 100644 index 0000000..2e8efc1 Binary files /dev/null and b/assets/fonts/Gotham_Rounded_Light.otf differ diff --git a/assets/images/0.png b/assets/images/0.png new file mode 100644 index 0000000..d2c7ee4 Binary files /dev/null and b/assets/images/0.png differ diff --git a/assets/images/1.png b/assets/images/1.png new file mode 100644 index 0000000..23ea255 Binary files /dev/null and b/assets/images/1.png differ diff --git a/assets/images/2.png b/assets/images/2.png new file mode 100644 index 0000000..4797eb9 Binary files /dev/null and b/assets/images/2.png differ diff --git a/assets/images/3.png b/assets/images/3.png new file mode 100644 index 0000000..018de1a Binary files /dev/null and b/assets/images/3.png differ diff --git a/assets/images/4.png b/assets/images/4.png new file mode 100644 index 0000000..4dc1ee0 Binary files /dev/null and b/assets/images/4.png differ diff --git a/assets/images/app_logo.jpg b/assets/images/app_logo.jpg new file mode 100644 index 0000000..ca6cdef Binary files /dev/null and b/assets/images/app_logo.jpg differ diff --git a/assets/images/background.jpg b/assets/images/background.jpg new file mode 100644 index 0000000..cefcc22 Binary files /dev/null and b/assets/images/background.jpg differ diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..d48118b Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/images/no_item_found.png b/assets/images/no_item_found.png new file mode 100644 index 0000000..2ff7d0c Binary files /dev/null and b/assets/images/no_item_found.png differ diff --git a/assets/images/qr.jpeg b/assets/images/qr.jpeg new file mode 100644 index 0000000..4610574 Binary files /dev/null and b/assets/images/qr.jpeg differ diff --git a/assets/images/sigin_in_background.jpg b/assets/images/sigin_in_background.jpg new file mode 100644 index 0000000..d2c7ee4 Binary files /dev/null and b/assets/images/sigin_in_background.jpg differ diff --git a/assets/rives/atoms_splash.flr b/assets/rives/atoms_splash.flr new file mode 100644 index 0000000..9522b31 Binary files /dev/null and b/assets/rives/atoms_splash.flr differ diff --git a/assets/rives/recording.riv b/assets/rives/recording.riv new file mode 100644 index 0000000..2bea11a Binary files /dev/null and b/assets/rives/recording.riv differ diff --git a/assets/subtitles/ar_subtitle.json b/assets/subtitles/ar_subtitle.json new file mode 100644 index 0000000..ada5f90 --- /dev/null +++ b/assets/subtitles/ar_subtitle.json @@ -0,0 +1,192 @@ +{ + "server_error_message": "الخدمة غير متاحة حاليا", + "failed_request_message": "الفشل في إكمال الطلب", + "successful_request_message": "تم إكمال الطلب بنجاح", + "request_lock_message": "انتظر حتى إكمال الطلب", + + + "cancel": "إلغاء", + "confirm": "تاكيد", + "done": "تم", + "exit": "إغلاق", + "exit_alert": "هل انت متاكد من رغبتك في إغلاق التطبيق؟", + "sign_out": "تسجيل الخروج", + "logout_alert": "هل انت متاكد من رغبتك في تسجيل الخروج؟", + "language": "اللغة", + + "name": "الاسم", + "email": "البريد الالكتروني", + "phone_number": "رقم الهاتف", + "password": "كلمة المرور", + "confirm_password": "تاكيد كلمة المرور", + "sign_in": "تسجيل الدخول", + "sign_up": "انشاء حساب", + "forget_password": "نسيت كلمة المرور", + "accept_terms_and_conditions": "موافقة علي الشروط والاحكام", + "email_validate_message": "البريد الالكتروني غير صحيح", + "name_validate_message": "الاسم مطلوب", + "password_validate_message": "علي الاقل استخدم 6 حروف او ارقام", + "confirm_password_validate_message": "كلمة المرور و تاكيد كلمة المرور غير متطابقان", + "phone_number_validate_message": "رقم الهاتف غير صالح", + "terms_and_conditions_validate_message": "موافقة علي الشروط والاحكام مطاوبة", + + "update": "تحديث", + "step": "خطوة", + "forget_password_with_mark": "نسيت كلمة المرور؟", + "show_password": "اظهار كلمة المرور", + "wrong_email_or_password": "البريد الالكتروني او كلمة المرور غير صحيح", + "email_exist": "البريد الالكتروني موجود بالفعل", + "phone_number_exist": "رقم الهاتف موجود بالفعل", + "next": "التالي", + "back": "السابق", + "search": "بحث", + "search_by_name": "بحث بالاسم", + "address": "العنوان", + "address_not_found": "لا يوجد عنوان", + "address_validate_message": "العنوان مطلوب", + "data_not_found": "لا يوجد تاريخ", + "description": "الوصف", + "description_not_found": "لا يوجد وصف", + "description_validate_message": "الوصف مطلوب", + "edit": "تعدل", + "email_not_found": "لا يوجد بريد الكتروني", + "from": "من", + "to": "إلى", + "link_not_found": "لا يوجد رابط", + "name_not_found": "لا يوجد إسم", + "phone_number_not_found": "لا يوجد رقم هاتف", + "title": "عنوان", + "title_not_found": "لا يوجد عنوان", + "title_validate_message": "العنوان مطلوب", + "url_not_found": "لا يوجد رابط", + + "date": "تاريخ", + "status": "الحالة", + "code": "كود", + "serialNumber": "رقم تسلسلي", + "add": "اضافة", + "brand": "ماركة", + "clearSearch": "تنظيف البحث", + "closed": "غلق", + "create": "انشاء", + "createServiceRequest": "انشاء طلب خدمة", + "delete": "مسح", + "details": "تفاصيل", + "device": "جهاز", + "deviceArName": "اسم الجهاز بعربي", + "deviceEnName": "اسم الجهاز بالانجليزية", + "deviceImages": "صور الجهاز", + "deviceModel": "مركة الجهاز", + "deviceName": "اسم الجهاز", + "deviceRequired": "الجهاز مطلوب", + "deviceSN": "رقم تسلسلي للجهاز", + "engineerName": "اسم المهندس", + "engineerPhone": "رقم المهندس", + "facebook": "فيسبوك", + "faultDescription": "تفاصيل الخطأ", + "general": "تفاصيل العامة", + "hospital": "العميل", + "hospitalRequired": "العميل مطلوبة", + "hotLine": "الخط ساخن", + "jobSheetNumber": "رقم ورقة العمل", + "linkedIn": "لينكد إن", + "maintenanceIssue": "مشكلة الصيانة", + "maintenanceIssueRequired": "مطلوب مسألة صيانة", + "maxImagesNumberIs5": "أقصى عدد للصورة 5", + "model": "نموذج", + "nameExist": "الاسم موجود", + "newServiceRequest": "طلب خدمة جديدة", + "newWord": "جديد", + "noDateFound": "لم يتم العثور على تاريخ", + "noDeviceFound": "لم يتم العثور على جهاز", + "noHospitalFound": "لا يوجد عميل", + "noModelFound": "لم يتم العثور على نموذج", + "noServiceRequestFound": "لم يتم العثور على طلب خدمة", + "noSnFound": "لم يتم العثور على رقم تسلسلي", + "notifications": "إشعارات", + "notificationsNotFound": "لم يتم العثور على إشعارات", + "noUniteFound": "لا توجد اقسام", + "ourWebsite": "موقعنا", + "pickDevice": "اختر الجهاز", + "pickHospital": "اختر العميل", + "pickUnite": "اختر القسم", + "policy": "سياسة", + "reason1": "لم يؤكد المهندس موعد الزيارة قبل ساعتين من وقت الطلب", + "reason2": "يقوم المهندس بتغيير موعد الزيارة دون التنسيق معي", + "reason3": "لم يحضر المهندس في التاريخ / الوقت", + "reason4": "لا يمكنني الوصول إلى المهندس عن طريق الهاتف أو الرسائل القصيرة", + "reason5": "هناك تأخير في إحضار قطع الغيار", + "repaired": "تم الاصلاح", + "repeated": "معاد", + "reportIssue": "الإبلاغ عن مشكلة", + "requestInformation": "معلومات طلب", + "searchBySn": "البحث بالرقم التسلسلي", + "serviceRequestInformation": "معلومات طلب الخدمة", + "serviceRequests": "طلبات الخدمة", + "shareAntherIssue": "مشاركة قضية أخرى", + "shareApp": "مشاركة التطبيق", + "sn": "الرقم التسلسلي", + "submit": "إرسال", + "trackServiceRequest": "تتبع طلبات الخدمة", + "twitter": "تويتر", + "underRepair": "تحت الاصلاح", + "unite": "قسم", + "uniteRequired": "القسم مطلوب", + "visitDate": "تاريخ الزيارة", + "whatsApp": "واتساب", + "workPerformed": "العمل انجز", + + "actualDate": "تاريخ الفعلي", + "done": "تم الانتهاء", + "expectDate": "التاريخ المتوقع", + "images": "الصور", + "imagesRequired": "الصور مطلوبة", + "noSerialNumberFound": "لا يوجد رقم تسلسلي", + "notYet": "ليس بعد", + "noVisitsFound": "لا توجد زيارات", + "onHold": "معلق", + "pickFromCamera": "الالتقاط من الكاميرا", + "pickFromGallery": "الاختيار من معرض صور", + "preventiveMaintenance": "الصيانة الوقائية", + "preventiveMaintenanceUpdatedSuccessfully": "تم تحديث الصيانة الوقائية بنجاح", + "regularVisits": "زيارات منتظمة", + "regularVisitsUpdatedSuccessfully": "تم تحديث الزيارات المنتظمة بنجاح", + "requiredStatus": "الحالة المطلوبة", + "updatePreventiveMaintenance": "تحديث الصيانة الوقائية", + "updateRegularVisits": "تحديث الزيارات المنتظمة", + "updateVisitsGroup": "تحديث مجموعة الزيارات", + "updatingDots": "تحديث ...", + "expectedVisitDate": "تاريخ الزيارة القادم", + "visitInformation": "معلومات الزيارة", + "travelingHours": "ساعات السفر", + "workingHours": "ساعات العمل", + "contactStatus": "تخصص", + "image": "صورة", + "pickImage": "اختر صورة", + "requiredImage": "الصورة مطلوبة", + "taskStatus": "حالة الطلب", + "activationAlert": "الحساب قيد المراجعة يرجى الانتظار حتى يتم تفعيله", + + "attachImage": "ارفق صورة", + "callLastSituation": "اخر موقف للطلب", + "customer": "العميل", + "editServiceReport": "تعديل التقرير الخدمة", + "invoiceCode": "كود الفتورة", + "invoiceNumber": "رقم الفتورة", + "newServiceReport": "تقرير خدمة جديد", + "number": "رقم", + "operatingHours": "ساعات العمل", + "partNumber": "رقم القطعة", + "quantity": "كمية", + "reasons": "الاسباب", + "reportStatus": "حالة التقرير", + "reportType": "نوع التقرير", + "callId": "رقم الطلب", + "requiredWord": "مطلوب", + "serviceType": "نوع الخدمة", + "workPreformed": "العمل المنجز", + "alert": "تنبيه", + "duplicateAlert": "تنبيه التكرار", + "duplicateAlertMessage": "هل أنت متأكد أنك تريد تكرار الطلب؟", + "duplicateRequest": "تكرار الطلب" +} \ No newline at end of file diff --git a/assets/subtitles/en_subtitle.json b/assets/subtitles/en_subtitle.json new file mode 100644 index 0000000..2353ebd --- /dev/null +++ b/assets/subtitles/en_subtitle.json @@ -0,0 +1,192 @@ +{ + "server_error_message": "Currently, Service not available", + "failed_request_message": "Failed to complete request", + "successful_request_message": "Request complete successfully", + "request_lock_message": "Wait until your request complete", + + "cancel": "Cancel", + "confirm": "Confirm", + "done": "Done", + "exit": "Exit", + "exit_alert": "Are you sure you want to exit?", + "sign_out": "Sign Out", + "logout_alert": "Are you sure you want to Sign Out?", + "language": "English", + + "name": "Name", + "email": "Email", + "phone_number": "Phone Number", + "password": "Password", + "confirm_password": "Confirm Password", + "sign_in": "Sign In", + "sign_up": "Sign Up", + "forget_password": "Forget Password", + "accept_terms_and_conditions": "Accept Terms And Conditions", + "email_validate_message": "Not valid email", + "name_validate_message": "name is required", + "password_validate_message": "at least 6 characters or numbers", + "confirm_password_validate_message": "Password and confirm password not match", + "phone_number_validate_message": "Not valid phone number", + "terms_and_conditions_validate_message": "Accept terms and conditions is required", + + "update": "Update", + "step": "Step", + "forget_password_with_mark": "Forget Password?", + "show_password": "show password", + "wrong_email_or_password": "Wrong email or password", + "email_exist": "Email exist", + "phone_number_exist": "Phone number exist", + "next": "Next", + "back": "Back", + "search": "Search", + "search_by_name": "Search by name", + "address": "Address", + "address_not_found": "Address not found", + "address_validate_message": "Address validate_message", + "data_not_found": "Data not found", + "description": "Description", + "description_not_found": "Description not found", + "description_validate_message": "Description can't be empty", + "edit": "Edit", + "email_not_found": "Email not found", + "from": "From", + "to": "To", + "link_not_found": "Link not found", + "name_not_found": "Name not found", + "phone_number_not_found": "Phone number not found", + "title": "Title", + "title_not_found": "Title not found", + "title_validate_message": "Title Can't be empty", + "url_not_found": "URL not found", + + "date": "Date", + "status": "Status", + "code": "Code", + "serialNumber": "Serial Number", + "add": "Add", + "brand": "Brand", + "clearSearch": "Clear Search", + "closed": "Closed", + "create": "Create", + "createServiceRequest": "Create Service Request", + "delete": "Delete", + "details": "Details", + "device": "Asset", + "deviceArName": "Asset Ar Name", + "deviceEnName": "Asset En Name", + "deviceImages": "Asset Images", + "deviceModel": "Asset Model", + "deviceName": "Asset Name", + "deviceRequired": "Asset Required", + "deviceSN": "Asset SN", + "engineerName": "Engineer Name", + "engineerPhone": "Engineer Phone", + "facebook": "facebook", + "faultDescription": "Fault Description", + "general": "General", + "hospital": "Client", + "hospitalRequired": "Client Required", + "hotLine": "Hot Line", + "jobSheetNumber": "Job Sheet Number", + "linkedIn": "linkedIn", + "maintenanceIssue": "Maintenance Issue", + "maintenanceIssueRequired": "maintenance Issue Required", + "maxImagesNumberIs5": "Maximum Images Number Is 5", + "model": "Model", + "nameExist": "Name Exist", + "newServiceRequest": "New Service Request", + "newWord": "New", + "noDateFound": "No Date Found", + "noDeviceFound": "No Device Found", + "noHospitalFound": "No Client Found", + "noModelFound": "No Model Found", + "noServiceRequestFound": "No Service Request Found", + "noSnFound": "No SN Found", + "notifications": "Notifications", + "notificationsNotFound": "Notifications Not Found", + "noUniteFound": "No Unit Found", + "ourWebsite": "Our Website", + "pickDevice": "Pick Device", + "pickHospital": "Pick Client", + "pickUnite": "Pick Unit", + "policy": "Policy", + "reason1": "The engineer didn't confirm visit date with 2 hours from the request time", + "reason2": "The engineer change the visit date without coordination with me", + "reason3": "the engineer didn't attend on date/time", + "reason4": "I can't reach engineer by phone or SMS", + "reason5": "There is delay ib bringing the spare parts", + "repaired": "Repaired", + "repeated": "Repeated", + "reportIssue": "Report a Issue", + "requestInformation": "request Information", + "searchBySn": "Search By Sn", + "serviceRequestInformation": "Service Request Information", + "serviceRequests": "Service Requests", + "shareAntherIssue": "share Another Issue", + "shareApp": "Share App", + "sn": "SN", + "submit": "Submit", + "trackServiceRequest": "Track Service Requests", + "twitter": "Twitter", + "underRepair": "Under Repair", + "unite": "Unit", + "uniteRequired": "Unit Required", + "visitDate": "Visit Date", + "whatsApp": "WhatsApp", + "workPerformed": "Work Performed", + + "actualDate": "Actual Date", + "done": "Done", + "expectDate": "Expect Date", + "images": "Images", + "imagesRequired": "Images Required", + "noSerialNumberFound": "No Serial Number Found", + "notYet": "Not Yet", + "noVisitsFound": "No Visits Found", + "onHold": "On Hold", + "pickFromCamera": "Pick From Camera", + "pickFromGallery": "Pick From Gallery", + "preventiveMaintenance": "Preventive Maintenance", + "preventiveMaintenanceUpdatedSuccessfully": "Preventive Maintenance Updated Successfully", + "regularVisits": "Regular Visits", + "regularVisitsUpdatedSuccessfully": "Regular Visits Updated Successfully", + "requiredStatus": "Required Status", + "updatePreventiveMaintenance": "Update Preventive Maintenance", + "updateRegularVisits": "Update Regular Visits", + "updateVisitsGroup": "Update Visits Group", + "updatingDots": "Updating ...", + "expectedVisitDate": "Next Visit Date", + "visitInformation": "Visit Information", + "travelingHours": "Traveling Hours", + "workingHours": "Working Hours", + "contactStatus": "Assigned To", + "image": "Image", + "pickImage": "Pick Image", + "requiredImage": "Image Required", + "taskStatus": "Task Status", + "activationAlert": "Account under reviewing please wait until be activated", + + "attachImage": "Attach Image", + "callLastSituation": "Call's Last Situation", + "customer": "Customer", + "editServiceReport": "Edit Service Report", + "invoiceCode": "Invoice Code", + "invoiceNumber": "Invoice Number", + "newServiceReport": "New Service Report", + "number": "Number", + "operatingHours": "Operating Hours", + "partNumber": "Part Number", + "quantity": "Quantity", + "reasons": "Reasons", + "reportStatus": "Report Status", + "reportType": "Report Type", + "callId": "Call Id", + "requiredWord": "required", + "serviceType": "Service Type", + "workPreformed": "Work Preformed", + + "alert": "Alert", + "duplicateAlert": "Duplicate Alert", + "duplicateAlertMessage": "Are you sure you want to duplicate request?", + "duplicateRequest": "Duplicate Request" +} \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..8d4492f --- /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 + 9.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5949063 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 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 */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 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 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase 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; + 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; + 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 = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.testSa; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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; + 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 = 9.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; + 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; + 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 = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.testSa; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.testSa; + 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 */ + 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 */; +} \ No newline at end of file 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..c87d15a --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + 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..70693e4 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} 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..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..4152ccf Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..562fa04 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..a6a1917 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..324b0db Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..53cc789 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..381ad0a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..fa50072 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..a6a1917 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..bb7246f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..783711d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000..b1ea3dc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 0000000..cb2d756 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..1fb76fd Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..3fa8b16 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..783711d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..ad9f039 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..b553aa4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..83e1907 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..e054ce8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..d81666a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0a32b49 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.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..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..4cb4d75 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Test Sa + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ATOMS + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + + 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/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart new file mode 100644 index 0000000..f09068a --- /dev/null +++ b/lib/controllers/api_routes/urls.dart @@ -0,0 +1,57 @@ +class URLs{ + URLs._(); + static const host2 = "http://194.163.164.213/atoms/api"; + static const host1 = "http://194.163.164.213/atoms/api"; + + // API Routes + static const login = "/handle/user/login"; // post + static const register = "/handle/create/user"; // post + static const updateProfile = "/update/user/profile"; // post + static const getHospitals = "/handle/return/all/clients"; // get + static const getDepartments = "/handle/return/all/departments"; // get + static const getEquipment = "/handle/return/all/client/equipments"; // get ?client=208051 + static const getServiceRequests = "/return/user/calls"; // get + + static const getPreventiveMaintenanceVisits = "/return/user/calibrations"; // get + static const updatePreventiveMaintenanceVisits = "/update/user/calibrations"; // get + + static const getRegularVisits = "/return/user/ppm"; // get + static const updateRegularVisits = "/update/user/ppm"; // get + + static const getSingleServiceRequest = "/return/call/information"; // get + static const getNotifications = "/return/user/notification"; // get + static const getRecentNotifications = "/return/user/recent/notification"; // get + static const createRequest = "/handle/create/request"; // get + static const createReport = "/handle/create/report/issue"; // get + static const updateRequestDate = "/handle/update/request"; // get + + // service report + static const createServiceReport = "/handle/create/service/report"; // get + static const updateServiceReport = "/handle/update/service/report"; // get + static const getServiceReport = "/handle/view/service/report"; // get + static const createDuplicatedReport = "/handle/duplicate/request"; // get + + static const getServiceReportReasons = "/return/service/report/reasons"; // get + static const getServiceReportTypes = "/return/service/report/type"; // get + static const getServiceReportStatus = "/return/service/report/status"; // get + static const getServiceReportLastCalls = "/return/call/last/situation"; // get + static const getServiceTypes = "/return/service/type"; // get + static const getPartNumber = "/handle/return/all/parts"; // get + + //gas refill + static const getGasTypes = "/return/gas/refill/types"; // get + static const getGasCylinderSize = "/return/gas/refill/size/cylinder"; // get + static const getGasStatus = "/return/gas/refill/status"; // get + static const requestGasRefill = "/create/gas/refill"; // get + static const updateGasRefill = "/update/gas/refill/"; // get + static const getGasRefill = "/search/gas/refill"; // get + + //device transfer + static const requestDeviceTransfer = "/create/transfer/asset"; // get + static const updateDeviceTransfer = "/update/transfer/asset"; // get + static const getDeviceTransfer = "/search/transfer/asset"; // get + + // employee + static const getEmployees = "/return/assigned/employee"; // get + +} \ No newline at end of file diff --git a/lib/controllers/http_status_manger/http_status_manger.dart b/lib/controllers/http_status_manger/http_status_manger.dart new file mode 100644 index 0000000..012a9a2 --- /dev/null +++ b/lib/controllers/http_status_manger/http_status_manger.dart @@ -0,0 +1,40 @@ +import 'package:test_sa/models/subtitle.dart'; +import 'package:meta/meta.dart'; + +class HttpStatusManger{ + static String getStatusMessage({ + @required int status, + @required Subtitle subtitle, + String messageFor400, + String messageFor200, + }){ + if(status == null) + // no status code - code error no need for subtitle + return "careful null status"; + if(status == -1) + // client's request in process + return subtitle.currentlyServiceNotAvailable; + if(status == -2){ + // client's request in process + return subtitle.waitUntilYourRequestComplete; + }else if(status >= 200 && status < 300){ + // client's request was successfully received + return messageFor200 ?? subtitle.requestCompleteSuccessfully; + } else if(status >= 400 && status < 500){ + // client's request have error + switch(status){ + case 400: + return messageFor400 ?? subtitle.failedToCompleteRequest; + default: + return subtitle.failedToCompleteRequest; + } + } else if(status >= 500){ + // server error + return subtitle.currentlyServiceNotAvailable; + } else { + // no error match so return default error + return subtitle.failedToCompleteRequest; + } + + } +} \ No newline at end of file diff --git a/lib/controllers/localization/localization.dart b/lib/controllers/localization/localization.dart new file mode 100644 index 0000000..fafc8aa --- /dev/null +++ b/lib/controllers/localization/localization.dart @@ -0,0 +1,48 @@ +import 'dart:convert'; + +import 'package:test_sa/models/subtitle.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + + +class AppLocalization { + AppLocalization(this.locale); + + final Locale locale; + static AppLocalization of(BuildContext context) { + return Localizations.of(context, AppLocalization); + } + + Subtitle _subtitle; + Subtitle get subtitle => _subtitle; + + Future load() async { + String jsonStringValues = + await rootBundle.loadString('assets/subtitles/${locale.languageCode}_subtitle.json'); + _subtitle = Subtitle.fromJson(json.decode(jsonStringValues)); + } + + // static member to have simple access to the delegate from Material App + static const LocalizationsDelegate delegate = + _DemoLocalizationsDelegate(); +} + +class _DemoLocalizationsDelegate + extends LocalizationsDelegate { + const _DemoLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) { + return ['en','ar'].contains(locale.languageCode); + } + + @override + Future load(Locale locale) async { + AppLocalization localization = new AppLocalization(locale); + await localization.load(); + return localization; + } + + @override + bool shouldReload(LocalizationsDelegate old) => false; +} \ No newline at end of file diff --git a/lib/controllers/notification/firebase_notification_manger.dart b/lib/controllers/notification/firebase_notification_manger.dart new file mode 100644 index 0000000..19ef543 --- /dev/null +++ b/lib/controllers/notification/firebase_notification_manger.dart @@ -0,0 +1,83 @@ +import 'dart:convert'; + +import 'package:test_sa/models/app_notification.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; + +import 'notification_manger.dart'; + +class FirebaseNotificationManger{ + + static FirebaseMessaging messaging = FirebaseMessaging.instance; + static String token; + + static Future getToken() async { + NotificationSettings settings = await messaging.requestPermission( + alert: true, + announcement: false, + badge: true, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: true, + ); + + if(settings.authorizationStatus == AuthorizationStatus.authorized){ + token = await messaging.getToken(); + } + return token; + } + + static initialized(BuildContext context) async { + await Firebase.initializeApp(); + NotificationSettings settings; + try{ + settings = await messaging.requestPermission( + alert: true, + announcement: false, + badge: true, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: true, + ); + }catch(error){ + return; + } + + if(settings.authorizationStatus != AuthorizationStatus.authorized){ + return; + } + + // Also handle any interaction when the app is in the background via a + // Stream listener + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + AppNotification notification = AppNotification.fromJson(message.data); + if(notification.path == null + || notification.path.isEmpty) + return; + Navigator.pushNamed( + context, + notification.path, + arguments: notification.requestId + ); + }); + + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + AppNotification notification = AppNotification.fromJson(message.data); + NotificationManger.showNotification( + title: message.notification.title, + subtext: message.notification.body, + hashcode: int.tryParse(notification.requestId ?? "") ?? 1, + payload: json.encode(message.data) + ); + return; + }); + } +} + +Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { +} \ No newline at end of file diff --git a/lib/controllers/notification/notification_manger.dart b/lib/controllers/notification/notification_manger.dart new file mode 100644 index 0000000..8c0de71 --- /dev/null +++ b/lib/controllers/notification/notification_manger.dart @@ -0,0 +1,85 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +class NotificationManger{ + // private constructor to avoid create object + NotificationManger._(); + + static FlutterLocalNotificationsPlugin localNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + /// initialisation setting for all platform + /// onNotificationPressed action when notification pressed to open tap + /// onIOSNotificationPressed action when notification pressed + /// to open tap in iOS versions older than 10 + static initialisation( + Function(NotificationResponse) onNotificationPressed, + DidReceiveLocalNotificationCallback onIOSNotificationPressed + ) async { + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + // initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('app_icon',); + + final DarwinInitializationSettings initializationSettingsDarwin = + DarwinInitializationSettings( + onDidReceiveLocalNotification: onIOSNotificationPressed); + + final InitializationSettings initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsDarwin, + macOS: initializationSettingsDarwin); + + await flutterLocalNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: onNotificationPressed); + } + + // push new notification + static Future showNotification( + { + @required String title, + @required String subtext, + @required int hashcode, + String payload + }) async { + + const AndroidNotificationDetails androidChannel = AndroidNotificationDetails( + 'com.newtrack.testsa', + 'Test SA', + channelDescription:'Push notification service for Test SA', + importance: Importance.max, + priority: Priority.max, + playSound: true, + channelShowBadge: true, + enableLights: true, + visibility: NotificationVisibility.public, + ledColor: AColors.secondaryColor, + ledOnMs: 1, + ledOffMs: 0, + enableVibration: true, + groupKey: 'com.newtrack.testsa', + ); + + const DarwinNotificationDetails iosNotificationDetails = + DarwinNotificationDetails( + categoryIdentifier: "testSA", + ); + + const platformChannel = NotificationDetails( + android: androidChannel, + iOS: iosNotificationDetails, + macOS: iosNotificationDetails, + ); + + await localNotificationsPlugin.show( + hashcode, + title, + subtext, + platformChannel, + payload: payload, + ); + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/departments_provider.dart b/lib/controllers/providers/api/departments_provider.dart new file mode 100644 index 0000000..1b66771 --- /dev/null +++ b/lib/controllers/providers/api/departments_provider.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/department.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; + +class DepartmentsProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + departments = null; + stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int stateCode; + + + // contain user data + // when user not login or register _user = null + List departments; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool isLoading; + + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getDepartment (String host) async { + if(isLoading == true) + return -2; + isLoading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse(host + URLs.getDepartments), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + } catch(error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List listJson = json.decode(utf8.decode(response.bodyBytes).replaceAll("\\", "")); + departments = listJson.map((department) => Department.fromJson(department)).toList(); + } + isLoading = false; + notifyListeners(); + return response.statusCode; + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/device_transfer_provider.dart b/lib/controllers/providers/api/device_transfer_provider.dart new file mode 100644 index 0000000..e646a6d --- /dev/null +++ b/lib/controllers/providers/api/device_transfer_provider.dart @@ -0,0 +1,206 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/models/department.dart'; +import 'package:test_sa/models/device/device_transfer.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/device/device_transfer_info.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_model.dart'; +import 'package:test_sa/models/hospital.dart'; +import 'package:test_sa/models/issue.dart'; +import 'package:test_sa/models/service_report.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/service_request/service_request_search.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; + +class DeviceTransferProvider extends ChangeNotifier{ + + // number of items call in each request + final pageItemNumber = 50; + + //reset provider data + void reset(){ + items = null; + nextPage = true; + stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int stateCode; + + // true if there is next page in product list and false if not + bool nextPage = true; + + // list of user requests + List items; + + // when requests in-process _loading = true + // done _loading = true + // failed _loading = false + bool isLoading; + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getRequests ({ + @required String host, + @required User user, + }) async { + if(isLoading == true) + return -2; + isLoading = true; + + // isLoading = false; + // stateCode = 200; + // items = []; + // items.addAll(List.generate(20, (index) => DeviceTransfer( + // title: "ddddd", + // id: "5", + // device: Device(id: "1",brand: "brand",model: "model"), + // destinationClient: Hospital(name: "hospital name",id: "1"), + // destinationDepartment: Department(id: "5",name: "destination Department"), + // senderDepartment: Department(id: "5",name: "sender Department"), + // userId: "5" + // ))); + // notifyListeners(); + // return 200; + Response response; + try{ + print( Uri.parse( + "$host${URLs.getDeviceTransfer}?uid=${user.id}" + "&token=${user.token}&page=${(items?.length ?? 0) ~/pageItemNumber}" + ),); + response = await get( + Uri.parse( + "$host${URLs.getDeviceTransfer}?uid=${user.id}" + "&token=${user.token}&page=${(items?.length ?? 0) ~/pageItemNumber}" + ), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + stateCode = response.statusCode; + if(stateCode >= 200 && stateCode < 300) { + // client's request was successfully received + List listJson = json.decode(utf8.decode(response.bodyBytes).replaceAll("\\", "")); + List itemsPage = listJson.map( + (request) => DeviceTransfer.fromJson(request)).toList(); + items ??= []; + items.addAll(itemsPage); + if(itemsPage.length == pageItemNumber){ + nextPage = true; + }else{ + nextPage = false; + } + } + isLoading = false; + notifyListeners(); + return response.statusCode; + + } catch(error) { + print(error); + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + + } + + Future createRequest ({ + @required String host, + @required User user, + @required DeviceTransfer model, + }) async { + Map body = { + "uid": user.id.toString(), + "token": user.token ?? "", + "serial_id": model.device.id ?? "", + "destination_client": model.receiver.client.id ?? "", + "destination_department": model.receiver.department.id ?? "", + }; + + + Response response; + try{ + response = await post( + Uri.parse( + host+URLs.requestDeviceTransfer), + body: body, + ); + + print(response.body); + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + if(items != null) { + items.insert( + 0, + DeviceTransfer.fromJson( + json.decode(utf8.decode(response.bodyBytes))[0] + ) + ); + notifyListeners(); + } + } + return response.statusCode; + + } catch(error) { + print(error); + return -1; + } + + } + + Future updateRequest ({ + @required String host, + @required User user, + @required bool isSender, + @required String requestId, + @required DeviceTransfer oldModel, + @required DeviceTransferInfo newModel, + }) async { + + Map body = { + "uid": user.id.toString(), + "token": user.token ?? "", + "current_user": user.id ?? "", + }; + + body.addAll(newModel.toJson(isSender)); + print(body); + Response response; + try{ + print(Uri.parse("$host${URLs.updateDeviceTransfer}/$requestId"),); + response = await post( + Uri.parse("$host${URLs.updateDeviceTransfer}/$requestId"), + body: body, + ); + + print(response.body); + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + reset(); + // oldModel.fromDeviceTransfer( + // DeviceTransfer.fromJson( + // json.decode(utf8.decode(response.bodyBytes))[0] + // ) + // ); + notifyListeners(); + } + return response.statusCode; + + } catch(error) { + print(error); + return -1; + } + + } +} \ No newline at end of file diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart new file mode 100644 index 0000000..808421d --- /dev/null +++ b/lib/controllers/providers/api/devices_provider.dart @@ -0,0 +1,145 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; + +class DevicesProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _devices = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + List _devices; + List get devices => _devices; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getEquipment ({ + @required String host, + @required User user, + @required String hospitalId + }) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse(host + URLs.getEquipment+"?client=$hospitalId"), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List equipmentListJson = json.decode(utf8.decode(response.bodyBytes)); + _devices = equipmentListJson.map((device) => Device.fromJson(device)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future> getDevicesList ({ + @required String host, + @required User user, + @required String hospitalId, + @required String title}) async { + Response response; + try{ + response = await get( + Uri.parse(host + URLs.getEquipment+"?client=$hospitalId" + + ( title == null || title.isEmpty ? "" : "&name=$title" )), + ); + _stateCode = response.statusCode; + List _page = []; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _page = categoriesListJson.map((device) => Device.fromJson(device)).toList(); + } + return _page; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return []; + } + + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future> getDevicesListBySN ({ + @required String host, + @required User user, + @required String hospitalId, + @required String sn + }) async { + Response response; + try{ + response = await get( + Uri.parse(host + URLs.getEquipment+"?client=$hospitalId" + + ( sn == null || sn.isEmpty ? "" : "&serial_qr=$sn" )), + ); + + _stateCode = response.statusCode; + List _page = []; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + print(categoriesListJson.length); + _page = categoriesListJson.map((device) => Device.fromJson(device)).toList(); + print(_page.length); + } + return _page; + } catch(error) { + print(error); + _loading = false; + _stateCode = -1; + notifyListeners(); + return []; + } + + } +} \ No newline at end of file diff --git a/lib/controllers/providers/api/gas_refill_provider.dart b/lib/controllers/providers/api/gas_refill_provider.dart new file mode 100644 index 0000000..e0f90bb --- /dev/null +++ b/lib/controllers/providers/api/gas_refill_provider.dart @@ -0,0 +1,191 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_model.dart'; +import 'package:test_sa/models/issue.dart'; +import 'package:test_sa/models/service_report.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/service_request/service_request_search.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; + +class GasRefillProvider extends ChangeNotifier{ + + // number of items call in each request + final pageItemNumber = 50; + + //reset provider data + void reset(){ + items = null; + nextPage = true; + stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int stateCode; + + // true if there is next page in product list and false if not + bool nextPage = true; + + // list of user requests + List items; + + // when requests in-process _loading = true + // done _loading = true + // failed _loading = false + bool isLoading; + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getRequests ({ + @required String host, + @required User user, + }) async { + if(isLoading == true) + return -2; + isLoading = true; + Response response; + try{ + print( Uri.parse( + "$host${URLs.getGasRefill}?uid=${user.id}" + "&token=${user.token}&page=${(items?.length ?? 0) ~/pageItemNumber}" + )); + response = await get( + Uri.parse( + "$host${URLs.getGasRefill}?uid=${user.id}" + "&token=${user.token}&page=${(items?.length ?? 0) ~/pageItemNumber}" + ), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + print(response.body); + stateCode = response.statusCode; + if(stateCode >= 200 && stateCode < 300) { + // client's request was successfully received + List requestsListJson = json.decode(utf8.decode(response.bodyBytes)); + List itemsPage = requestsListJson.map( + (request) => GasRefillModel.fromJson(request)).toList(); + items ??= []; + items.addAll(itemsPage); + if(itemsPage.length == pageItemNumber){ + nextPage = true; + }else{ + nextPage = false; + } + } + isLoading = false; + notifyListeners(); + return response.statusCode; + + } catch(error) { + print(error); + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + + } + + Future createModel ({ + @required String host, + @required User user, + @required GasRefillModel model, + }) async { + Map body = { + "uid": user.id.toString(), + "token": user.token ?? "", + "title": model.title ?? "", + "status": "0",//model.status.value.toString(), + }; + + body["details"] = jsonEncode(model.details.map((model) => { + "type": model.type.id.toString(), + "size": model.cylinderSize.id.toString(), + "requsted_qty": model.requestedQuantity.toString(), + }).toList()); + + print(body); + Response response; + try{ + response = await post( + Uri.parse( + host+URLs.requestGasRefill), + body: body, + ); + + print(response.body); + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + if(items != null) { + items.insert( + 0, + GasRefillModel.fromJson( + json.decode(utf8.decode(response.bodyBytes))[0] + ) + ); + notifyListeners(); + } + } + return response.statusCode; + + } catch(error) { + print(error); + return -1; + } + + } + + Future updateModel ({ + @required String host, + @required User user, + @required GasRefillModel oldModel, + @required GasRefillModel newModel, + }) async { + Map body = { + "uid": user.id.toString(), + "token": user.token ?? "", + "title": newModel.title ?? "", + "status": newModel.status.id.toString(), + }; + + body["details"] = jsonEncode(newModel.details.map((model) => { + "type": model.type.id.toString(), + "size": model.cylinderSize.id.toString(), + "requsted_qty": model.requestedQuantity.toString(), + "deliverd_qty": model.deliveredQuantity.toString(), + }).toList()); + + print(body); + print(Uri.parse("$host${URLs.updateGasRefill}/${newModel.id}"),); + Response response; + try{ + response = await post( + Uri.parse("$host${URLs.updateGasRefill}/${newModel.id}"), + body: body, + ); + + print(response.body); + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + oldModel.fromGasRefillModel(newModel); + notifyListeners(); + } + return response.statusCode; + + } catch(error) { + print(error); + return -1; + } + + } +} \ No newline at end of file diff --git a/lib/controllers/providers/api/hospitals_provider.dart b/lib/controllers/providers/api/hospitals_provider.dart new file mode 100644 index 0000000..3ead9f6 --- /dev/null +++ b/lib/controllers/providers/api/hospitals_provider.dart @@ -0,0 +1,133 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/hospital.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class HospitalsProvider extends ChangeNotifier{ + + // number of items call in each request + final pageItemNumber = 50; + + //reset provider data + void reset(){ + _hospitals = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // true if there is next page in product list and false if not + bool _nextPage = true; + bool get nextPage => _nextPage; + + // contain user data + // when user not login or register _user = null + List _hospitals; + List get hospitals => _hospitals; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getHospitals ({String host,User user,String title}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + print( Uri.parse( + host + URLs.getHospitals + + "?page=${(_hospitals?.length ?? 0) ~/pageItemNumber}" + + ( title == null || title.isEmpty ? "" : "&name=$title" ))); + response = await get( + Uri.parse( + host + URLs.getHospitals + + "?page=${(_hospitals?.length ?? 0) ~/pageItemNumber}" + + ( title == null || title.isEmpty ? "" : "&name=$title" ) + ), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + List _page = categoriesListJson.map((category) => Hospital.fromJson(category)).toList(); + if(hospitals == null) + _hospitals = []; + + _hospitals.addAll(_page); + if(_page.length >= pageItemNumber){ + _nextPage = true; + }else{ + _nextPage = false; + } + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future> getHospitalsList ({String host,User user,String title}) async { + Response response; + try{ + + response = await get( + Uri.parse(host + URLs.getHospitals + + ( title == null || title.isEmpty ? "" : "?name=$title" )), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + _stateCode = response.statusCode; + + List _page = []; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _page = categoriesListJson.map((category) => Hospital.fromJson(category)).toList(); + } + return _page; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return []; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/notifications_provider.dart b/lib/controllers/providers/api/notifications_provider.dart new file mode 100644 index 0000000..0bea5f2 --- /dev/null +++ b/lib/controllers/providers/api/notifications_provider.dart @@ -0,0 +1,127 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/app_notification.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; + +class NotificationsProvider extends ChangeNotifier{ + + // number of items call in each request + final pageItemNumber = 20; + + //reset provider data + void reset(){ + notifications = null; + nextPage = true; + stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int stateCode; + + // true if there is next page in product list and false if not + bool nextPage = true; + + // list of user requests + List notifications; + + // when requests in-process _loading = true + // done _loading = true + // failed _loading = false + bool isLoading; + + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getNotifications ({ + @required String host, + @required User user, + @required String hospitalId, + }) async { + if(isLoading == true) + return -2; + isLoading = true; + notifyListeners(); + Response response; + // userId = 397.toString(); // testing id to view data + try{ + response = await get( + Uri.parse(host+URLs.getNotifications + +"?uid=${user.id}" + "&token=${user.token}" + "&page=${(notifications?.length ?? 0) ~/pageItemNumber}"), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List requestsListJson = json.decode(utf8.decode(response.bodyBytes)); + List _serviceRequestsPage = requestsListJson.map( + (request) => AppNotification.fromJson(request)).toList(); + if(notifications == null) + notifications = []; + notifications.addAll(_serviceRequestsPage); + if(_serviceRequestsPage.length == pageItemNumber){ + nextPage = true; + }else{ + nextPage = false; + } + } + isLoading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future> getRecentNotifications ({ + @required String host, + @required User user, + }) async { + Response response; + //userId = 397.toString(); // testing id to view data + + try{ + response = await get( + Uri.parse(host+URLs.getNotifications + +"?uid=${user.id}&token=${user.token}"), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List requestsListJson = json.decode(utf8.decode(response.bodyBytes)); + List _recentNotifications = requestsListJson.map( + (request) => AppNotification.fromJson(request)).toList(); + return _recentNotifications; + } + return null; + + } catch(error) { + return null; + } + + } +} \ No newline at end of file diff --git a/lib/controllers/providers/api/parts_provider.dart b/lib/controllers/providers/api/parts_provider.dart new file mode 100644 index 0000000..061e3da --- /dev/null +++ b/lib/controllers/providers/api/parts_provider.dart @@ -0,0 +1,130 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/part.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class PartsProvider extends ChangeNotifier{ + + // number of items call in each request + final pageItemNumber = 50; + + //reset provider data + void reset(){ + _parts = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // true if there is next page in product list and false if not + bool _nextPage = true; + bool get nextPage => _nextPage; + + // contain user data + // when user not login or register _user = null + List _parts; + List get parts => _parts; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getParts ({String host,User user,String title}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse( + host + URLs.getPartNumber + + "?page=${(_parts?.length ?? 0) ~/pageItemNumber}" + + ( title == null || title.isEmpty ? "" : "&name=$title" ) + ), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + List _page = categoriesListJson.map((part) => Part.fromJson(part)).toList(); + if(parts == null) + _parts = []; + + _parts.addAll(_page); + if(_page.length >= pageItemNumber){ + _nextPage = true; + }else{ + _nextPage = false; + } + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future> getPartsList ({String host,User user,String title}) async { + Response response; + try{ + + response = await get( + Uri.parse( + host + URLs.getPartNumber + + ( title == null || title.isEmpty ? "" : "?name=$title" ) + ), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + _stateCode = response.statusCode; + List _page = []; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _page = categoriesListJson.map((part) => Part.fromJson(part)).toList(); + } + return _page; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return []; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/preventive_maintenance_visits_provider.dart b/lib/controllers/providers/api/preventive_maintenance_visits_provider.dart new file mode 100644 index 0000000..14103fd --- /dev/null +++ b/lib/controllers/providers/api/preventive_maintenance_visits_provider.dart @@ -0,0 +1,137 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/models/visits/visit.dart'; +import 'package:test_sa/models/visits/visits_group.dart'; +import 'package:test_sa/models/visits/visits_search.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; + +class PreventiveMaintenanceVisitsProvider extends ChangeNotifier{ + + // number of items call in each request + final pageItemNumber = 50; + + //reset provider data + void reset(){ + visits = null; + nextPage = true; + stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int stateCode; + + // true if there is next page in product list and false if not + bool nextPage = true; + + // list of user requests + List visits; + + // when requests in-process _loading = true + // done _loading = true + // failed _loading = false + bool isLoading; + + VisitsSearch visitsSearch = VisitsSearch(); + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getVisits ({ + @required String host, + @required User user, + // VisitsSearch visitsSearch, + }) async { + if(isLoading == true) + return -2; + isLoading = true; + Response response; + //userId = 397.toString(); // testing id to view data + try{ + response = await get( + Uri.parse( + host + URLs.getPreventiveMaintenanceVisits + +"?uid=${user.id}" + "&token=${user.token}" + "&page=${(visits?.length ?? 0) ~/pageItemNumber}" + +visitsSearch?.toSearchString() ?? "" + ), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + } catch(error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List requestsListJson = json.decode(utf8.decode(response.bodyBytes)); + List _visits = requestsListJson.map( + (request) => Visit.fromJson(request)).toList(); + if(visits == null) + visits = []; + visits.addAll(_visits); + if(_visits.length == pageItemNumber){ + nextPage = true; + }else{ + nextPage = false; + } + } + isLoading = false; + notifyListeners(); + return response.statusCode; + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future updateGroupOfVisits ({ + @required String host, + @required User user, + VisitsGroup group, + }) async { + Response response; + Map body = group.toJson(); + body["token"] = user.token ?? ""; + body["uid"] = user.id; + //userId = 397.toString(); // testing id to view data + try{ + response = await post( + Uri.parse( + host+URLs.updatePreventiveMaintenanceVisits + ), + body: body, + ); + + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + group.visits.forEach((visit) { + visit.status = group.status; + visit.actualDate = group.date.toString().split(" ").first; + }); + group.visits.clear(); + notifyListeners(); + } + + return response.statusCode; + } catch(error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} \ No newline at end of file diff --git a/lib/controllers/providers/api/regular_visits_provider.dart b/lib/controllers/providers/api/regular_visits_provider.dart new file mode 100644 index 0000000..1db8979 --- /dev/null +++ b/lib/controllers/providers/api/regular_visits_provider.dart @@ -0,0 +1,141 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/models/visits/visit.dart'; +import 'package:test_sa/models/visits/visits_group.dart'; +import 'package:test_sa/models/visits/visits_search.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; + +class RegularVisitsProvider extends ChangeNotifier{ + + // number of items call in each request + final pageItemNumber = 50; + + //reset provider data + void reset(){ + visits = null; + nextPage = true; + stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int stateCode; + + // true if there is next page in product list and false if not + bool nextPage = true; + + // list of user requests + List visits; + + // when requests in-process _loading = true + // done _loading = true + // failed _loading = false + bool isLoading; + + VisitsSearch visitsSearch = VisitsSearch(); + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getVisits ({ + @required String host, + @required User user, + // VisitsSearch visitsSearch, + }) async { + if(isLoading == true) + return -2; + isLoading = true; + Response response; + //userId = 397.toString(); // testing id to view data + try{ + response = await get( + Uri.parse( + host+URLs.getRegularVisits + +"?uid=${user.id}" + "&token=${user.token}" + "&page=${(visits?.length ?? 0) ~/pageItemNumber}" + +visitsSearch?.toSearchString() ?? "" + ), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + } catch(error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + try{ + List requestsListJson = json.decode(utf8.decode(response.bodyBytes)); + List _visits = requestsListJson.map( + (request) => Visit.fromJson(request) + ).toList(); + if(visits == null) + visits = []; + visits.addAll(_visits); + if(_visits.length == pageItemNumber){ + nextPage = true; + }else{ + nextPage = false; + } + }catch(error){ + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + + } + isLoading = false; + notifyListeners(); + return response.statusCode; + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future updateGroupOfVisits ({ + @required String host, + @required User user, + VisitsGroup group, + }) async { + Response response; + Map body = group.toJson(); + body["token"] = user.token ?? ""; + body["uid"] = user.id; + //userId = 397.toString(); // testing id to view data + try{ + response = await post( + Uri.parse(host+URLs.updateRegularVisits), + body: body, + ); + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + reset(); + notifyListeners(); + } + + return response.statusCode; + } catch(error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } + + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/service_requests_provider.dart b/lib/controllers/providers/api/service_requests_provider.dart new file mode 100644 index 0000000..b5ab602 --- /dev/null +++ b/lib/controllers/providers/api/service_requests_provider.dart @@ -0,0 +1,436 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/models/issue.dart'; +import 'package:test_sa/models/service_report.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/service_request/service_request_search.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; + +class ServiceRequestsProvider extends ChangeNotifier{ + + // number of items call in each request + final pageItemNumber = 50; + + //reset provider data + void reset(){ + serviceRequests = null; + nextPage = true; + stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int stateCode; + + // true if there is next page in product list and false if not + bool nextPage = true; + + // list of user requests + List serviceRequests; + + // when requests in-process _loading = true + // done _loading = true + // failed _loading = false + bool isLoading; + + ServiceRequestSearch search = ServiceRequestSearch(); + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getRequests ({ + @required String host, + @required User user, + @required String hospitalId, + }) async { + if(isLoading == true) + return -2; + isLoading = true; + Response response; + try{ + print( Uri.parse( + host+URLs.getServiceRequests + +"?uid=${user.id}" + +(hospitalId == null? "" :"&client_nid=$hospitalId") + +"&token=${user.token}" + "&page=${(serviceRequests?.length ?? 0) ~/pageItemNumber}" + +search?.toSearchString() ?? "" + )); + response = await get( + Uri.parse( + host+URLs.getServiceRequests + +"?uid=${user.id}" + +(hospitalId == null? "" :"&client_nid=$hospitalId") + +"&token=${user.token}" + "&page=${(serviceRequests?.length ?? 0) ~/pageItemNumber}" + +search?.toSearchString() ?? "" + ), + headers: { + "Content-Type":"application/json; charset=utf-8" + } + ); + print(response.body); + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List requestsListJson = json.decode(utf8.decode(response.bodyBytes).replaceAll("\\", "")); + List _serviceRequestsPage = requestsListJson.map( + (request) => ServiceRequest.fromJson(request)).toList(); + if(serviceRequests == null) + serviceRequests = []; + serviceRequests.addAll(_serviceRequestsPage); + if(_serviceRequestsPage.length == pageItemNumber){ + nextPage = true; + }else{ + nextPage = false; + } + } + isLoading = false; + notifyListeners(); + return response.statusCode; + + } catch(error) { + print(error); + isLoading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + + } + + Future getSingleServiceRequest({ + @required String requestId, + @required String host, + @required User user, + @required Subtitle subtitle, + }) async { + String userData = ''; + if(user != null){ + userData += "&uid="+user.id; + userData += "&token="+user.token; + } + + Response response; + try{ + response = await get( + Uri.parse( + host+URLs.getSingleServiceRequest + +'?call_nid=$requestId' + '$userData',) + ); + }catch(error){ + throw(HttpStatusManger.getStatusMessage( + status: -1, subtitle: subtitle)); + } + + // If the call to the server was successful, parse the JSON. + if (response.statusCode >= 200 && response.statusCode < 300){ + // If the call to the server was successful, parse the JSON. + List jsonList = json.decode(utf8.decode(response.bodyBytes).replaceAll("\\", "")); + List _requests = jsonList.map((i) => ServiceRequest.fromJson(i)).toList(); + return _requests[0]; + }else{ + throw(HttpStatusManger.getStatusMessage( + status: response.statusCode, subtitle: subtitle)); + } + + } + + Future createRequest ({ + @required String host, + @required User user, + @required ServiceRequest serviceRequest, + }) async { + + var body = { + "uid": user.id, + "token": user.token ?? "", + "sn_id": serviceRequest.deviceId ?? "", + "date": (DateTime.now().millisecondsSinceEpoch).toString(), + "client": user.hospital.id ?? '', + "complaint": serviceRequest.maintenanceIssue, + "image": json.encode(serviceRequest.devicePhotos), + }; + if(serviceRequest.audio != null){ + body["audio"] = serviceRequest.audio; + } + print(body); + Response response; + try{ + response = await post( + Uri.parse( + host+URLs.createRequest), + body: body, + ); + + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + if(serviceRequests != null) + serviceRequests.insert( + 0, + ServiceRequest.fromJson( + json.decode(utf8.decode(response.bodyBytes))[0] + ) + ); + notifyListeners(); + } + return response.statusCode; + + } catch(error) { + print(error); + return -1; + } + + } + + Future createIssueReport ({ + @required String host, + @required User user, + @required Issue issue, + }) async { + Response response; + Map body = issue.toMap(); + body["uid"] = user.id; + body["token"] = user.token; + try{ + response = await post( + Uri.parse(host+URLs.createReport), + body: body, + ); + + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + + } + return response.statusCode; + + } catch(error) { + return -1; + } + + } + + Future updateDate ({ + @required String host, + @required User user, + @required String newDate, + @required Status employee, + @required ServiceRequest request, + }) async { + Response response; + Map body = {}; + body["uid"] = user.id; + body["token"] = user.token; + body["nid"] = request.id; + if(newDate != null) body["date"] = newDate; + if(employee != null) body["ass_emp"] = employee.id.toString(); + print(body); + try{ + response = await post( + Uri.parse( + host+URLs.updateRequestDate), + body: body, + ); + print(response.body); + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + request.engineerName = employee.label; + notifyListeners(); + } + return response.statusCode; + + } catch(error) { + return -1; + } + + } + + Future createServiceReport ({ + @required String host, + @required User user, + @required ServiceReport report, + @required ServiceRequest request, + }) async { + Response response; + Map body = report.toMap(); + body["uid"] = user.id; + body["token"] = user.token; + body["job_id"] = request.id; + try{ + response = await post( + Uri.parse( + host+URLs.createServiceReport), + body: body, + ); + + stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + reset(); + notifyListeners(); + } + return response.statusCode; + + } catch(error) { + + return -1; + } + + } + + Future createDuplicatedReport ({ + @required String host, + @required User user, + @required ServiceRequest request, + }) async { + Response response; + String userData = ''; + if(user != null){ + userData += "&uid="+user.id; + userData += "&token="+user.token; + } + + try{ + response = await get( + Uri.parse( + host+URLs.createDuplicatedReport + +"?nid=${request.id}" + +userData), + ); + + stateCode = response.statusCode; + + if(response.statusCode >= 200 && response.statusCode < 300) { + reset(); + notifyListeners(); + } + return response.statusCode; + + } catch(error) { + + return -1; + } + + } + + Future updateServiceReport ({ + @required String host, + @required User user, + @required ServiceReport report, + @required ServiceRequest request, + }) async { + Response response; + Map body = report.toMap(); + body["uid"] = user.id; + body["token"] = user.token; + body["job_id"] = request.id; + body["report_id"] = request.reportID; + try{ + response = await post( + Uri.parse( + host+URLs.updateServiceReport), + body: body, + ); + stateCode = response.statusCode; + + if(response.statusCode >= 200 && response.statusCode < 300) { + reset(); + notifyListeners(); + } + return response.statusCode; + + } catch(error) { + + return -1; + } + + } + + Future updateServiceReportTimer ({ + @required String host, + @required User user, + @required TimerModel timer, + @required ServiceRequest request, + }) async { + Response response; + Map body = {}; + body["uid"] = user.id; + body["token"] = user.token; + body["job_id"] = request.id; + body["start_time"] = (timer.startAt.millisecondsSinceEpoch / 1000).toStringAsFixed(0); + body["end_time"] = (timer.endAt.millisecondsSinceEpoch / 1000).toStringAsFixed(0); + body["working_hours"] = (timer.durationInSecond / 60 / 60).toStringAsFixed(5); + body["report_id"] = request.reportID; + try{ + response = await post( + Uri.parse( + host+URLs.updateServiceReport), + body: body, + ); + print(response.body); + //stateCode = response.statusCode; + + if(response.statusCode >= 200 && response.statusCode < 300) { + // reset(); + // notifyListeners(); + } + return response.statusCode; + + } catch(error) { + + return -1; + } + + } + + Future getSingleServiceReport({ + @required String reportId, + @required String host, + @required User user, + @required Subtitle subtitle, + }) async { + String userData = ''; + if(user != null){ + userData += "&uid="+user.id; + userData += "&token="+user.token; + } + + Response response; + try{ + + response = await get( + Uri.parse( + host+URLs.getServiceReport + +'?report_id=$reportId' + '$userData',) + ); + + }catch(error){ + throw(HttpStatusManger.getStatusMessage( + status: -1, subtitle: subtitle)); + } + + // If the call to the server was successful, parse the JSON. + if (response.statusCode >= 200 && response.statusCode < 300){ + // If the call to the server was successful, parse the JSON. + return ServiceReport.fromJson( + json.decode(utf8.decode(response.bodyBytes)),reportId); + }else{ + throw(HttpStatusManger.getStatusMessage( + status: response.statusCode, subtitle: subtitle)); + } + + } + + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/employee/employee_provider.dart b/lib/controllers/providers/api/status_drop_down/employee/employee_provider.dart new file mode 100644 index 0000000..7c6a675 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/employee/employee_provider.dart @@ -0,0 +1,75 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class EmployeesProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _items = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _items; + List get items => _items; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getData ({String host,User user}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse( + host + URLs.getEmployees), + ); + print(response.body); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _items = categoriesListJson.map((type) => Status.fromJson(type)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/gas_refill/gas_cylinder_size_provider.dart b/lib/controllers/providers/api/status_drop_down/gas_refill/gas_cylinder_size_provider.dart new file mode 100644 index 0000000..a453378 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/gas_refill/gas_cylinder_size_provider.dart @@ -0,0 +1,73 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class GasCylinderSizesProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _items = null; + _loading = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _items; + List get items => _items; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getData ({String host,User user,}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse(host + URLs.getGasCylinderSize), + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _items = categoriesListJson.map((item) => Status.fromJson(item)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/gas_refill/gas_status_provider.dart b/lib/controllers/providers/api/status_drop_down/gas_refill/gas_status_provider.dart new file mode 100644 index 0000000..60d5db9 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/gas_refill/gas_status_provider.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class GasStatusProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _items = null; + _loading = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _items; + List get items => _items; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getData ({String host,User user,}) async { + if(_loading == true) return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse(host + URLs.getGasStatus), + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _items = categoriesListJson.map((item) => Status.fromJson(item)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/gas_refill/gas_types_provider.dart b/lib/controllers/providers/api/status_drop_down/gas_refill/gas_types_provider.dart new file mode 100644 index 0000000..33ab1b2 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/gas_refill/gas_types_provider.dart @@ -0,0 +1,73 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class GasTypesProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _items = null; + _loading = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _items; + List get items => _items; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getData ({String host,User user,}) async { + if(_loading == true) return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse(host + URLs.getGasTypes), + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _items = categoriesListJson.map((item) => Status.fromJson(item)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + print(error); + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/report/service_report_last_calls_provider.dart b/lib/controllers/providers/api/status_drop_down/report/service_report_last_calls_provider.dart new file mode 100644 index 0000000..b7ac8b7 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/report/service_report_last_calls_provider.dart @@ -0,0 +1,76 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class ServiceReportLastCallsProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _calls = null; + _loading = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _calls; + List get calls => _calls; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getCalls ({String host,User user,String serviceStatus}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse( + host + URLs.getServiceReportLastCalls + +(serviceStatus == null ? "" : "?service_status=$serviceStatus") + ), + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _calls = categoriesListJson.map((type) => Status.fromServiceReportJson(type)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/report/service_report_reasons_provider.dart b/lib/controllers/providers/api/status_drop_down/report/service_report_reasons_provider.dart new file mode 100644 index 0000000..5d2f499 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/report/service_report_reasons_provider.dart @@ -0,0 +1,73 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class ServiceReportReasonsProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _reasons = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _reasons; + List get reasons => _reasons; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getTypes ({String host,User user}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse( + host + URLs.getServiceReportReasons), + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _reasons = categoriesListJson.map((type) => Status.fromServiceReportJson(type)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/report/service_report_status_provider.dart b/lib/controllers/providers/api/status_drop_down/report/service_report_status_provider.dart new file mode 100644 index 0000000..1cc21f4 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/report/service_report_status_provider.dart @@ -0,0 +1,74 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class ServiceReportStatusProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _status = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _status; + List get statuses => _status; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getTypes ({String host,User user}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await get( + Uri.parse( + host + URLs.getServiceReportStatus), + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _status = categoriesListJson.map((type) => Status.fromServiceReportJson(type)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/report/service_report_types_provider.dart b/lib/controllers/providers/api/status_drop_down/report/service_report_types_provider.dart new file mode 100644 index 0000000..3737fc5 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/report/service_report_types_provider.dart @@ -0,0 +1,74 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class ServiceReportTypesProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _types = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _types; + List get types => _types; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getTypes ({String host,User user}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + + response = await get( + Uri.parse( + host + URLs.getServiceReportTypes), + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _types = categoriesListJson.map((type) => Status.fromServiceReportJson(type)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/status_drop_down/report/service_types_provider.dart b/lib/controllers/providers/api/status_drop_down/report/service_types_provider.dart new file mode 100644 index 0000000..5cde5e9 --- /dev/null +++ b/lib/controllers/providers/api/status_drop_down/report/service_types_provider.dart @@ -0,0 +1,75 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class ServiceStatusProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _statuses = null; + _stateCode = null; + } + + // state code of current request to defied error message + // like 400 customer request failed + // 500 service not available + int _stateCode; + int get stateCode => _stateCode; + + // contain user data + // when user not login or register _user = null + List _statuses; + List get statuses => _statuses; + + // when categories in-process _loading = true + // done _loading = true + // failed _loading = false + bool _loading; + bool get isLoading => _loading; + set isLoading(bool isLoading){ + _loading = isLoading; + notifyListeners(); + } + + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future getTypes ({String host,User user}) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + + response = await get( + Uri.parse( + host + URLs.getServiceTypes), + ); + _stateCode = response.statusCode; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List categoriesListJson = json.decode(utf8.decode(response.bodyBytes)); + _statuses = categoriesListJson.map((e) => Status.fromServiceReportJson(e)).toList(); + + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/api/user_provider.dart b/lib/controllers/providers/api/user_provider.dart new file mode 100644 index 0000000..843746c --- /dev/null +++ b/lib/controllers/providers/api/user_provider.dart @@ -0,0 +1,162 @@ +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class UserProvider extends ChangeNotifier{ + + //reset provider data + void reset(){ + _user = null; + _loading = false; + } + + // contain user data + // when user not login or register _user = null + User _user; + User get user => _user; + set user(User user) { + _user = user; + notifyListeners(); + } + + + // when login or register in-process _login = true + // when login or register is done or not start = false + bool _loading = false; + bool get isLoading => _loading; + set isLoading(bool isLoading) { + _loading = isLoading; + notifyListeners(); + } + + /// sign in with user - need (email or phone) and password; + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future login ({ + @required String host, + @required User user, + }) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await post( + Uri.parse( + host+URLs.login), + body: await user.toLoginJson(), + ); + _loading = false; + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + _user = User.fromJson(jsonDecode(utf8.decode(response.bodyBytes))[0]); + + + return response.statusCode; + } + notifyListeners(); + return response.statusCode; + } catch(error) { + _loading = false; + notifyListeners(); + return -1; + } + } + + /// sign up with User object; + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details about state codes check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future register ({ + @required String host, + @required User user, + }) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + try{ + response = await post( + Uri.parse( + host+URLs.register), + body: await user.toRegisterJson() + ); + } catch(error) { + _loading = false; + notifyListeners(); + return -1; + } + _loading = false; + notifyListeners(); + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + _user = User.fromJson(jsonDecode(utf8.decode(response.bodyBytes))[0]); + _user.hospital = user.hospital; + notifyListeners(); + return response.statusCode; + } + return response.statusCode; + } + + /// sign up with User object; + /// return -2 if request in progress + /// return -1 if error happen when sending request + /// return state code if request complete may be 200, 404 or 403 + /// for more details about state codes check http state manager + /// lib\controllers\http_status_manger\http_status_manger.dart + Future updateProfile ({ + @required String host, + @required User user, + }) async { + if(_loading == true) + return -2; + _loading = true; + notifyListeners(); + Response response; + + Map jsonObject ={}; + jsonObject["uid"] = user.id; + jsonObject["token"] = user.token; + if(user.department.id != _user.department.id) + jsonObject["department"] = user.department.id; + if(user.whatsApp != _user.whatsApp) + jsonObject["whatsapp"] = user.whatsApp; + if(user.phoneNumber != _user.phoneNumber) + jsonObject["phone"] = user.phoneNumber; + try{ + response = await post( + Uri.parse( + host+URLs.updateProfile), + body: jsonObject + ); + } catch(error) { + _loading = false; + notifyListeners(); + return -1; + } + + _loading = false; + notifyListeners(); + + if(response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + _user = User.fromJson(jsonDecode(utf8.decode(response.bodyBytes))[0]); + _user.hospital = user.hospital; + _user.department = user.department; + notifyListeners(); + return response.statusCode; + } + return response.statusCode; + } + +} \ No newline at end of file diff --git a/lib/controllers/providers/settings/app_settings.dart b/lib/controllers/providers/settings/app_settings.dart new file mode 100644 index 0000000..2d09c2a --- /dev/null +++ b/lib/controllers/providers/settings/app_settings.dart @@ -0,0 +1,6 @@ +class ASettings { + static final String user = "user"; + static final String host = "host"; + static final String language = "language"; + static final String speechToText = "speech_to_text"; +} \ No newline at end of file diff --git a/lib/controllers/providers/settings/setting_provider.dart b/lib/controllers/providers/settings/setting_provider.dart new file mode 100644 index 0000000..118b24c --- /dev/null +++ b/lib/controllers/providers/settings/setting_provider.dart @@ -0,0 +1,97 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../settings/app_settings.dart'; + + +class SettingProvider extends ChangeNotifier{ + + resetSettings() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + user = null; + prefs.remove(ASettings.user); + notifyListeners(); + } + + // check if setting loaded or not + bool isLoaded = false; + + // contain saved user data + User user; + + Future setUser(User user) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(ASettings.user, json.encode(user.toJson())); + this.user = user; + notifyListeners(); + } + + String _host; + String get host => _host; + Future setHost(String host) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(ASettings.host, host); + _host = host; + notifyListeners(); + } + + String _language; + String get language => _language; + Future setLanguage(String currentLanguage) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(ASettings.language, currentLanguage); + _language = currentLanguage; + notifyListeners(); + } + + String _speechToText; + String get speechToText => _speechToText; + Future setSpeechToText(String currentLanguage) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(ASettings.speechToText, currentLanguage); + _speechToText = currentLanguage; + notifyListeners(); + } + + // call loadSharedPreferences when provider initialise + SettingProvider(){ + loadSharedPreferences(); + } + + // get app setting + Future loadSharedPreferences() async { + + SharedPreferences prefs = await SharedPreferences.getInstance(); + + if(prefs.containsKey(ASettings.language)){ + _language = prefs.getString(ASettings.language); + }else{ + _language = 'en'; + } + + if(prefs.containsKey(ASettings.speechToText)){ + _speechToText = prefs.getString(ASettings.speechToText); + }else{ + _speechToText = 'ar'; + } + + if(prefs.containsKey(ASettings.user)){ + String userJson = prefs.getString(ASettings.user); + user = User.fromJson(json.decode(userJson)); + } + + if(prefs.containsKey(ASettings.host)){ + _host = prefs.getString(ASettings.host); + } else{ + _host = URLs.host1; + } + + isLoaded = true; + notifyListeners(); + } + +} \ No newline at end of file diff --git a/lib/controllers/validator/validator.dart b/lib/controllers/validator/validator.dart new file mode 100644 index 0000000..0eb09e9 --- /dev/null +++ b/lib/controllers/validator/validator.dart @@ -0,0 +1,58 @@ +class Validator{ + // private constructor to avoid create class object + Validator._(); + + // check if string not empty and has value + static bool hasValue(String string){ + if (string == null || string.isEmpty) + return false; + return true; + } + + // Return true if email is valid. Otherwise, return false + static bool isEmail(String email){ + RegExp exp = new RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); + if(exp.hasMatch(email)) + return true; + return false; + } + + // Return true if phone number is valid. Otherwise, return false + static bool isPhoneNumber(String phoneNumber){ + if (phoneNumber == null || phoneNumber.isEmpty) { + return false; + } + + final pattern = r'^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$'; + final regExp = RegExp(pattern); + + if (regExp.hasMatch(phoneNumber)) + return true; + return false; + } + + // Return true if password is valid. Otherwise, return false + static bool isValidPassword(String password){ + if(password == null) + return false; + if(password.length <6) + return false; + return true; + } + + // Return true if String is valid Numeric. Otherwise, return false + static bool isNumeric(String s) { + if (s == null) { + return false; + } + return double.tryParse(s) != null; + } + + // Return true if String is valid Numeric. Otherwise, return false + static bool isInt(String value) { + if (value == null) { + return false; + } + return int.tryParse(value) != null; + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..2cf47e6 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,148 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/departments_provider.dart'; +import 'package:test_sa/controllers/providers/api/device_transfer_provider.dart'; +import 'package:test_sa/controllers/providers/api/devices_provider.dart'; +import 'package:test_sa/controllers/providers/api/gas_refill_provider.dart'; +import 'package:test_sa/controllers/providers/api/hospitals_provider.dart'; +import 'package:test_sa/controllers/providers/api/notifications_provider.dart'; +import 'package:test_sa/controllers/providers/api/regular_visits_provider.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/employee/employee_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_cylinder_size_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_status_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_types_provider.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/pages/login.dart'; +import 'package:test_sa/views/pages/register.dart'; +import 'package:test_sa/views/pages/splash_screen.dart'; +import 'package:test_sa/views/pages/user/gas_refill/request_gas_refill.dart'; +import 'package:test_sa/views/pages/user/gas_refill/track_gas_refill.dart'; +import 'package:test_sa/views/pages/user/land_page.dart'; +import 'package:test_sa/views/pages/user/notifications/notifications_page.dart'; +import 'package:test_sa/views/pages/user/profile_page.dart'; +import 'package:test_sa/views/pages/user/report_issues_page.dart'; +import 'package:test_sa/views/pages/user/requests/create_request.dart'; +import 'package:test_sa/views/pages/user/requests/future_request_service_details.dart'; +import 'package:test_sa/views/pages/user/requests/requests_page.dart'; +import 'package:test_sa/views/pages/user/visits/preventive_maintenance_visits_page.dart'; +import 'package:test_sa/views/pages/user/visits/regular_visits_page.dart'; +import 'package:test_sa/views/widgets/departments/single_department_picker.dart'; +import 'package:test_sa/views/widgets/equipment/single_device_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:provider/provider.dart'; +import 'controllers/providers/api/parts_provider.dart'; +import 'controllers/providers/api/preventive_maintenance_visits_provider.dart'; +import 'controllers/providers/api/status_drop_down/report/service_report_last_calls_provider.dart'; +import 'controllers/providers/api/status_drop_down/report/service_report_reasons_provider.dart'; +import 'controllers/providers/api/status_drop_down/report/service_report_status_provider.dart'; +import 'controllers/providers/api/status_drop_down/report/service_report_types_provider.dart'; +import 'controllers/providers/api/status_drop_down/report/service_types_provider.dart'; +import 'controllers/providers/api/user_provider.dart'; +import 'controllers/providers/settings/setting_provider.dart'; +import 'views/pages/device_transfer/request_device_transfer.dart'; +import 'views/pages/device_transfer/track_device_transfer.dart'; +import 'views/widgets/hospitals/single_hospital_picker.dart'; + +void main() { + runApp( + ChangeNotifierProvider( + create: (_) => SettingProvider(), + child: MyApp(), + ) + ); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + final _settingProvider = Provider.of(context); + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => UserProvider()), + ChangeNotifierProvider(create: (_) => HospitalsProvider()), + ChangeNotifierProvider(create: (_) => DevicesProvider()), + ChangeNotifierProvider(create: (_) => ServiceRequestsProvider()), + ChangeNotifierProvider(create: (_) => DepartmentsProvider()), + ChangeNotifierProvider(create: (_) => NotificationsProvider()), + ChangeNotifierProvider(create: (_) => PreventiveMaintenanceVisitsProvider()), + ChangeNotifierProvider(create: (_) => RegularVisitsProvider()), + ChangeNotifierProvider(create: (_) => PartsProvider()), + ChangeNotifierProvider(create: (_) => ServiceReportReasonsProvider()), + ChangeNotifierProvider(create: (_) => ServiceReportStatusProvider()), + ChangeNotifierProvider(create: (_) => ServiceReportTypesProvider()), + ChangeNotifierProvider(create: (_) => ServiceStatusProvider()), + ChangeNotifierProvider(create: (_) => ServiceReportLastCallsProvider()), + ChangeNotifierProvider(create: (_) => GasCylinderSizesProvider()), + ChangeNotifierProvider(create: (_) => GasStatusProvider()), + ChangeNotifierProvider(create: (_) => GasTypesProvider()), + ChangeNotifierProvider(create: (_) => GasRefillProvider()), + ChangeNotifierProvider(create: (_) => DeviceTransferProvider()), + ChangeNotifierProvider(create: (_) => EmployeesProvider()), + ], + child: GestureDetector( + onTap: () { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + FocusManager.instance.primaryFocus.unfocus(); + } + }, + child: MaterialApp( + title: 'ATOMS', + debugShowCheckedModeBanner: false, + theme: ThemeData( + fontFamily: "Swiss", + //canvasColor: AColors.primaryColor, + scaffoldBackgroundColor: AColors.scaffoldBackgroundColor, + primaryColor: AColors.primaryColor, + indicatorColor: AColors.primaryColor, + colorScheme: const ColorScheme.light( + primary: AColors.primaryColor, + onPrimary: Colors.white, + secondary: AColors.secondaryColor, + onSecondary: Colors.white + ) + ), + localizationsDelegates: const [ + // ... app-specific localization delegate[s] here + AppLocalization.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en'), + Locale('ar'), + ], + locale: Locale(_settingProvider.language ?? 'en'), + initialRoute: SplashScreen.id, + routes: { + SplashScreen.id: (_)=> const SplashScreen(), + LandPage.id: (_)=> const LandPage(), + Login.id: (_)=> Login(), + Register.id: (_)=> Register(), + ProfilePage.id: (_)=> ProfilePage(), + ReportIssuesPage.id: (_)=> const ReportIssuesPage(), + RequestGasRefill.id: (_)=> const RequestGasRefill(), + CreateRequestPage.id: (_)=> CreateRequestPage(), + SingleHospitalPicker.id: (_)=> SingleHospitalPicker(), + SingleDevicePicker.id: (_)=> SingleDevicePicker(), + SingleDepartmentPicker.id: (_)=> SingleDepartmentPicker(), + ServiceRequestsPage.id: (_)=> ServiceRequestsPage(), + NotificationsPage.id: (_)=> NotificationsPage(), + FutureRequestServiceDetails.id: (_)=> FutureRequestServiceDetails(), + PreventiveMaintenanceVisitsPage.id: (_)=> PreventiveMaintenanceVisitsPage(), + RegularVisitsPage.id: (_)=> RegularVisitsPage(), + TrackGasRefillPage.id: (_)=> const TrackGasRefillPage(), + RequestDeviceTransfer.id: (_)=> const RequestDeviceTransfer(), + TrackDeviceTransferPage.id: (_)=> const TrackDeviceTransferPage(), + }, + ), + ), + ); + } +} + + + diff --git a/lib/models/app_notification.dart b/lib/models/app_notification.dart new file mode 100644 index 0000000..d199a07 --- /dev/null +++ b/lib/models/app_notification.dart @@ -0,0 +1,27 @@ +import 'package:test_sa/views/pages/user/requests/future_request_service_details.dart'; + +class AppNotification{ + String requestId; + String title; + String description; + String date; + String path; + + AppNotification({ + this.requestId, + this.title, + this.description, + this.date, + this.path, + }); + + factory AppNotification.fromJson(Map parsedJson){ + return AppNotification( + requestId: parsedJson["nid"], + title: parsedJson["call_client"] ?? parsedJson["title"], + description: parsedJson["task_description"], + date: parsedJson["creation_date"], + path: FutureRequestServiceDetails.id + ); + } +} \ No newline at end of file diff --git a/lib/models/department.dart b/lib/models/department.dart new file mode 100644 index 0000000..27df7b0 --- /dev/null +++ b/lib/models/department.dart @@ -0,0 +1,22 @@ +class Department{ + String id; + String name; + + Department({ + this.id, + this.name, + }); + + factory Department.fromJson(Map parsedJson){ + return Department( + id: parsedJson["nid"] ?? parsedJson["id"], + name: parsedJson["dept_name"] ?? parsedJson["value"], + ); + } + factory Department.fromDepartment(Department department){ + return Department( + id: department?.id, + name: department?.name, + ); + } +} \ No newline at end of file diff --git a/lib/models/device/device.dart b/lib/models/device/device.dart new file mode 100644 index 0000000..a817b00 --- /dev/null +++ b/lib/models/device/device.dart @@ -0,0 +1,62 @@ +class Device{ + String id; + String serialNumber; + String brand; + String model; + DateTime productionDate; + DateTime supplyDate; + DateTime installDate; + DateTime receivingDate; + DateTime operationDate; + DateTime warrantyDate; + + Device({ + this.id, + this.serialNumber, + this.brand, + this.model, + this.productionDate, + this.supplyDate, + this.installDate, + this.receivingDate, + this.operationDate, + this.warrantyDate, + }); + + factory Device.fromJson(Map parsedJson){ + return Device( + id: parsedJson["nid"] ?? parsedJson["id"], + serialNumber: parsedJson["sn"] ?? parsedJson["value"], + brand: parsedJson["brand"].toString(), + model: parsedJson["model"].toString(), + productionDate: getDateFromString(parsedJson["production_date"]), + supplyDate: getDateFromString(parsedJson["supply_date "]), + installDate: getDateFromString(parsedJson["install_date "]), + receivingDate: getDateFromString(parsedJson["receving_date "]), + operationDate: getDateFromString(parsedJson["operation_date "]), + warrantyDate: getDateFromString(parsedJson["warranty_date "]), + ); + } + + factory Device.fromDevice(Device device){ + return Device( + id: device.id, + serialNumber: device.serialNumber, + brand: device.brand, + model: device.model, + productionDate: device.productionDate, + supplyDate: device.supplyDate, + installDate: device.installDate, + receivingDate: device.receivingDate, + operationDate: device.operationDate, + warrantyDate: device.warrantyDate, + ); + } +} + +DateTime getDateFromString(String unixDate){ + if(unixDate == null) + return null; + return DateTime.fromMillisecondsSinceEpoch( + int.parse(unixDate)); +} \ No newline at end of file diff --git a/lib/models/device/device_transfer.dart b/lib/models/device/device_transfer.dart new file mode 100644 index 0000000..2cad5ed --- /dev/null +++ b/lib/models/device/device_transfer.dart @@ -0,0 +1,54 @@ +import 'package:http/http.dart'; +import 'package:test_sa/models/department.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/device/device_transfer_info.dart'; +import 'package:test_sa/models/hospital.dart'; +import 'package:test_sa/models/status.dart'; + +class DeviceTransfer{ + String id; + String userId; + String title; + Device device; + DeviceTransferInfo sender; + DeviceTransferInfo receiver; + + DeviceTransfer({ + this.id, + this.device, + this.title, + this.userId, + this.sender, + this.receiver, + }); + + bool validate(){ + if(device == null) return false; + return receiver.validate(); + } + + fromDeviceTransfer(DeviceTransfer old){ + id = old.id; + title = old.title; + userId = old.userId; + device = Device.fromDevice(old.device); + final sender = DeviceTransferInfo(); + sender.fromDetails(old.sender); + this.sender = sender; + final receiver = DeviceTransferInfo(); + receiver.fromDetails(old.receiver); + this.receiver = receiver; + } + + factory DeviceTransfer.fromJson(Map parsedJson){ + return DeviceTransfer( + id: parsedJson["id"], + title: parsedJson["title"], + userId: parsedJson["uid"], + device: Device.fromJson(parsedJson["eq_sn"]), + sender: DeviceTransferInfo.fromJson(parsedJson,"sender_"), + receiver: DeviceTransferInfo.fromJson(parsedJson,"receiver_"), + ); + } +} + diff --git a/lib/models/device/device_transfer_info.dart b/lib/models/device/device_transfer_info.dart new file mode 100644 index 0000000..f3f1d80 --- /dev/null +++ b/lib/models/device/device_transfer_info.dart @@ -0,0 +1,74 @@ +import 'package:http/http.dart'; +import 'package:test_sa/models/department.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/hospital.dart'; +import 'package:test_sa/models/status.dart'; + +class DeviceTransferInfo{ + String userId; + String comment; + Hospital client; + Department department; + String workingHours; + String travelingHours; + String name; + String signature; + Status status; + + DeviceTransferInfo({ + this.userId, + this.comment, + this.department, + this.client, + this.name, + this.travelingHours, + this.workingHours, + this.signature, + this.status, + }); + + MaptoJson(bool isSender){ + Map body = {}; + final baseKey = isSender ? "sender_" : "receiver_"; + + if(comment != null && comment.isNotEmpty) body["${baseKey}comment"] = comment; + if(workingHours != null && workingHours.isNotEmpty) body["${baseKey}working_hours"] = workingHours; + if(travelingHours != null && travelingHours.isNotEmpty) body["${baseKey}travel_hours"] = comment; + if(status != null) body["${baseKey}status"] = status.id.toString(); + if(signature != null && signature.isNotEmpty) body["${baseKey}image"] = signature; + return body; + } + + bool validate(){ + if(client == null) return false; + if(department == null) return false; + return true; + } + + fromDetails(DeviceTransferInfo old,{bool withSignature = true}){ + userId = old.userId; + name = old.name; + client = Hospital.fromHospital(old.client); + department = Department.fromDepartment(old.department); + workingHours = old.workingHours; + travelingHours = old.travelingHours; + comment = old.comment; + if(withSignature) signature = old.signature; + status = old.status; + } + + factory DeviceTransferInfo.fromJson(Map parsedJson,String key){ + return DeviceTransferInfo( + workingHours: parsedJson["${key}working_hours"], + travelingHours: parsedJson["${key}travel_hours"], + name: parsedJson["${key}name"], + signature: parsedJson["${key}image"], + userId: parsedJson["${key}id"], + comment: parsedJson["${key}comment"], + client: Hospital.fromJson(parsedJson["${key}client"]), + department: Department.fromJson(parsedJson["${key}department"]), + status: Status.fromJson(parsedJson["${key}status"]), + ); + } +} + diff --git a/lib/models/enums/user_types.dart b/lib/models/enums/user_types.dart new file mode 100644 index 0000000..2d608f7 --- /dev/null +++ b/lib/models/enums/user_types.dart @@ -0,0 +1,4 @@ +enum UsersTypes{ + engineer, // 0 + normal_user, // 1 +} \ No newline at end of file diff --git a/lib/models/gas_refill/gas_refill_details.dart b/lib/models/gas_refill/gas_refill_details.dart new file mode 100644 index 0000000..35ed91f --- /dev/null +++ b/lib/models/gas_refill/gas_refill_details.dart @@ -0,0 +1,43 @@ +import 'package:test_sa/models/status.dart'; + +class GasRefillDetails{ + Status type; + Status cylinderSize; + int requestedQuantity; + int deliveredQuantity; + + GasRefillDetails({ + this.type, + this.cylinderSize, + this.requestedQuantity, + this.deliveredQuantity, + }); + + bool validate(){ + //if(cylinderSize == null) return false; + if(type == null) return false; + if(requestedQuantity == null) return false; + return true; + } + factory GasRefillDetails.fromJson(Map parsedJson){ + return GasRefillDetails( + type: Status.fromJson(parsedJson["type"]), + + cylinderSize: Status.fromJson(parsedJson["size"]), + requestedQuantity: parsedJson["requsted_qty"] == null + ? 0 : int.tryParse(parsedJson["requsted_qty"].toString()) ?? 0, + deliveredQuantity: parsedJson["deliverd_qty"] == null + ? 0 : int.tryParse(parsedJson["deliverd_qty"].toString()) ?? 0, + ); + } + + factory GasRefillDetails.fromDetails(GasRefillDetails details){ + return GasRefillDetails( + type: Status.fromStatus(details.type), + cylinderSize:Status.fromStatus(details.cylinderSize), + requestedQuantity: details.requestedQuantity, + deliveredQuantity: details.deliveredQuantity, + ); + } +} + diff --git a/lib/models/gas_refill/gas_refill_model.dart b/lib/models/gas_refill/gas_refill_model.dart new file mode 100644 index 0000000..b0941c2 --- /dev/null +++ b/lib/models/gas_refill/gas_refill_model.dart @@ -0,0 +1,53 @@ +import 'package:test_sa/models/gas_refill/gas_refill_details.dart'; +import 'package:test_sa/models/status.dart'; + +class GasRefillModel{ + String id; + String userId; + String clientName; + String title; + Status status; + List details; + + GasRefillModel({ + this.id, + this.userId, + this.clientName, + this.title, + this.status, + this.details, + }); + + bool validate(){ + if(title == null) return false; + if(status == null) return false; + if(details == null && details.isEmpty) return false; + return true; + } + + fromGasRefillModel(GasRefillModel model){ + id = model.id; + userId = model.userId; + clientName = model.clientName; + title = model.title; + status = Status.fromStatus(model.status); + details = model.details.map((e) => GasRefillDetails.fromDetails(e)).toList(); + } + + factory GasRefillModel.fromJson(Map parsedJson){ + List details = []; + if(parsedJson["details"] != null){ + List list = parsedJson["details"]; + details = list.map((e) => GasRefillDetails.fromJson(e)).toList(); + } + return GasRefillModel( + id: parsedJson["id"], + userId: parsedJson["uid"], + title: parsedJson["title"], + clientName: parsedJson["client"], + status: Status.fromJson(parsedJson["status"]), + details: details, + ); + } +} + diff --git a/lib/models/hospital.dart b/lib/models/hospital.dart new file mode 100644 index 0000000..34e2e3a --- /dev/null +++ b/lib/models/hospital.dart @@ -0,0 +1,23 @@ +class Hospital{ + String id; + String name; + + Hospital({ + this.id, + this.name, + }); + + factory Hospital.fromJson(Map parsedJson){ + return Hospital( + id: parsedJson["nid"] ?? parsedJson["id"], + name: parsedJson["client_name"] ?? parsedJson["value"], + ); + } + + factory Hospital.fromHospital(Hospital hospital){ + return Hospital( + id: hospital?.id, + name: hospital?.name, + ); + } +} \ No newline at end of file diff --git a/lib/models/issue.dart b/lib/models/issue.dart new file mode 100644 index 0000000..cc186eb --- /dev/null +++ b/lib/models/issue.dart @@ -0,0 +1,27 @@ +class Issue{ + String title; + String userId; + List reports; + String serviceRequestId; + String description; + bool isSelected; + + Issue({ + this.description, + this.isSelected, + this.title, + this.userId, + this.reports, + this.serviceRequestId, + }); + + Map toMap(){ + Map map ={}; + if(title != null) map["title"] = title; + if(reports != null) map["issue_report"] = reports.toString(); + if(userId != null) map["uid"] = userId; + if(description != null) map["desc"] = description; + if(serviceRequestId != null) map["call_id"] = serviceRequestId; + return map; + } +} \ No newline at end of file diff --git a/lib/models/part.dart b/lib/models/part.dart new file mode 100644 index 0000000..d75bb49 --- /dev/null +++ b/lib/models/part.dart @@ -0,0 +1,23 @@ +class Part{ + String id; + String code; + String name; + int quantity; + + Part({ + this.id, + this.code, + this.name, + this.quantity = 1, + }); + + factory Part.fromJson(Map parsedJson){ + return Part( + id: parsedJson["nid"] ?? parsedJson["id"], + code: parsedJson["part_code"] ?? parsedJson["name"], + name: parsedJson["part_name"], + quantity: parsedJson["qty"] == null + ? 1 : int.tryParse(parsedJson["qty"].toString()) ?? 1, + ); + } +} \ No newline at end of file diff --git a/lib/models/service_report.dart b/lib/models/service_report.dart new file mode 100644 index 0000000..7737c9f --- /dev/null +++ b/lib/models/service_report.dart @@ -0,0 +1,150 @@ +import 'dart:convert'; + +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/part.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/timer_model.dart'; + +class ServiceReport { + String id; + String operatingHours; + DateTime visitDate; + DateTime endDate; + Status serviceType; + Status callLastSituation; + Status status; + Status type; + Status reason; + String faultDescription; + String workPreformed; + //String workHours; + String travelingHours; + String invoiceNumber; + String invoiceCode; + List parts; + String image; + Device device; + String quantity; + String jobSheetNumber; + TimerModel timer; + + ServiceReport({ + this.id, + this.visitDate, + this.endDate, + this.serviceType, + this.status, + this.type, + this.faultDescription, + //this.workHours, + this.travelingHours, + this.parts, + this.workPreformed, + this.reason, + this.operatingHours, + this.callLastSituation, + this.jobSheetNumber, + this.image, + this.device, + this.invoiceCode, + this.invoiceNumber, + this.quantity = "1", + this.timer, + }); + + Map toMap(){ + Map _map = {}; + if(id != null) _map["id"] = id; + if(visitDate != null) _map["visit_date"] = (visitDate.millisecondsSinceEpoch ~/ 1000).toString(); + if(serviceType != null) _map["service_type"] = serviceType.id.toString(); + if(status != null) _map["status"] = status.id.toString(); + if(type != null) _map["service_report_type"] = type.id.toString(); + if(faultDescription != null && faultDescription.isNotEmpty) _map["fault_description"] = faultDescription; + //if(workHours != null && workHours.isNotEmpty) _map["working_hours"] = workHours; + if(timer != null){ + _map["start_time"] = (timer.startAt.millisecondsSinceEpoch / 1000).toStringAsFixed(0); + _map["end_time"] = ((timer.endAt ?? DateTime.now()).millisecondsSinceEpoch / 1000).toStringAsFixed(0); + _map["working_hours"] = (timer.durationInSecond / 60 / 60).toStringAsFixed(5); + } + if(travelingHours != null && travelingHours.isNotEmpty) _map["traveling_hours"] = travelingHours; + if(workPreformed != null && workPreformed.isNotEmpty) _map["work_performed"] = workPreformed; + if(jobSheetNumber != null && jobSheetNumber.isNotEmpty) _map["job_sheet_no"] = jobSheetNumber; + if(parts != null && parts.isNotEmpty){ + Map _partsMap = {}; + parts.forEach((part) { + if(part.id.isNotEmpty) + _partsMap[part.id] = part.quantity; + }); + _map["parts"] = json.encode(_partsMap); + } + if(device?.id != null && device.id != null) _map["eq_id"] = device.id; + if(quantity != null && quantity.isNotEmpty) _map["qty"] = quantity; + if(endDate != null) _map["end_date"] = (endDate.millisecondsSinceEpoch ~/ 1000).toString(); + if(reason != null) _map["reasons"] = reason.id.toString(); + if(operatingHours != null && operatingHours.isNotEmpty) _map["operation_hours"] = operatingHours; + if(callLastSituation != null) _map["call_last_situtation"] = callLastSituation.id.toString(); + if(image != null) _map["image"] = image; + if(invoiceCode != null) _map["invoice_no"] = invoiceCode; + if(invoiceNumber != null) _map["invoice_code"] = invoiceNumber; + return _map; + } + + bool validate(){ + + if(visitDate == null) return false; + if(serviceType == null) return false; + if(status == null) return false; + if(type == null) return false; + if(callLastSituation == null) return false; + if(callLastSituation?.id == 12){ + if(invoiceCode != null || invoiceCode?.isEmpty == true) return false; + if(invoiceNumber != null || invoiceNumber?.isEmpty== true ) return false; + } + if(parts == null) return false; + //if(endDate == null) return false; + //if(reason == null) return false; + if((device?.id == null || device.id.isEmpty) && type?.id != 1) return false; + //if(quantity == null || quantity.isEmpty) return false; + //if(image == null) return false; + return true; + } + + factory ServiceReport.fromJson(Map parsedJson,String id){ + List _parts = []; + if(parsedJson["parts"] != null){ + if(parsedJson["parts"][0]["id"] != null){ + List partsList = parsedJson["parts"]; + _parts = partsList.map((e) => Part.fromJson(e)).toList(); + } + + } + return ServiceReport( + id: id, + serviceType: Status.fromJson(parsedJson["service_type"]), + callLastSituation: Status.fromJson(parsedJson["call_last_situtation"]), + reason: Status.fromJson(parsedJson["reasons"]), + status: Status.fromJson(parsedJson["service_report_status"]), + type: Status.fromJson(parsedJson["service_report_type"]), + faultDescription: parsedJson["fault_description"], + endDate:getDate(parsedJson["end_date"]), + invoiceCode: parsedJson["invoice_code"], + invoiceNumber: parsedJson["invoice_no"], + jobSheetNumber: parsedJson["job_sheet_no"], + operatingHours: parsedJson["operation_hours"], + parts: _parts, + quantity: parsedJson["nid"], + travelingHours: parsedJson["traveling_hours"], + visitDate: getDate(parsedJson["visit_date"]), + //workHours: parsedJson["working_hours"], + timer: TimerModel( + durationInSecond: (int.tryParse(parsedJson["working_hours"] ?? "") ?? 0) * 60 *60), + workPreformed: parsedJson["work_performed"], + device: parsedJson["eq_nid"] == null ? null : Device(id: parsedJson["eq_nid"],serialNumber: parsedJson["eq_serial"]) + ); + } + + static getDate(String date){ + return date == null || date.isEmpty + ? null : DateTime.fromMillisecondsSinceEpoch(int.tryParse(date) * 1000); + } +} \ No newline at end of file diff --git a/lib/models/service_request/service_request.dart b/lib/models/service_request/service_request.dart new file mode 100644 index 0000000..81ea674 --- /dev/null +++ b/lib/models/service_request/service_request.dart @@ -0,0 +1,95 @@ +import '../timer_model.dart'; + +class ServiceRequest{ + String id; + String requestCode; + String deviceSerialNumber; + String deviceId; + String deviceArName; + String deviceEnName; + List devicePhotos; + String maintenanceIssue; + String hospitalName; + String hospitalId; + String departmentName; + String engineerName; + String date; + String audio; + int statusValue; + String statusLabel; + + bool viewReport; + String engineerMobile; + String deviceModel; + String faultDescription; + String workPerformed; + String visitDate; + DateTime nextVisitDate; + String jobSheetNumber; + String reportID; + + ServiceRequest({ + this.id, + this.date, + this.requestCode, + this.hospitalName, + this.deviceArName, + this.deviceEnName, + this.devicePhotos, + this.hospitalId, + this.deviceSerialNumber, + this.maintenanceIssue, + this.statusLabel, + this.statusValue, + this.departmentName, + this.deviceId, + this.audio, + this.engineerName, + + this.viewReport = false, + this.deviceModel, + this.engineerMobile, + this.faultDescription, + this.jobSheetNumber, + this.visitDate, + this.nextVisitDate, + this.workPerformed, + this.reportID, + + }); + + factory ServiceRequest.fromJson(Map parsedJson){ + return ServiceRequest( + id: parsedJson["nid"], + requestCode: parsedJson["call_id"] ?? parsedJson["jobcode"] , + hospitalName: parsedJson["call_client"], + deviceId: parsedJson["deviceid"], + audio: parsedJson["audio"] ?? "", + deviceArName: parsedJson["equipment_arabic_name"] == false + ? "No Name found" : parsedJson["equipment_arabic_name"], + deviceEnName: parsedJson["equipment_english_name"] == false + ? "No Name found" : parsedJson["equipment_english_name"], + devicePhotos: List.from(parsedJson["image"]), + deviceSerialNumber: parsedJson["call_sn"], + date: parsedJson["call_data"], + maintenanceIssue: parsedJson["call_complaint"] ?? parsedJson["complaint"], + statusLabel: parsedJson["status_value"], + statusValue: int.tryParse(parsedJson["status"]??"-1"), + departmentName: parsedJson["department_name"], + engineerName: parsedJson["employee_name"], + hospitalId: parsedJson["client"], + reportID: parsedJson["service_report_nid"] is String + ? parsedJson["service_report_nid"] + : null , + viewReport: parsedJson["service_report_nid"] is bool ? false : true, + deviceModel: parsedJson["device_model"], + engineerMobile: parsedJson["engineer_mobile"], + faultDescription: parsedJson["fault_desc"], + jobSheetNumber: parsedJson["job_sheet_number"], + visitDate: parsedJson["visit_date"], + nextVisitDate:parsedJson["next_visit_date"] == null + ? null : DateTime.fromMillisecondsSinceEpoch(int.tryParse(parsedJson["next_visit_date"]) * 1000), + workPerformed: parsedJson["work_performed"], + ); + } +} \ No newline at end of file diff --git a/lib/models/service_request/service_request_search.dart b/lib/models/service_request/service_request_search.dart new file mode 100644 index 0000000..257f25f --- /dev/null +++ b/lib/models/service_request/service_request_search.dart @@ -0,0 +1,46 @@ +class ServiceRequestSearch{ + String deviceSerialNumber; + String deviceName; + String hospital; + String model; + int statusValue; + + + ServiceRequestSearch({ + this.deviceSerialNumber, + this.statusValue, + this.deviceName, + this.model, + this.hospital, + }); + + fromSearch(ServiceRequestSearch newSearch){ + deviceSerialNumber = newSearch.deviceSerialNumber; + statusValue = newSearch.statusValue; + hospital = newSearch.hospital; + model = newSearch.model; + } + + String toSearchString(){ + String _search = ""; + if(deviceSerialNumber != null && deviceSerialNumber.isNotEmpty){ + _search += "&sn_id=$deviceSerialNumber"; + } + + if(statusValue != null){ + _search += "&status=$statusValue"; + } + + if(deviceName != null && deviceName.isNotEmpty){ + _search += "&equipment_en_name=$deviceName"; + } + + if(hospital != null && hospital.isNotEmpty){ + _search += "&client=$hospital"; + } + if(model != null && model.isNotEmpty){ + _search += "&model=$model"; + } + return _search; + } +} \ No newline at end of file diff --git a/lib/models/status.dart b/lib/models/status.dart new file mode 100644 index 0000000..79786f4 --- /dev/null +++ b/lib/models/status.dart @@ -0,0 +1,47 @@ +class Status{ + + final String label; + final String key; + final int id; + + const Status({ + this.label, + this.key, + this.id, + }); + + @override + bool operator == (Object other) => + identical(this, other) || other is Status && + key == other.key && + id == other.id; + + + @override + int get hashCode => id.hashCode; + + factory Status.fromStatus(Status old){ + return Status( + label: old.label, + id: old.id, + key: old.key, + ); + } + + factory Status.fromJson(Map parsedJson){ + if(parsedJson["id"] == null && parsedJson["uid"] == null) return null; + return Status( + label: parsedJson["value"], + id: parsedJson["id"] is int + ? parsedJson["id"] + : int.tryParse(parsedJson["id"] ?? parsedJson["uid"]), + ); + } + + factory Status.fromServiceReportJson(Map parsedJson){ + return Status( + label: parsedJson["value"], + id: parsedJson["id"], + ); + } +} \ No newline at end of file diff --git a/lib/models/subtitle.dart b/lib/models/subtitle.dart new file mode 100644 index 0000000..a2f212c --- /dev/null +++ b/lib/models/subtitle.dart @@ -0,0 +1,603 @@ +import 'package:meta/meta.dart'; + +class Subtitle{ + // http requests status messages + String currentlyServiceNotAvailable; + String waitUntilYourRequestComplete; + String requestCompleteSuccessfully; + String failedToCompleteRequest; + + // dialogs + // words + String signOut; + String exit; + String confirm; + String cancel; + // phrases + String signOutAlert; + String exitAlert; + + // image picker + String pickFromCamera; + String pickFromGallery; + String maxImagesNumberIs5; + String imagesRequired; + String images; + + // login and register page + // words + String language; + String name; + String email; + String phoneNumber; + String password; + String confirmPassword; + String showPassword; + String forgetPassword; + String forgetPasswordWithMark; + String signIn; + String signUp; + // phrases + String wrongEmailOrPassword; + String emailExist; + String nameExist; + String phoneNumberExist; + String nameValidateMessage; + String emailValidateMessage; + String phoneNumberValidateMessage; + String passwordValidateMessage; + String confirmPasswordValidateMessage; + + // commend words + String title; + String address; + String description; + String update; + String next; + String back; + String edit; + String search; + String searchByName; + String from; + String to; + + String descriptionValidateMessage; + String titleValidateMessage; + String addressValidateMessage; + + String nameNotFound; + String titleNotFound; + String phoneNumberNotFound; + String emailNotFound; + String descriptionNotFound; + String addressNotFound; + String dataNotFound; + String linkNotFound; + String urlNotFound; + + // notifications + String notifications; + String notificationsNotFound; + + // service request + String createServiceRequest; + String hospital; + String device; + String deviceImages; + String pickDevice; + String maintenanceIssue; + String create; + String delete; + String newServiceRequest; + String trackServiceRequest; + String hospitalRequired; + String deviceRequired; + String maintenanceIssueRequired; + String add; + String noServiceRequestFound; + String noModelFound; + String noSnFound; + String noDateFound; + String requestInformation; + String clearSearch; + String deviceName; + String serialNumber; + String code; + String deviceSN; + String deviceModel; + String engineerName; + String engineerPhone; + String date; + String status; + String unite; + String unitRequired; + String deviceArName; + String deviceEnName; + String details; + String general; + String serviceRequestInformation; + String faultDescription; + String workPerformed; + String visitDate; + String nextVisitDate; + String jobSheetNumber; + String noHospitalFound; + String pickHospital; + String pickUnite; + String noUniteFound; + String serviceRequests; + + // device + String model; + String brand; + String sn; + String searchBySn; + String noDeviceFound; + + // report issue + String reason1; + String reason2; + String reason3; + String reason4; + String reason5; + String reportIssue; + String submit; + String shareAntherIssue; + + // request status + String newWord; + String repaired; + String repeated; + String closed; + String underRepair; + + // visit status + String done; + String notYet; + String onHold; + + // land page + String hotLine; + String facebook; + String linkedIn; + String twitter; + String whatsApp; + String ourWebsite; + String shareApp; + String policy; + + // visits + String preventiveMaintenance; + String regularVisits; + String updatePreventiveMaintenance; + String updateRegularVisits; + String preventiveMaintenanceUpdatedSuccessfully; + String regularVisitsUpdatedSuccessfully; + String updatingDots; + String updateVisitsGroup; + String noSerialNumberFound; + String requiredStatus; + String visitInformation; + String expectDate; + String actualDate; + String noVisitsFound; + String contactStatus; + String workingHours; + String travelingHours; + String image; + String pickImage; + String requiredImage; + String taskStatus; + String activationAlert; + + // service report + String newServiceReport; + String editServiceReport; + String callId; + String customer; + String reportType; + String requiredWord; + String serviceType; + String reportStatus; + String callLastSituation; + String invoiceNumber; + String invoiceCode; + String workPreformed; + String reasons; + String attachImage; + String operatingHours; + String partNumber; + String number; + String quantity; + + // all + String duplicateRequest; + String duplicateAlert; + String duplicateAlertMessage; + String alert; + + void setIssues(List issues){ + issues.clear(); + issues.add(reason1); + issues.add(reason2); + issues.add(reason3); + issues.add(reason4); + issues.add(reason5); + } + + Subtitle({ + @required this.currentlyServiceNotAvailable, + @required this.waitUntilYourRequestComplete, + @required this.requestCompleteSuccessfully, + @required this.failedToCompleteRequest, + @required this.update, + @required this.cancel, + @required this.confirm, + @required this.exit, + @required this.exitAlert, + @required this.signOutAlert, + @required this.language, + @required this.name, + @required this.email, + @required this.phoneNumber, + @required this.password, + @required this.forgetPassword, + @required this.confirmPassword, + @required this.signIn, + @required this.signUp, + @required this.nameValidateMessage, + @required this.emailValidateMessage, + @required this.phoneNumberValidateMessage, + @required this.passwordValidateMessage, + @required this.confirmPasswordValidateMessage, + @required this.emailExist, + @required this.forgetPasswordWithMark, + @required this.phoneNumberExist, + @required this.showPassword, + @required this.signOut, + @required this.wrongEmailOrPassword, + @required this.next, + @required this.back, + @required this.search, + @required this.searchByName, + @required this.address, + @required this.description, + @required this.title, + @required this.addressNotFound, + @required this.addressValidateMessage, + @required this.dataNotFound, + @required this.descriptionNotFound, + @required this.descriptionValidateMessage, + @required this.edit, + @required this.emailNotFound, + @required this.from, + @required this.linkNotFound, + @required this.nameNotFound, + @required this.phoneNumberNotFound, + @required this.titleNotFound, + @required this.titleValidateMessage, + @required this.to, + @required this.urlNotFound, + + @required this.nameExist, + @required this.unitRequired, + @required this.unite, + @required this.hospitalRequired, + @required this.whatsApp, + @required this.submit, + @required this.shareAntherIssue, + @required this.reportIssue, + @required this.noUniteFound, + @required this.pickUnite, + @required this.noHospitalFound, + @required this.pickHospital, + @required this.shareApp, + @required this.ourWebsite, + @required this.linkedIn, + @required this.facebook, + @required this.hotLine, + @required this.trackServiceRequest, + @required this.newServiceRequest, + @required this.deviceModel, + @required this.noServiceRequestFound, + @required this.engineerName, + @required this.serviceRequests, + @required this.jobSheetNumber, + @required this.visitDate, + @required this.workPerformed, + @required this.faultDescription, + @required this.serviceRequestInformation, + @required this.maintenanceIssue, + @required this.deviceArName, + @required this.hospital, + @required this.status, + @required this.date, + @required this.engineerPhone, + @required this.device, + @required this.deviceSN, + @required this.details, + @required this.requestInformation, + @required this.model, + @required this.brand, + @required this.noDeviceFound, + @required this.noDateFound, + @required this.searchBySn, + @required this.pickDevice, + @required this.notifications, + @required this.notificationsNotFound, + @required this.twitter, + @required this.add, + @required this.code, + @required this.deviceEnName, + @required this.deviceName, + @required this.serialNumber, + @required this.policy, + @required this.clearSearch, + @required this.closed, + @required this.create, + @required this.createServiceRequest, + @required this.delete, + @required this.deviceImages, + @required this.deviceRequired, + @required this.general, + @required this.maintenanceIssueRequired, + @required this.maxImagesNumberIs5, + @required this.newWord, + @required this.noModelFound, + @required this.noSnFound, + @required this.reason1, + @required this.reason2, + @required this.reason3, + @required this.reason4, + @required this.reason5, + @required this.repaired, + @required this.repeated, + @required this.sn, + @required this.underRepair, + @required this.actualDate, + @required this.expectDate, + @required this.visitInformation, + @required this.regularVisitsUpdatedSuccessfully, + @required this.regularVisits, + @required this.updateRegularVisits, + @required this.updatingDots, + @required this.preventiveMaintenanceUpdatedSuccessfully, + @required this.updatePreventiveMaintenance, + @required this.preventiveMaintenance, + @required this.requiredStatus, + @required this.noSerialNumberFound, + @required this.updateVisitsGroup, + @required this.pickFromGallery, + @required this.pickFromCamera, + @required this.images, + @required this.done, + @required this.imagesRequired, + @required this.notYet, + @required this.noVisitsFound, + @required this.onHold, + @required this.nextVisitDate, + @required this.contactStatus, + @required this.travelingHours, + @required this.workingHours, + @required this.taskStatus, + @required this.image, + @required this.pickImage, + @required this.requiredImage, + @required this.activationAlert, + + @required this.callId, + @required this.requiredWord, + @required this.quantity, + @required this.callLastSituation, + @required this.invoiceNumber, + @required this.invoiceCode, + @required this.attachImage, + @required this.customer, + @required this.editServiceReport, + @required this.newServiceReport, + @required this.number, + @required this.operatingHours, + @required this.partNumber, + @required this.reasons, + @required this.reportStatus, + @required this.reportType, + @required this.serviceType, + @required this.workPreformed, + @required this.alert, + @required this.duplicateAlert, + @required this.duplicateAlertMessage, + @required this.duplicateRequest, + }); + + factory Subtitle.fromJson(Map parsedJson){ + return Subtitle( + currentlyServiceNotAvailable: parsedJson["server_error_message"], + failedToCompleteRequest: parsedJson["failed_request_message"], + requestCompleteSuccessfully: parsedJson["successful_request_message"], + waitUntilYourRequestComplete: parsedJson["request_lock_message"], + update: parsedJson["update"], + cancel: parsedJson["cancel"], + confirm: parsedJson["confirm"], + exit: parsedJson["exit"], + exitAlert: parsedJson["exit_alert"], + signOut: parsedJson["sign_out"], + signOutAlert: parsedJson["logout_alert"], + language: parsedJson["language"], + + name: parsedJson["name"], + email: parsedJson["email"], + phoneNumber: parsedJson["phone_number"], + password: parsedJson["password"], + confirmPassword: parsedJson["confirm_password"], + signUp: parsedJson["sign_up"], + signIn: parsedJson["sign_in"], + forgetPassword: parsedJson["forget_password"], + emailValidateMessage: parsedJson["email_validate_message"], + nameValidateMessage: parsedJson["name_validate_message"], + passwordValidateMessage: parsedJson["password_validate_message"], + confirmPasswordValidateMessage: parsedJson["confirm_password_validate_message"], + phoneNumberValidateMessage: parsedJson["phone_number_validate_message"], + + forgetPasswordWithMark: parsedJson["forget_password_with_mark"], + showPassword: parsedJson["show_password"], + wrongEmailOrPassword: parsedJson["wrong_email_or_password"], + emailExist: parsedJson["email_exist"], + phoneNumberExist: parsedJson["phone_number_exist"], + next: parsedJson["next"], + back: parsedJson["back"], + search: parsedJson["search"], + searchByName: parsedJson["search_by_name"], + + address: parsedJson["address"], + addressNotFound: parsedJson["address_not_found"], + addressValidateMessage: parsedJson["address_validate_message"], + dataNotFound: parsedJson["data_not_found"], + description: parsedJson["description"], + descriptionNotFound: parsedJson["description_not_found"], + descriptionValidateMessage: parsedJson["description_validate_message"], + edit: parsedJson["edit"], + emailNotFound: parsedJson["email_not_found"], + from: parsedJson["from"], + to: parsedJson["to"], + linkNotFound: parsedJson["link_not_found"], + nameNotFound: parsedJson["name_not_found"], + phoneNumberNotFound: parsedJson["phone_number_not_found"], + title: parsedJson["title"], + titleNotFound: parsedJson["title_not_found"], + titleValidateMessage: parsedJson["title_validate_message"], + urlNotFound: parsedJson["url_not_found"], + + date: parsedJson["date"], + status: parsedJson["status"], + code: parsedJson["code"], + serialNumber: parsedJson["serialNumber"], + add: parsedJson["add"], + brand: parsedJson["brand"], + clearSearch: parsedJson["clearSearch"], + closed: parsedJson["closed"], + create: parsedJson["create"], + createServiceRequest: parsedJson["createServiceRequest"], + delete: parsedJson["delete"], + details: parsedJson["details"], + device: parsedJson["device"], + deviceArName: parsedJson["deviceArName"], + deviceEnName: parsedJson["deviceEnName"], + deviceImages: parsedJson["deviceImages"], + deviceModel: parsedJson["deviceModel"], + deviceName: parsedJson["deviceName"], + deviceRequired: parsedJson["deviceRequired"], + deviceSN: parsedJson["deviceSN"], + engineerName: parsedJson["engineerName"], + engineerPhone: parsedJson["engineerPhone"], + facebook: parsedJson["facebook"], + faultDescription: parsedJson["faultDescription"], + general: parsedJson["general"], + hospital: parsedJson["hospital"], + hospitalRequired: parsedJson["hospitalRequired"], + hotLine: parsedJson["hotLine"], + jobSheetNumber: parsedJson["jobSheetNumber"], + linkedIn: parsedJson["linkedIn"], + maintenanceIssue: parsedJson["maintenanceIssue"], + maintenanceIssueRequired: parsedJson["maintenanceIssueRequired"], + maxImagesNumberIs5: parsedJson["maxImagesNumberIs5"], + model: parsedJson["model"], + nameExist: parsedJson["nameExist"], + newServiceRequest: parsedJson["newServiceRequest"], + newWord: parsedJson["newWord"], + noDateFound: parsedJson["noDateFound"], + noDeviceFound: parsedJson["noDeviceFound"], + noHospitalFound: parsedJson["noHospitalFound"], + noModelFound: parsedJson["noModelFound"], + noServiceRequestFound: parsedJson["noServiceRequestFound"], + noSnFound: parsedJson["noSnFound"], + notifications: parsedJson["notifications"], + notificationsNotFound: parsedJson["notificationsNotFound"], + noUniteFound: parsedJson["noUniteFound"], + ourWebsite: parsedJson["ourWebsite"], + pickDevice: parsedJson["pickDevice"], + pickHospital: parsedJson["pickHospital"], + pickUnite: parsedJson["pickUnite"], + policy: parsedJson["policy"], + reason1: parsedJson["reason1"], + reason2: parsedJson["reason2"], + reason3: parsedJson["reason3"], + reason4: parsedJson["reason4"], + reason5: parsedJson["reason5"], + repaired: parsedJson["repaired"], + repeated: parsedJson["repeated"], + reportIssue: parsedJson["reportIssue"], + requestInformation: parsedJson["requestInformation"], + searchBySn: parsedJson["searchBySn"], + serviceRequestInformation: parsedJson["serviceRequestInformation"], + serviceRequests: parsedJson["serviceRequests"], + shareAntherIssue: parsedJson["shareAntherIssue"], + shareApp: parsedJson["shareApp"], + sn: parsedJson["sn"], + submit: parsedJson["submit"], + trackServiceRequest: parsedJson["trackServiceRequest"], + twitter: parsedJson["twitter"], + underRepair: parsedJson["underRepair"], + unite: parsedJson["unite"], + unitRequired: parsedJson["uniteRequired"], + visitDate: parsedJson["visitDate"], + whatsApp: parsedJson["whatsApp"], + workPerformed: parsedJson["workPerformed"], + + actualDate: parsedJson["actualDate"], + done: parsedJson["done"], + expectDate: parsedJson["expectDate"], + images: parsedJson["images"], + imagesRequired: parsedJson["imagesRequired"], + noSerialNumberFound: parsedJson["noSerialNumberFound"], + notYet: parsedJson["notYet"], + noVisitsFound: parsedJson["noVisitsFound"], + onHold: parsedJson["onHold"], + pickFromCamera: parsedJson["pickFromCamera"], + pickFromGallery: parsedJson["pickFromGallery"], + preventiveMaintenance: parsedJson["preventiveMaintenance"], + preventiveMaintenanceUpdatedSuccessfully: parsedJson["preventiveMaintenanceUpdatedSuccessfully"], + regularVisits: parsedJson["regularVisits"], + regularVisitsUpdatedSuccessfully: parsedJson["regularVisitsUpdatedSuccessfully"], + requiredStatus: parsedJson["requiredStatus"], + updatePreventiveMaintenance: parsedJson["updatePreventiveMaintenance"], + updateRegularVisits: parsedJson["updateRegularVisits"], + updateVisitsGroup: parsedJson["updateVisitsGroup"], + updatingDots: parsedJson["updatingDots"], + visitInformation: parsedJson["visitInformation"], + nextVisitDate: parsedJson["expectedVisitDate"], + contactStatus: parsedJson["contactStatus"], + travelingHours: parsedJson["travelingHours"], + workingHours: parsedJson["workingHours"], + image: parsedJson["image"], + pickImage: parsedJson["pickImage"], + requiredImage: parsedJson["requiredImage"], + taskStatus: parsedJson["taskStatus"], + activationAlert: parsedJson["activationAlert"], + + attachImage: parsedJson["attachImage"], + callLastSituation: parsedJson["callLastSituation"], + customer: parsedJson["customer"], + editServiceReport: parsedJson["editServiceReport"], + invoiceCode: parsedJson["invoiceCode"], + invoiceNumber: parsedJson["invoiceNumber"], + newServiceReport: parsedJson["newServiceReport"], + number: parsedJson["number"], + operatingHours: parsedJson["operatingHours"], + partNumber: parsedJson["partNumber"], + quantity: parsedJson["quantity"], + reasons: parsedJson["reasons"], + reportStatus: parsedJson["reportStatus"], + reportType: parsedJson["reportType"], + callId: parsedJson["callId"], + requiredWord: parsedJson["requiredWord"], + serviceType: parsedJson["serviceType"], + workPreformed: parsedJson["workPreformed"], + + alert: parsedJson["alert"], + duplicateAlert: parsedJson["duplicateAlert"], + duplicateAlertMessage: parsedJson["duplicateAlertMessage"], + duplicateRequest: parsedJson["duplicateRequest"], + ); + } + +} \ No newline at end of file diff --git a/lib/models/timer_model.dart b/lib/models/timer_model.dart new file mode 100644 index 0000000..191128f --- /dev/null +++ b/lib/models/timer_model.dart @@ -0,0 +1,7 @@ +class TimerModel { + DateTime startAt; + DateTime endAt; + int durationInSecond; + + TimerModel({this.startAt,this.endAt,this.durationInSecond}); +} \ No newline at end of file diff --git a/lib/models/user.dart b/lib/models/user.dart new file mode 100644 index 0000000..10fb06b --- /dev/null +++ b/lib/models/user.dart @@ -0,0 +1,120 @@ +import 'package:test_sa/controllers/notification/firebase_notification_manger.dart'; +import 'package:test_sa/models/department.dart'; +import 'package:test_sa/models/enums/user_types.dart'; +import 'package:test_sa/models/hospital.dart'; + +class User{ + String id; + String userName; + String password; + String email; + String image; + String hospitalID; + Hospital hospital; + Department department; + UsersTypes type; + String phoneNumber; + String whatsApp; + String token; + bool isActive; + + + User({ + this.id, + this.userName = "", + this.email = "", + this.password = "", + this.phoneNumber = "", + this.hospital, + this.image, + this.department, + this.type, + this.whatsApp, + this.token, + this.isActive = false + }); + + Future> toLoginJson() async { + if(FirebaseNotificationManger.token == null) + await FirebaseNotificationManger.getToken(); + return { + "username":userName, + "pass":password, + "firebase_token": FirebaseNotificationManger?.token ?? "", + }; + } + + Map toUpdateProfileJson(){ + Map jsonObject ={}; + if(department?.id != null && department.id.isNotEmpty) + jsonObject["department"] = department.id; + if(whatsApp != null && whatsApp.isNotEmpty) + jsonObject["whatsapp"] = whatsApp; + if(phoneNumber != null && phoneNumber.isNotEmpty) + jsonObject["phone"] = phoneNumber; + return jsonObject; + } + + Future> toRegisterJson() async { + if(FirebaseNotificationManger.token == null) + await FirebaseNotificationManger.getToken(); + return { + "username": userName, + "email":email, + "whatsapp":whatsApp, + "client":hospital.id, + "department":department?.id, + "phone":phoneNumber, + "pass":password, + "firebase_token": FirebaseNotificationManger?.token ?? "", + }; + } + + Map toJson(){ + return { + "uid":id, + "name": userName, + "email":email, + "token":token, + "phone":phoneNumber, + "whatsapp":whatsApp, + "client":hospital?.id, + "client_name":hospital?.name, + "department":department?.id, + "department_name":department?.name, + "password":password, + "picture":image, + "active":isActive ? 1 : 0, + "role": type == UsersTypes.engineer + ? "field_engineer" : "normal_user" , + // "token":token, pass is token + }; + } + + factory User.fromJson(Map parsedJson){ + UsersTypes type; + switch(parsedJson["role"]){ + case "field_engineer": type = UsersTypes.engineer; break; + default: type = UsersTypes.normal_user; break; + } + return User( + id: parsedJson["uid"], + userName: parsedJson["name"] ?? parsedJson["title"], + email: parsedJson["mail"] ?? parsedJson["email"], + hospital: Hospital( + id: parsedJson["client"], + name: parsedJson["client_name"] + ), + department: Department( + id: parsedJson["department"], + name: parsedJson["department_name"], + ), + image: parsedJson["picture"], + phoneNumber: parsedJson["phone"], + whatsApp: parsedJson["whatsapp"], + token: parsedJson["token"], + isActive: parsedJson["active"] == "1", + type:type + ); + } +} \ No newline at end of file diff --git a/lib/models/visits/visit.dart b/lib/models/visits/visit.dart new file mode 100644 index 0000000..55071bf --- /dev/null +++ b/lib/models/visits/visit.dart @@ -0,0 +1,67 @@ +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/timer_model.dart'; + +class Visit{ + String id; + String serialNumber; + String expectDate; + String actualDate; + String hospitalId; + String hospitalName; + String deviceId; + String deviceSerialNumber; + String deviceArabicName; + String deviceEnglishName; + String employId; + String employName; + String modelAndBrand; + String contactStatus; + Status status; + String assignTo; + List images; + + Visit({ + this.id, + this.serialNumber, + this.hospitalId, + this.hospitalName, + this.deviceId, + this.deviceSerialNumber, + this.deviceArabicName, + this.deviceEnglishName, + this.employId, + this.employName, + this.expectDate, + this.actualDate, + this.status, + this.modelAndBrand, + this.contactStatus, + this.images, + this.assignTo, + }); + + factory Visit.fromJson(Map parsedJson){ + return Visit( + id: parsedJson["nid"], + serialNumber: parsedJson["title"], + hospitalId: parsedJson["client"], + hospitalName: parsedJson["client_name"], + deviceId: parsedJson["medical_equipment_nid"], + deviceSerialNumber: parsedJson["medical_equipment"], + deviceEnglishName: parsedJson["equipment_english_name"], + deviceArabicName: parsedJson["equipment_arabic_name"], + employId: parsedJson["assigned_employee"], + employName: parsedJson["assigned_employee_name"], + expectDate: parsedJson["expected_date"], + actualDate: parsedJson["actual_date"], + modelAndBrand: parsedJson["mode_brand"], + contactStatus: parsedJson["contactStatus"], + images: List.from(parsedJson["images"] ?? []), + status: Status( + id: int.tryParse(parsedJson["status"] ?? "-1"), // actual value (0,1,2) + label: parsedJson["status_value"] // text value + ), + assignTo: parsedJson["assigned_to"], + ); + } +} \ No newline at end of file diff --git a/lib/models/visits/visits_group.dart b/lib/models/visits/visits_group.dart new file mode 100644 index 0000000..b408e93 --- /dev/null +++ b/lib/models/visits/visits_group.dart @@ -0,0 +1,45 @@ + +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/visits/visit.dart'; + +class VisitsGroup{ + String userId; + String workingHours; + String travelingHours; + String jobSheetNumber; + String image; + Status status; + Status taskStatus; + DateTime date; + List visits; + + VisitsGroup({ + this.userId, + this.status, + this.date, + this.jobSheetNumber, + this.travelingHours, + this.workingHours, + this.visits, + this.image, + this.taskStatus, + }); + + Map toJson(){ + Map jsonObject = {}; + jsonObject["nids"] = visits.map((e) => e.id).toList().join(','); + if(status != null) jsonObject["status"] = status.id.toString(); + if(date != null) jsonObject["date"] = date.toString().split(" ").first; + if(jobSheetNumber != null && jobSheetNumber.isNotEmpty) + jsonObject["job_sheet_no"] = jobSheetNumber; + if(travelingHours != null && travelingHours.isNotEmpty) + jsonObject["traveling_hours"] = travelingHours; + if(workingHours != null && workingHours.isNotEmpty) + jsonObject["working_hours"] = workingHours; + if(image != null) jsonObject["image"] = image; + if(taskStatus != null) jsonObject["task_status"] = taskStatus.id.toString(); + return jsonObject; + } + + +} \ No newline at end of file diff --git a/lib/models/visits/visits_search.dart b/lib/models/visits/visits_search.dart new file mode 100644 index 0000000..7cfd4ce --- /dev/null +++ b/lib/models/visits/visits_search.dart @@ -0,0 +1,83 @@ +class VisitsSearch{ + String deviceSerialNumber; + String hospitalName; + String brand; + String model; + String contactStatus; + DateTime expectedDateFrom; + DateTime expectedDateTo; + DateTime actualDateFrom; + DateTime actualDateTo; + int statusValue; + + VisitsSearch({ + this.deviceSerialNumber, + this.statusValue, + this.brand, + this.hospitalName, + this.actualDateTo, + this.actualDateFrom, + this.model, + this.contactStatus, + this.expectedDateFrom, + this.expectedDateTo, + }); + + fromSearch(VisitsSearch newSearch){ + deviceSerialNumber = newSearch.deviceSerialNumber; + brand = newSearch.brand; + hospitalName = newSearch.hospitalName; + actualDateTo = newSearch.actualDateTo; + actualDateFrom = newSearch.actualDateFrom; + model = newSearch.model; + contactStatus = newSearch.contactStatus; + expectedDateFrom = newSearch.expectedDateFrom; + expectedDateTo = newSearch.expectedDateTo; + statusValue = newSearch.statusValue; + } + + String toSearchString(){ + String _search = ""; + if(deviceSerialNumber != null && deviceSerialNumber.isNotEmpty){ + _search += "&sn_id=$deviceSerialNumber"; + } + + if(hospitalName != null && hospitalName.isNotEmpty){ + _search += "&client=$hospitalName"; + } + + if(brand != null && brand.isNotEmpty){ + _search += "&brand=$brand"; + } + + if(model != null && model.isNotEmpty){ + _search += "&model=$model"; + } + + if(expectedDateFrom != null){ + _search += "&expected_date_from=${expectedDateFrom.millisecondsSinceEpoch ~/ 1000}"; + } + + if(expectedDateTo != null){ + _search += "&expected_date_to=${expectedDateTo.millisecondsSinceEpoch~/1000}"; + } + + if(actualDateFrom != null){ + _search += "&actual_date_from=${actualDateFrom.millisecondsSinceEpoch ~/ 1000}"; + } + + if(actualDateTo != null){ + _search += "&actual_date_to=${actualDateTo.millisecondsSinceEpoch~/1000}"; + } + + if(statusValue != null){ + _search += "&status=$statusValue"; + } + + if(contactStatus != null){ + _search += "&assigned_to=$contactStatus"; + } + return _search; + } +} + diff --git a/lib/views/app_style/colors.dart b/lib/views/app_style/colors.dart new file mode 100644 index 0000000..6d87c5f --- /dev/null +++ b/lib/views/app_style/colors.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/models/status.dart'; +class AColors { + AColors._(); + static const Color white = Color(0xffffffff); + static const Color black = Color(0xff000000); + static const Color grey = Color(0xffe1e7e7); + static const green = Colors.green; + static const Color orange = Colors.orange; + static const Color deepOrange = Colors.deepOrangeAccent; + static const Color red = Colors.red; + static const Color deepRed = Color(0xFFD32F2F); + static const Color scaffoldBackgroundColor = Color(0xffffffff); + static const Color secondaryColor = Color(0xff111427); + static const Color primaryColor = Color(0xff3B7097); + static const Color cyan = Color(0xff4A8DB7); + static const Color onPrimaryColor = Color(0xffffffff); + + static Color getRequestStatusColor(int id){ + switch(id){ + case 4: return AColors.deepRed; + case 6: return AColors.green; + case 5: return AColors.orange; + case 8: return AColors.green; + case 9: return AColors.orange; + default : return AColors.grey; + } + } + + static Color getGasStatusColor(int id){ + switch(id){ + case 0: return AColors.orange; + case 1: return AColors.green; + default : return AColors.grey; + } + } +} + diff --git a/lib/views/app_style/sizing.dart b/lib/views/app_style/sizing.dart new file mode 100644 index 0000000..c7b9436 --- /dev/null +++ b/lib/views/app_style/sizing.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +class AppStyle { + AppStyle._(); + + static const double borderRadius = 12; + + static const BoxShadow boxShadow = BoxShadow( + color: Colors.black26, + blurRadius: 3, + offset: Offset(0,2) + ); + + static double getBorderRadius(BuildContext context){ + return borderRadius * getScaleFactor(context); + } + + static double getScaleFactor(BuildContext context){ + return MediaQuery.of(context).orientation == Orientation.portrait + ? MediaQuery.of(context).size.width/(360) > 1.5 + ? 1.5 : MediaQuery.of(context).size.width/(360) + : MediaQuery.of(context).size.height/(360) > 1.5 + ? 1.5 : MediaQuery.of(context).size.height/(360); + } + + static BorderRadius getCardBorder(BuildContext context){ + return BorderRadius.only( + topRight: Radius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + topLeft: Radius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + bottomRight: Radius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + ); + } + +} \ No newline at end of file diff --git a/lib/views/pages/device_transfer/device_transfer_details.dart b/lib/views/pages/device_transfer/device_transfer_details.dart new file mode 100644 index 0000000..9b08944 --- /dev/null +++ b/lib/views/pages/device_transfer/device_transfer_details.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/device_transfer_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/device/device_transfer.dart'; +import 'package:test_sa/models/device/device_transfer_info.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/pages/device_transfer/update_device_transfer.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_small_button.dart'; +import 'package:test_sa/views/widgets/device_trancfer/device_transfer_info_section.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/requests/info_row.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +class DeviceTransferDetails extends StatefulWidget { + final DeviceTransfer model; + const DeviceTransferDetails({Key key, this.model}) : super(key: key); + + @override + State createState() => _DeviceTransferDetailsState(); +} + +class _DeviceTransferDetailsState extends State { + + final DeviceTransferInfo _model = DeviceTransferInfo(); + bool _isSender = false; + bool _isReceiver = false; + UserProvider _userProvider; + SettingProvider _settingProvider; + DeviceTransferProvider _deviceTransferProvider; + bool _isLoading = false; + Subtitle _subtitle; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + void initState() { + + + super.initState(); + } + + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _deviceTransferProvider = Provider.of(context); + print(_userProvider.user.id); + _isSender = _userProvider.user.id == widget.model.sender?.userId; + _isReceiver = _userProvider.user.id == widget.model.receiver?.userId; + return Scaffold( + key: _scaffoldKey, + body: SafeArea( + child: Form( + key: _formKey, + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Column( + children: [ + Container( + color: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Row( + children: [ + const ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.details, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + const SizedBox(width: 58), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + padding: EdgeInsets.all(16*AppStyle.getScaleFactor(context)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RequestInfoRow( + title: _subtitle.title, + info: widget.model.title, + ), + RequestInfoRow( + title: _subtitle.device, + info: widget.model.device.serialNumber, + ), + const SizedBox(height:8), + Row( + children: [ + Expanded( + child: Text( + "Sender", + style: Theme.of(context).textTheme.headline6 + ), + ), + if(_isSender) + ASmallButton( + text: _subtitle.edit, + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_)=> UpdateDeviceTransfer(model: widget.model,isSender: _isSender,) + ) + ); + }, + ), + ], + ), + const SizedBox(height:12), + DeviceTransferInfoSection( + info: widget.model.sender, + ), + const SizedBox(height:8), + Row( + children: [ + Expanded( + child: Text( + "Receiver", + style: Theme.of(context).textTheme.headline6 + ), + ), + if(_isReceiver) + ASmallButton( + text: _subtitle.edit, + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_)=> UpdateDeviceTransfer(model: widget.model,isSender: _isSender,) + ) + ); + }, + ), + ], + ), + const SizedBox(height:12), + DeviceTransferInfoSection( + info: widget.model.receiver, + ), + + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/device_transfer/request_device_transfer.dart b/lib/views/pages/device_transfer/request_device_transfer.dart new file mode 100644 index 0000000..f8ad694 --- /dev/null +++ b/lib/views/pages/device_transfer/request_device_transfer.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/providers/api/device_transfer_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/device/device_transfer.dart'; +import 'package:test_sa/models/device/device_transfer_info.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/departments/department_button.dart'; +import 'package:test_sa/views/widgets/e_signature/e_signature.dart'; +import 'package:test_sa/views/widgets/equipment/device_button.dart'; +import 'package:test_sa/views/widgets/hospitals/hospital_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; + +import '../../../../controllers/localization/localization.dart'; +class RequestDeviceTransfer extends StatefulWidget { + static const String id = "/request-device-transfer"; + const RequestDeviceTransfer({Key key}) : super(key: key); + + @override + State createState() => _RequestDeviceTransferState(); +} + +class _RequestDeviceTransferState extends State { + bool _isLoading = false; + bool _validate = false; + Subtitle _subtitle; + UserProvider _userProvider; + SettingProvider _settingProvider; + DeviceTransferProvider _deviceTransferProvider; + final TextEditingController _requestedQuantityController = TextEditingController(); + final DeviceTransfer _formModel = DeviceTransfer(receiver: DeviceTransferInfo()); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + void setState(VoidCallback fn){ + if(mounted) super.setState(() {}); + } + + _onSubmit() async { + _validate = true; + if(!_formKey.currentState.validate()){ + setState(() {}); + return false; + } + _formKey.currentState.save(); + + if(!_formModel.validate()) { + setState(() { }); + return false; + } + + _isLoading = true; + setState(() {}); + + int status = await _deviceTransferProvider.createRequest( + user: _userProvider.user, + host: _settingProvider.host, + model: _formModel, + ); + _isLoading =false; + setState(() {}); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.requestCompleteSuccessfully, + ); + Navigator.of(context).pop(); + }else{ + String errorMessage = HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + } + + @override + void dispose() { + _requestedQuantityController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _deviceTransferProvider = Provider.of(context,listen: false); + return Scaffold( + key: _scaffoldKey, + body: Form( + key: _formKey, + child: SafeArea( + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: SingleChildScrollView( + padding: EdgeInsets.all(12 * AppStyle.getScaleFactor(context)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Transfer Device", + style: Theme.of(context).textTheme.headline5.copyWith( + color: Theme.of(context).primaryColor, + fontSize: 28, + fontWeight: FontWeight.bold + ), + ), + ), + ), + // const SizedBox(height: 8,), + // ASubTitle(_subtitle.title), + // if(_validate && _formModel.title == null || _formModel.title?.isEmpty == true) + // ASubTitle(_subtitle.requiredWord,color: Colors.red,), + // const SizedBox(height: 4,), + // ATextFormField( + // initialValue: _formModel?.title, + // textAlign: TextAlign.center, + // style: Theme.of(context).textTheme.subtitle1, + // textInputType: TextInputType.text, + // onSaved: (value){ + // _formModel.title = value; + // }, + // ), + const SizedBox(height: 8,), + const ASubTitle("Device"), + if(_validate && _formModel.device == null) + ASubTitle(_subtitle.requiredWord,color: Colors.red,), + const SizedBox(height: 4,), + DeviceButton( + device: _formModel.device, + onDevicePick: (device){ + _formModel.device = device; + setState(() {}); + }, + ), + // const SizedBox(height: 8,), + // const ASubTitle("Sender Department"), + // if(_validate && _formModel.senderDepartment == null) + // ASubTitle(_subtitle.requiredWord,color: Colors.red,), + // const SizedBox(height: 4,), + // DepartmentButton( + // department: _formModel.senderDepartment, + // onDepartmentPick: (department){ + // _formModel.senderDepartment = department; + // setState(() {}); + // }, + // ), + const SizedBox(height: 8,), + const ASubTitle("Destination Client"), + if(_validate && _formModel.receiver.client == null) + ASubTitle(_subtitle.requiredWord,color: Colors.red,), + const SizedBox(height: 4,), + HospitalButton( + hospital: _formModel.receiver.client, + onHospitalPick: (hospital){ + _formModel.receiver.client = hospital; + setState(() {}); + }, + ), + const SizedBox(height: 8,), + const ASubTitle("Destination Department"), + if(_validate && _formModel.receiver.department == null) + ASubTitle(_subtitle.requiredWord,color: Colors.red,), + const SizedBox(height: 4,), + DepartmentButton( + department: _formModel.receiver.department, + onDepartmentPick: (department){ + _formModel.receiver.department = department; + setState(() {}); + }, + ), + + Padding( + padding: const EdgeInsets.all(16.0), + child: AButton( + text: _subtitle.submit, + onPressed: _onSubmit, + ), + ), + const SizedBox(height: 100,) + ], + ), + ), + ), + ), + ), + ); + } +} + diff --git a/lib/views/pages/device_transfer/track_device_transfer.dart b/lib/views/pages/device_transfer/track_device_transfer.dart new file mode 100644 index 0000000..02da494 --- /dev/null +++ b/lib/views/pages/device_transfer/track_device_transfer.dart @@ -0,0 +1,103 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/device_transfer_provider.dart'; +import 'package:test_sa/controllers/providers/api/gas_refill_provider.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/service_request/service_request_search.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/widgets/device_trancfer/device_transfer_list.dart'; +import 'package:test_sa/views/widgets/gas_refill/gas_refill_list.dart'; +import 'package:test_sa/views/widgets/requests/service_request_list.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/search/service_request_search_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class TrackDeviceTransferPage extends StatefulWidget { + static const String id = "/track-device-transfer"; + + const TrackDeviceTransferPage({Key key}) : super(key: key); + + @override + State createState() => _TrackDeviceTransferPageState(); +} + +class _TrackDeviceTransferPageState extends State + with TickerProviderStateMixin{ + DeviceTransferProvider _deviceTransferProvider; + UserProvider _userProvider; + SettingProvider _settingProvider; + @override + Widget build(BuildContext context) { + _deviceTransferProvider = Provider.of(context); + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + Subtitle _subtitle = AppLocalization.of(context).subtitle; + + return Scaffold( + body: SafeArea( + child: LoadingManager( + isLoading: _deviceTransferProvider.isLoading, + isFailedLoading: _deviceTransferProvider.items == null, + stateCode: _deviceTransferProvider.stateCode, + onRefresh: () async { + _deviceTransferProvider.reset(); + await _deviceTransferProvider.getRequests( + user: _userProvider.user, + host: _settingProvider.host, + ); + }, + child: Stack( + children: [ + Column( + children: [ + Container( + color:AColors.primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Column( + children: [ + Row( + children: [ + ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.serviceRequests, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + const SizedBox(width: 48,) + ], + ), + + ], + ), + ), + Expanded( + child: DeviceTransferList( + nextPage: _deviceTransferProvider.nextPage, + onLazyLoad: () async { + await _deviceTransferProvider.getRequests( + user: _userProvider.user, + host: _settingProvider.host, + ); + }, + items: _deviceTransferProvider.items, + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/device_transfer/update_device_transfer.dart b/lib/views/pages/device_transfer/update_device_transfer.dart new file mode 100644 index 0000000..f9044b6 --- /dev/null +++ b/lib/views/pages/device_transfer/update_device_transfer.dart @@ -0,0 +1,233 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/providers/api/device_transfer_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/device/device_transfer.dart'; +import 'package:test_sa/models/device/device_transfer_info.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/departments/department_button.dart'; +import 'package:test_sa/views/widgets/e_signature/e_signature.dart'; +import 'package:test_sa/views/widgets/equipment/device_button.dart'; +import 'package:test_sa/views/widgets/hospitals/hospital_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/gas_refill/gas_status.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; + +import '../../../../controllers/localization/localization.dart'; +class UpdateDeviceTransfer extends StatefulWidget { + final DeviceTransfer model; + final bool isSender; + const UpdateDeviceTransfer({Key key, this.model, this.isSender}) : super(key: key); + + @override + State createState() => _UpdateDeviceTransferState(); +} + +class _UpdateDeviceTransferState extends State { + bool _isLoading = false; + bool _validate = false; + Subtitle _subtitle; + UserProvider _userProvider; + SettingProvider _settingProvider; + Uint8List _signature; + DeviceTransferProvider _deviceTransferProvider; + final TextEditingController _requestedQuantityController = TextEditingController(); + final DeviceTransferInfo _formModel = DeviceTransferInfo(); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + + _update() async { + _validate = true; + if(!_formKey.currentState.validate()){ + setState(() {}); + return false; + } + _formKey.currentState.save(); + + _isLoading =true; + setState(() {}); + int status = await _deviceTransferProvider.updateRequest( + user: _userProvider.user, + host: _settingProvider.host, + requestId: widget.model.id, + isSender: widget.isSender, + newModel: _formModel, + oldModel: widget.model + ); + _isLoading =false; + setState(() {}); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.requestCompleteSuccessfully, + ); + _validate = false; + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }else{ + String errorMessage = HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + } + + @override + void setState(VoidCallback fn){ + if(mounted) super.setState(() {}); + } + + @override + void initState() { + _formModel.fromDetails(widget.isSender + ? widget.model.sender : widget.model.receiver, + withSignature: false + ); + super.initState(); + } + + @override + void dispose() { + _requestedQuantityController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _deviceTransferProvider = Provider.of(context,listen: false); + return Scaffold( + key: _scaffoldKey, + body: Form( + key: _formKey, + child: SafeArea( + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: SingleChildScrollView( + padding: EdgeInsets.all(12 * AppStyle.getScaleFactor(context)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Edit Transfer Device", + style: Theme.of(context).textTheme.headline5.copyWith( + color: Theme.of(context).primaryColor, + fontSize: 28, + fontWeight: FontWeight.bold + ), + ), + ), + ), + const SizedBox(height: 8,), + ASubTitle("Comment"), + + const SizedBox(height: 4,), + ATextFormField( + initialValue: _formModel?.comment, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + textInputType: TextInputType.text, + onSaved: (value){ + _formModel.comment = value; + }, + ), + const SizedBox(height: 8,), + ASubTitle(_subtitle.travelingHours), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _formModel?.travelingHours, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + textInputType: TextInputType.number, + onSaved: (value){ + _formModel.travelingHours = value; + }, + ), + const SizedBox(height: 8,), + ASubTitle(_subtitle.workingHours), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _formModel?.workingHours, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + textInputType: TextInputType.number, + onSaved: (value){ + _formModel.workingHours = value; + }, + ), + // const SizedBox(height: 8,), + // const ASubTitle("Sender Department"), + // if(_validate && _formModel.senderDepartment == null) + // ASubTitle(_subtitle.requiredWord,color: Colors.red,), + // const SizedBox(height: 4,), + // DepartmentButton( + // department: _formModel.senderDepartment, + // onDepartmentPick: (department){ + // _formModel.senderDepartment = department; + // setState(() {}); + // }, + // ), + const SizedBox(height: 8,), + ASubTitle(_subtitle.status), + const SizedBox(height: 4,), + GasStatusMenu( + initialValue: _formModel.status, + onSelect: (status){ + _formModel.status = status; + setState(() {}); + }, + ), + const SizedBox(height: 8,), + const ASubTitle("Signature"), + // if(_validate && _formModel.signature == null) + // ASubTitle(_subtitle.requiredWord,color: Colors.red,), + const SizedBox(height: 4,), + ESignature( + oldSignature: widget.isSender + ? widget.model.sender.signature + : widget.model.receiver.signature, + newSignature: _signature, + onSaved: (signature){ + _signature = signature; + _formModel.signature = base64Encode(signature); + }, + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: AButton( + text: _subtitle.update, + onPressed: _update, + ), + ), + const SizedBox(height: 100,) + ], + ), + ), + ), + ), + ), + ); + } +} + diff --git a/lib/views/pages/login.dart b/lib/views/pages/login.dart new file mode 100644 index 0000000..9d06692 --- /dev/null +++ b/lib/views/pages/login.dart @@ -0,0 +1,164 @@ +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/notification/notification_manger.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/pages/register.dart'; +import 'package:test_sa/views/pages/user/land_page.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_flat_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; + +import '../widgets/buttons/app_outlined_button.dart'; +class Login extends StatefulWidget { + static final String id = "/login"; + @override + _LoginState createState() => _LoginState(); +} + +class _LoginState extends State { + UserProvider _userProvider; + SettingProvider _settingProvider; + User _user = User(); + bool _obscurePassword = true; + bool _firstTime = true; + double _height; + double _width; + String _payload; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _height = MediaQuery.of(context).size.height; + _width = MediaQuery.of(context).size.width; + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + key: _scaffoldKey, + body:SafeArea( + child: LoadingManager( + isLoading: _userProvider.isLoading || !_settingProvider.isLoaded, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Form( + key: _formKey, + child: SingleChildScrollView( + //padding: EdgeInsets.symmetric(horizontal: 32), + child: Column( + children: [ + //AppNameBar(), + SizedBox(height: MediaQuery.of(context).size.height / 5,), + Hero( + tag: "logo", + child: Image( + height: _height/6, + fit: BoxFit.contain, + image: AssetImage("assets/images/logo.png"), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: 24 * AppStyle.getScaleFactor(context), + vertical: 24 * AppStyle.getScaleFactor(context) + ), + child: Column( + children: [ + SizedBox(height: 24 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _user?.userName, + hintText: _subtitle.name, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headline6, + prefixIconData: Icons.account_circle, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.nameValidateMessage, + textInputType: TextInputType.name, + onSaved: (value){ + _user.userName = value; + }, + ), + SizedBox(height: 24 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _user?.password, + hintText: _subtitle.password, + obscureText: _obscurePassword, + style: Theme.of(context).textTheme.headline6, + prefixIconData: Icons.vpn_key_sharp, + textAlign: TextAlign.center, + validator: (value) => Validator.isValidPassword(value) + ? null : _subtitle.passwordValidateMessage, + showPassword: (){ + _obscurePassword = !_obscurePassword; + setState(() {}); + }, + onSaved: (value){ + _user.password = value; + }, + ), + SizedBox(height: 32 * AppStyle.getScaleFactor(context),), + AButton( + text: _subtitle.signIn, + onPressed: () async { + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + int status = await _userProvider.login( + user: _user, + host: _settingProvider.host, + ); + if(status >= 200 && status < 300){ + _settingProvider.setUser(_userProvider.user); + if(_userProvider.user.isActive) + Navigator.of(context).pushNamed(LandPage.id); + else + Fluttertoast.showToast(msg: _subtitle.activationAlert); + }else{ + String errorMessage = status == 400 + ? _subtitle.wrongEmailOrPassword + : HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + }, + ), + SizedBox(height: 140 * AppStyle.getScaleFactor(context),), + AOutLinedButton( + text: _subtitle.signUp, + //color: AColors.cyan, + onPressed: (){ + Navigator.of(context).pushNamed(Register.id); + }, + ), + + SizedBox(height: 32,), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/register.dart b/lib/views/pages/register.dart new file mode 100644 index 0000000..04312e9 --- /dev/null +++ b/lib/views/pages/register.dart @@ -0,0 +1,258 @@ +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/departments/department_button.dart'; +import 'package:test_sa/views/widgets/hospitals/hospital_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; + +class Register extends StatefulWidget { + static final String id = "/register"; + @override + _RegisterState createState() => _RegisterState(); +} + +class _RegisterState extends State { + UserProvider _userProvider; + SettingProvider _settingProvider; + double _width; + double _height; + User _user = User(); + bool _obscurePassword = true; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + @override + Widget build(BuildContext context) { + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _width = MediaQuery.of(context).size.width; + _height = MediaQuery.of(context).size.height; + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + key: _scaffoldKey, + body:LoadingManager( + isLoading: _userProvider.isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: SafeArea( + child: Stack( + children: [ + Form( + key: _formKey, + child: ListView( + children: [ + //AppNameBar(), + //SizedBox(height: 16,), + Hero( + tag: "logo", + child: Padding( + padding: const EdgeInsets.all(16), + child: Image( + height: _height/6, + image: AssetImage("assets/images/logo.png"), + ), + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 16), + margin: EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: AColors.cyan, + borderRadius: BorderRadius.circular(AppStyle.getBorderRadius(context)), + boxShadow: [ + BoxShadow( + color: AColors.grey, + offset: Offset(0,-1), + ) + ] + ), + child: Column( + children: [ + ATextFormField( + initialValue: _user.userName, + hintText: _subtitle.name, + prefixIconData: Icons.account_circle, + style: Theme.of(context).textTheme.headline6, + validator: (value) => Validator.hasValue(value) + ? null : _subtitle.nameValidateMessage, + onSaved: (value){ + _user.userName = value; + }, + ), + SizedBox(height: 8,), + ATextFormField( + initialValue: _user.email, + hintText: _subtitle.email, + prefixIconData: Icons.email, + textInputType: TextInputType.emailAddress, + style: Theme.of(context).textTheme.headline6, + validator: (value) => Validator.isEmail(value) + ? null : _subtitle.emailValidateMessage, + onSaved: (value){ + _user.email = value; + }, + ), + SizedBox(height: 8,), + ATextFormField( + initialValue: _user.password, + hintText: _subtitle.password, + prefixIconData: Icons.vpn_key_sharp, + style: Theme.of(context).textTheme.headline6, + obscureText: _obscurePassword, + validator: (value) => Validator.isValidPassword(value) + ? null : _subtitle.passwordValidateMessage, + showPassword: (){ + _obscurePassword = !_obscurePassword; + setState(() {}); + }, + onSaved: (value){ + _user.password = value; + }, + onChange: (value){ + _user.password = value; + }, + ), + SizedBox(height: 8,), + ATextFormField( + initialValue: _user.password, + prefixIconData: Icons.vpn_key_sharp, + hintText: _subtitle.confirmPassword, + style: Theme.of(context).textTheme.headline6, + obscureText: _obscurePassword, + validator: (value) => + _user.password == value + ? null : _subtitle.confirmPasswordValidateMessage, + showPassword: (){ + _obscurePassword = !_obscurePassword; + setState(() {}); + }, + ), + SizedBox(height: 8,), + HospitalButton( + hospital: _user.hospital, + onHospitalPick: (hospital){ + _user.hospital = hospital; + setState(() {}); + }, + ), + SizedBox(height: 8,), + DepartmentButton( + department: _user.department, + onDepartmentPick: (department){ + _user.department = department; + setState(() {}); + }, + ), + SizedBox(height: 8,), + ATextFormField( + initialValue: _user.phoneNumber, + hintText: _subtitle.phoneNumber, + style: Theme.of(context).textTheme.headline6, + prefixIconData: Icons.phone_android, + validator: (value) => + Validator.isPhoneNumber(value) + ? null : _subtitle.phoneNumberValidateMessage, + textInputType: TextInputType.phone, + onSaved: (value){ + _user.phoneNumber = value; + }, + ), + SizedBox(height: 8,), + ATextFormField( + initialValue: _user.whatsApp, + hintText: _subtitle.whatsApp, + style: Theme.of(context).textTheme.headline6, + prefixIconData: FontAwesomeIcons.whatsapp, + prefixIconSize: 36, + validator: (value) => + Validator.isPhoneNumber(value) ? null : _subtitle.phoneNumberValidateMessage, + textInputType: TextInputType.phone, + onSaved: (value){ + _user.whatsApp = value; + }, + ), + ], + ), + ), + SizedBox(height: 16,), + Center( + child: SizedBox( + height: _width / 8, + width: _width/1.2, + child: AButton( + text: _subtitle.signUp, + onPressed: () async { + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + if(_user.hospital == null){ + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + _subtitle.hospitalRequired + ), + ) + ); + return; + } + if(_user.department == null){ + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + _subtitle.unitRequired + ), + ) + ); + return; + } + int status = await _userProvider.register( + user: _user, + host: _settingProvider.host, + ); + if(status >= 200 && status < 300){ + Fluttertoast.showToast(msg: _subtitle.activationAlert); + Navigator.of(context).pop(); + }else{ + String errorMessage = status == 402 + ? _subtitle.nameExist + : status == 401 + ? _subtitle.emailExist + : HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + }, + ), + ), + ), + SizedBox(height: 32,), + ], + ), + ), + ABackButton(), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/splash_screen.dart b/lib/views/pages/splash_screen.dart new file mode 100644 index 0000000..3291134 --- /dev/null +++ b/lib/views/pages/splash_screen.dart @@ -0,0 +1,87 @@ +import 'dart:convert'; + +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flare_flutter/flare_actor.dart'; +import 'package:test_sa/controllers/notification/notification_manger.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/app_notification.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/views/pages/user/land_page.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'login.dart'; + +class SplashScreen extends StatefulWidget { + static const String id = '/splash'; + const SplashScreen({Key key}) : super(key: key); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + + SettingProvider _settingProvider; + UserProvider _userProvider; + + _goToUserScreen(User user){ + _userProvider.user = user; + // Navigator.of(context).pushNamed(Login.id); + Navigator.of(context).pushNamed(LandPage.id); + } + + @override + void initState() { + Firebase.initializeApp(); + + NotificationManger.initialisation( + (notificationDetails) { + AppNotification notification = AppNotification.fromJson(json.decode(notificationDetails.payload)); + if(notification.path == null || notification.path.isEmpty) + return; + Navigator.pushNamed( + context, + notification.path, + arguments: notification.requestId + ); + } + , (id, title, body, payload) async { + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + _settingProvider = Provider.of(context,listen: false); + _userProvider = Provider.of(context,listen: false); + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width/1.1, + child: FlareActor( + "assets/rives/atoms_splash.flr", + fit: BoxFit.contain, + animation: "splash", + callback: (animation) async { + print(await FirebaseMessaging.instance.getToken()); + Navigator.of(context).pushNamed(Login.id); + if(_settingProvider.isLoaded && _settingProvider.user != null){ + print(_settingProvider.user.toJson()); + _goToUserScreen(_settingProvider.user); + } + }, + ), + //const Center(child: CircularProgressIndicator()) + + // Image.asset("assets/images/logo.png", + // fit: BoxFit.contain, + // ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/gas_refill/gas_refill_details.dart b/lib/views/pages/user/gas_refill/gas_refill_details.dart new file mode 100644 index 0000000..33dd686 --- /dev/null +++ b/lib/views/pages/user/gas_refill/gas_refill_details.dart @@ -0,0 +1,222 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/gas_refill_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/enums/user_types.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_model.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/gas_refill/gas_refill_create_details_item.dart'; +import 'package:test_sa/views/widgets/gas_refill/gas_refill_update_details_item.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/requests/info_row.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; +import 'package:test_sa/views/widgets/status/gas_refill/gas_status.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; + +import '../../../app_style/colors.dart'; +class GasRefillDetails extends StatefulWidget { + final GasRefillModel model; + const GasRefillDetails({Key key, this.model}) : super(key: key); + + @override + State createState() => _GasRefillDetailsState(); +} + +class _GasRefillDetailsState extends State { + + final GasRefillModel _model = GasRefillModel(); + bool _enableEdit = false; + bool _validate = false; + UserProvider _userProvider; + SettingProvider _settingProvider; + GasRefillProvider _gasRefillProvider; + bool _isLoading = false; + Subtitle _subtitle; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + _update() async { + _validate = true; + if(!_formKey.currentState.validate()){ + setState(() {}); + return false; + } + _formKey.currentState.save(); + + _isLoading =true; + setState(() {}); + int status = await _gasRefillProvider.updateModel( + user: _userProvider.user, + host: _settingProvider.host, + newModel: _model, + oldModel: widget.model + ); + _isLoading =false; + setState(() {}); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.requestCompleteSuccessfully, + ); + _enableEdit = false; + _validate = false; + //Navigator.of(context).pop(); + }else{ + String errorMessage = HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + } + + @override + void initState() { + _model.fromGasRefillModel(widget.model); + super.initState(); + } + + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _gasRefillProvider = Provider.of(context); + return Scaffold( + key: _scaffoldKey, + body: SafeArea( + child: Form( + key: _formKey, + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Column( + children: [ + Container( + color: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Row( + children: [ + const ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.details, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + if(_userProvider.user.type == UsersTypes.engineer) + AIconButton( + iconData: _enableEdit ? Icons.cancel : Icons.edit, + color: Theme.of(context).colorScheme.onPrimary, + buttonSize: 42, + backgroundColor: AColors.green, + onPressed: () async { + _enableEdit = !_enableEdit; + _model.fromGasRefillModel(widget.model); + setState(() {}); + }, + ), + const SizedBox(width: 16,) + ], + ), + ), + Expanded( + child: SingleChildScrollView( + padding: EdgeInsets.all(16*AppStyle.getScaleFactor(context)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RequestInfoRow( + title: _subtitle.title, + info: _model.title, + ), + RequestInfoRow( + title: _subtitle.hospital, + info: _model.clientName, + ), + _enableEdit ? + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 8,), + ASubTitle(_subtitle.status), + if(_validate && _model.status == null) + ASubTitle(_subtitle.requiredWord,color: Colors.red,), + const SizedBox(height: 4,), + GasStatusMenu( + initialValue: _model.status, + onSelect: (status){ + _model.status = status; + }, + ) + ], + ): + Row( + children: [ + Expanded( + child: Text( + "${_subtitle.status} : ", + style: Theme.of(context).textTheme.subtitle2, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + StatusLabel(label: _model.status.label, + color: AColors.getGasStatusColor(_model.status.id) + ), + ], + ), + const SizedBox(height: 8,), + const ASubTitle("Gas Requests"), + const SizedBox(height: 8,), + ListView.builder( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemCount: _model.details.length, + itemBuilder: (context,index){ + final details = _model.details[index]; + return GasRefillUpdateDetailsItem( + details: details, + validate: _validate, + enableEdit: _enableEdit, + ); + } + ), + if(_enableEdit) + Column( + children: [ + const SizedBox(height: 16,), + AButton( + text: _subtitle.update, + onPressed: _update, + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/gas_refill/request_gas_refill.dart b/lib/views/pages/user/gas_refill/request_gas_refill.dart new file mode 100644 index 0000000..3b0cb34 --- /dev/null +++ b/lib/views/pages/user/gas_refill/request_gas_refill.dart @@ -0,0 +1,255 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/providers/api/gas_refill_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_details.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_model.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/gas_refill/gas_refill_create_details_item.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/gas_refill/gas_cylinder_size.dart'; +import 'package:test_sa/views/widgets/status/gas_refill/gas_status.dart'; +import 'package:test_sa/views/widgets/status/gas_refill/gas_type.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; + +import '../../../../controllers/localization/localization.dart'; +class RequestGasRefill extends StatefulWidget { + static const String id = "/request-gas-refill"; + const RequestGasRefill({Key key}) : super(key: key); + + @override + State createState() => _RequestGasRefillState(); +} + +class _RequestGasRefillState extends State { + bool _isLoading = false; + bool _validate = false; + Subtitle _subtitle; + UserProvider _userProvider; + SettingProvider _settingProvider; + GasRefillProvider _gasRefillProvider; + GasRefillDetails _currentDetails = GasRefillDetails(); + final TextEditingController _requestedQuantityController = TextEditingController(); + final GasRefillModel _formModel = GasRefillModel(details: []); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _DetailsKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + void setState(VoidCallback fn){ + if(mounted) super.setState(() {}); + } + + _onSubmit() async { + if(_formModel.details.isEmpty){ + if(!_addNewModel()) return; + } + + _isLoading =true; + setState(() {}); + + int status = await _gasRefillProvider.createModel( + user: _userProvider.user, + host: _settingProvider.host, + model: _formModel, + ); + _isLoading =false; + setState(() {}); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.requestCompleteSuccessfully, + ); + Navigator.of(context).pop(); + }else{ + String errorMessage = HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + } + + bool _addNewModel(){ + _validate = true; + if(!_formKey.currentState.validate()){ + setState(() {}); + return false; + } + _formKey.currentState.save(); + if(!_currentDetails.validate()) { + setState(() { }); + return false; + } + + + _formModel.details.insert(0,_currentDetails); + _validate = false; + Scrollable.ensureVisible(_DetailsKey.currentContext); + _requestedQuantityController.clear(); + _currentDetails = GasRefillDetails(); + setState(() {}); + return true; + } + + @override + void dispose() { + _requestedQuantityController.dispose(); + super.dispose(); + } + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _gasRefillProvider = Provider.of(context,listen: false); + return Scaffold( + key: _scaffoldKey, + body: Form( + key: _formKey, + child: SafeArea( + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: SingleChildScrollView( + padding: EdgeInsets.all(12 * AppStyle.getScaleFactor(context)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Request Gas Refill", + style: Theme.of(context).textTheme.headline5.copyWith( + color: Theme.of(context).primaryColor, + fontSize: 28, + fontWeight: FontWeight.bold + ), + ), + ), + ), + // const SizedBox(height: 4,), + // ASubTitle(_subtitle.title), + // if(_validate && _formModel.title == null) + // ASubTitle(_subtitle.requiredWord,color: Colors.red,), + // SizedBox(height: 4,), + // ATextFormField( + // initialValue: _formModel?.title, + // textAlign: TextAlign.center, + // style: Theme.of(context).textTheme.subtitle1, + // textInputType: TextInputType.text, + // onSaved: (value){ + // _formModel.title = value; + // }, + // ), + // // const SizedBox(height: 8,), + // ASubTitle(_subtitle.status), + // if(_validate && _formModel.status == null) + // ASubTitle(_subtitle.requiredWord,color: Colors.red,), + // const SizedBox(height: 4,), + // GasStatusMenu( + // initialValue: _formModel.status, + // onSelect: (status){ + // _formModel.status = status; + // }, + // ), + // const SizedBox(height: 8,), + // Divider(color: Theme.of(context).colorScheme.primary,), + const SizedBox(height: 4,), + const ASubTitle("Type"), + if(_validate && _currentDetails.type == null) + ASubTitle(_subtitle.requiredWord,color: Colors.red,), + const SizedBox(height: 4,), + GasTypeMenu( + initialValue: _currentDetails.type, + onSelect: (status){ + _currentDetails.type = status; + }, + ), + + // const SizedBox(height: 8,), + // const ASubTitle("Cylinder Size"), + // if(_validate && _currentDetails.cylinderSize == null) + // ASubTitle(_subtitle.requiredWord,color: Colors.red,), + // const SizedBox(height: 4,), + // GasCylinderSizeMenu( + // initialValue: _currentDetails.cylinderSize, + // onSelect: (status){ + // _currentDetails.cylinderSize = status; + // }, + // ), + const SizedBox(height: 8,), + ASubTitle(_subtitle.quantity), + if(_validate && _currentDetails.requestedQuantity == null) + ASubTitle(_subtitle.requiredWord,color: Colors.red,), + SizedBox(height: 4,), + ATextFormField( + initialValue: (_currentDetails?.requestedQuantity ?? "").toString(), + textAlign: TextAlign.center, + controller: _requestedQuantityController, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.isNumeric(value) + ? null : "allow numbers only", + textInputType: TextInputType.number, + onSaved: (value){ + _currentDetails.requestedQuantity = int.tryParse(value); + }, + ), + const SizedBox(height: 8,), + Padding( + padding: const EdgeInsets.all(16.0), + child: AButton( + text: _subtitle.add, + onPressed: _addNewModel, + ), + ), + if(_formModel.details.isNotEmpty) + const ASubTitle("Gas Requests"), + ListView.builder( + key: _DetailsKey, + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemCount: _formModel.details.length, + itemBuilder: (context,index){ + final model = _formModel.details[index]; + return GasRefillCreateDetailsItem( + model: model, + onDelete: (){ + _formModel.details.remove(model); + setState(() {}); + }, + ); + } + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: AButton( + text: _subtitle.submit, + onPressed: _onSubmit, + ), + ), + const SizedBox(height: 100,) + ], + ), + ), + ), + ), + ), + ); + } +} + diff --git a/lib/views/pages/user/gas_refill/track_gas_refill.dart b/lib/views/pages/user/gas_refill/track_gas_refill.dart new file mode 100644 index 0000000..2b502f6 --- /dev/null +++ b/lib/views/pages/user/gas_refill/track_gas_refill.dart @@ -0,0 +1,102 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/gas_refill_provider.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/service_request/service_request_search.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/widgets/gas_refill/gas_refill_list.dart'; +import 'package:test_sa/views/widgets/requests/service_request_list.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/search/service_request_search_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class TrackGasRefillPage extends StatefulWidget { + static const String id = "/track-gas-refill"; + + const TrackGasRefillPage({Key key}) : super(key: key); + + @override + State createState() => _TrackGasRefillPageState(); +} + +class _TrackGasRefillPageState extends State + with TickerProviderStateMixin{ + GasRefillProvider _gasRefillProvider; + UserProvider _userProvider; + SettingProvider _settingProvider; + @override + Widget build(BuildContext context) { + _gasRefillProvider = Provider.of(context); + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + Subtitle _subtitle = AppLocalization.of(context).subtitle; + + return Scaffold( + body: SafeArea( + child: LoadingManager( + isLoading: _gasRefillProvider.isLoading, + isFailedLoading: _gasRefillProvider.items == null, + stateCode: _gasRefillProvider.stateCode, + onRefresh: () async { + _gasRefillProvider.reset(); + await _gasRefillProvider.getRequests( + user: _userProvider.user, + host: _settingProvider.host, + ); + }, + child: Stack( + children: [ + Column( + children: [ + Container( + color:AColors.primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Column( + children: [ + Row( + children: [ + ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.serviceRequests, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + const SizedBox(width: 48,) + ], + ), + + ], + ), + ), + Expanded( + child: GasRefillList( + nextPage: _gasRefillProvider.nextPage, + onLazyLoad: () async { + await _gasRefillProvider.getRequests( + user: _userProvider.user, + host: _settingProvider.host, + ); + }, + items: _gasRefillProvider.items, + ), + ), + ], + ), + + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/land_page.dart b/lib/views/pages/user/land_page.dart new file mode 100644 index 0000000..d29f360 --- /dev/null +++ b/lib/views/pages/user/land_page.dart @@ -0,0 +1,466 @@ +import 'dart:io'; + +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/notification/firebase_notification_manger.dart'; +import 'package:test_sa/controllers/providers/api/departments_provider.dart'; +import 'package:test_sa/controllers/providers/api/devices_provider.dart'; +import 'package:test_sa/controllers/providers/api/preventive_maintenance_visits_provider.dart'; +import 'package:test_sa/controllers/providers/api/regular_visits_provider.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/enums/user_types.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/pages/device_transfer/request_device_transfer.dart'; +import 'package:test_sa/views/pages/device_transfer/track_device_transfer.dart'; +import 'package:test_sa/views/pages/user/gas_refill/request_gas_refill.dart'; +import 'package:test_sa/views/pages/user/gas_refill/track_gas_refill.dart'; +import 'package:test_sa/views/pages/user/notifications/notifications_page.dart'; +import 'package:test_sa/views/pages/user/requests/create_request.dart'; +import 'package:test_sa/views/pages/user/visits/preventive_maintenance_visits_page.dart'; +import 'package:test_sa/views/pages/user/visits/regular_visits_page.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/dialogs/dialog.dart'; +import 'package:test_sa/views/widgets/drawer/drawer_item.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:package_info/package_info.dart'; +import 'package:provider/provider.dart'; +import 'package:share/share.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../widgets/land_page/land_page_item.dart'; +import 'profile_page.dart'; +import 'requests/requests_page.dart'; +class LandPage extends StatefulWidget { + static const String id = "/land-page"; + + const LandPage({Key key}) : super(key: key); + + @override + State createState() => _LandPageState(); +} + +class _LandPageState extends State { + double _height; + double _width; + UserProvider _userProvider; + SettingProvider _settingProvider; + DepartmentsProvider _departmentsProvider; + DevicesProvider _devicesProvider; + double _buttonHeight; + bool firstTime = true; + ServiceRequestsProvider _serviceRequestsProvider; + PreventiveMaintenanceVisitsProvider _preventiveMaintenanceVisitsProvider; + RegularVisitsProvider _regularVisitsProvider; + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + try{ + FirebaseNotificationManger.initialized(context); + }catch(error){ + } + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + String path = ModalRoute.of(context).settings.arguments; + _height = MediaQuery.of(context).size.height; + _width = MediaQuery.of(context).size.width; + _settingProvider = Provider.of(context); + _userProvider = Provider.of(context); + _departmentsProvider = Provider.of(context); + _devicesProvider = Provider.of(context); + _serviceRequestsProvider = Provider.of(context); + _preventiveMaintenanceVisitsProvider = Provider.of(context); + _regularVisitsProvider = Provider.of(context); + Subtitle _subtitle = AppLocalization.of(context).subtitle; + if(firstTime){ + if(path != null){ + Navigator.of(context).pushNamed( + "/"+path.split("/").first, + arguments: path.split("/").last + ); + } + firstTime = false; + } + _buttonHeight = 68 * AppStyle.getScaleFactor(context); + return WillPopScope( + onWillPop: () async { + print("back pressed"); + bool result = await showDialog( + context: context, + builder: (_) => AAlertDialog( + title: _subtitle.exit, + content: _subtitle.exitAlert, + ) + ); + if(result == true){ + if(Platform.isAndroid){ + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } else { + exit(0); + } + + } + return false; + }, + child: Scaffold( + key: _scaffoldKey, + body: SafeArea( + child: Stack( + children: [ + ListView( + children: [ + //AppNameBar(), + // SizedBox( + // height: _height/3.2, + // width: _width, + // child: CarouselSlider.builder( + // options: CarouselOptions( + // height: _height/3, + // autoPlay: true, + // viewportFraction: 1 + // ), + // itemCount: 4, + // itemBuilder: (BuildContext context, int itemIndex, int pageViewIndex) => + // Image( + // //width: _width, + // image: AssetImage("assets/images/$itemIndex.png"), + // fit: BoxFit.cover, + // ) + // ), + // ), + SizedBox(height: 48 * AppStyle.getScaleFactor(context),), + Hero( + tag: "logo", + child: Image( + height: _height/6, + image: const AssetImage("assets/images/logo.png"), + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: GridView.count( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + crossAxisCount: 2, + crossAxisSpacing: 5, + mainAxisSpacing: 5, + childAspectRatio: 1.15, + children: [ + if (_userProvider.user.type == UsersTypes.normal_user) + LandPageItem( + text: _subtitle.newServiceRequest, + icon: FontAwesomeIcons.tools, + onPressed: (){ + Navigator.of(context).pushNamed(CreateRequestPage.id); + }, + ), + LandPageItem( + text: _subtitle.trackServiceRequest, + icon: FontAwesomeIcons.tasks, + onPressed: (){ + Navigator.of(context).pushNamed(ServiceRequestsPage.id); + }, + ), + //if (_userProvider.user.type == UsersTypes.engineer) + LandPageItem( + text: _subtitle.preventiveMaintenance, + icon: FontAwesomeIcons.personWalking, + onPressed: (){ + Navigator.of(context).pushNamed(RegularVisitsPage.id); + }, + ), + //if (_userProvider.user.type == UsersTypes.engineer) + // LandPageItem( + // text: _subtitle.preventiveMaintenance, + // icon: FontAwesomeIcons.toolbox, + // onPressed: (){ + // Navigator.of(context).pushNamed(PreventiveMaintenanceVisitsPage.id); + // }, + // ), + if (_userProvider.user.type != UsersTypes.engineer) + LandPageItem( + text: "Request Gas Refill", + icon: FontAwesomeIcons.truckFast, + onPressed: (){ + Navigator.of(context).pushNamed(RequestGasRefill.id); + }, + ), + LandPageItem( + text: "Track Gas Refill", + icon: Icons.content_paste_search, + onPressed: (){ + Navigator.of(context).pushNamed(TrackGasRefillPage.id); + }, + ), + LandPageItem( + text: "transfer Device", + icon: FontAwesomeIcons.rightLeft, + onPressed: (){ + Navigator.of(context).pushNamed(RequestDeviceTransfer.id); + }, + ), + LandPageItem( + text: "Track Device Transfer", + icon: FontAwesomeIcons.peopleCarryBox, + onPressed: (){ + Navigator.of(context).pushNamed(TrackDeviceTransferPage.id); + }, + ), + ], + ), + ), + SizedBox(height: 100,) + ], + ), + Align( + alignment: Alignment.topLeft, + child: ABackButton( + onPressed: () async { + bool result = await showDialog( + context: context, + builder: (_) => AAlertDialog( + title: _subtitle.signOut, + content: _subtitle.signOutAlert, + ) + ); + if(result){ + _devicesProvider.reset(); + _departmentsProvider.reset(); + _serviceRequestsProvider.reset(); + _regularVisitsProvider.reset(); + _preventiveMaintenanceVisitsProvider.reset(); + _settingProvider.resetSettings(); + Navigator.of(context).pop(); + } + }, + ), + ), + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4 + ), + child: AIconButton( + iconData: Icons.menu, + color: AColors.primaryColor, + buttonSize: 42, + backgroundColor: AColors.white, + onPressed: (){ + _scaffoldKey.currentState.openEndDrawer(); + }, + ), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + padding: EdgeInsets.all(16), + color: AColors.primaryColor, + child: Row( + children: [ + AIconButton( + iconData: Icons.mail, + onPressed: (){ + launch("mailto:customerservice@Test SA.com"); + + }, + ), + Expanded( + child: MaterialButton( + splashColor: AColors.secondaryColor.withOpacity(.5), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 8 * AppStyle.getScaleFactor(context) + ), + color: AColors.onPrimaryColor + ), + child: Icon( + Icons.phone_in_talk, + color: AColors.primaryColor , + size: 32, + ), + ), + SizedBox( + width: 12 * AppStyle.getScaleFactor(context), + ), + Text( + "${_subtitle.hotLine}\n15564", + + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + letterSpacing: 2.75, + fontWeight: FontWeight.bold + ), + ), + ], + ), + onPressed: (){ + launch("tel:15564"); + }, + ), + ), + AIconButton( + iconData: Icons.notifications, + onPressed: (){ + Navigator.of(context).pushNamed(NotificationsPage.id); + }, + ), + ], + ), + ), + ), + ], + ), + ), + endDrawer: Drawer( + child: Container( + color: AColors.primaryColor, + child: Column( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width, + child: DrawerHeader( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onPrimary, + ), + padding: EdgeInsets.zero, + child: MaterialButton( + padding: EdgeInsets.zero, + onPressed: (){ + Navigator.of(context).pop(); + Navigator.of(context).pushNamed(ProfilePage.id); + + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 100 * AppStyle.getScaleFactor(context), + width: 100 * AppStyle.getScaleFactor(context), + padding: EdgeInsets.all(4), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).primaryColor, + width: 2 + ), + shape: BoxShape.circle + ), + child: ClipOval( + child: ImageLoader( + url: "https://cdn.business2community.com/wp-content/uploads/2017/08/blank-profile-picture-973460_640.png", + ), + ), + ), + Text( + _userProvider.user.userName, + style: Theme.of(context).textTheme.headline6.copyWith( + fontWeight: FontWeight.normal, + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ], + ), + ), + ), + ), + Row( + children: [ + Radio( + value: "en", + activeColor: Colors.white, + focusColor: Colors.white, + groupValue: _settingProvider.language, + onChanged: (value){ + _settingProvider.setLanguage(value); + } + ), + Text( + "English", + style: Theme.of(context).textTheme.bodyText1.copyWith( + color: Colors.white + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + Radio( + value: "ar", + activeColor: Colors.white, + focusColor: Colors.white, + groupValue: _settingProvider.language, + onChanged: (value){ + _settingProvider.setLanguage(value); + } + ), + Text( + "عربي", + style: Theme.of(context).textTheme.bodyText1.copyWith( + color: Colors.white + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ], + ), + DrawerItem( + icon: FontAwesomeIcons.linkedinIn, + title: _subtitle.linkedIn, + onPressed: (){ + launch("https://www.linkedin.com/company/Test SA/"); + }, + ), + DrawerItem( + icon: FontAwesomeIcons.globe, + title: _subtitle.ourWebsite, + onPressed: (){ + launch("https://www.Test SA.com/"); + }, + ), + DrawerItem( + icon: Icons.share, + title: _subtitle.shareApp, + onPressed: () async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + String shareLink = + "\n https://play.google.com/store/apps/details?id=" + packageInfo.packageName + + "\n https://apps.apple.com/us/app/"; + Share.share(shareLink); + }, + ), + Expanded(child: Center(child: Image.asset("assets/images/qr.jpeg"))), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Powered By NewTrack", + style: Theme.of(context).textTheme.bodyText2.copyWith( + fontWeight: FontWeight.normal, + color: AColors.white + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/notifications/notifications_list.dart b/lib/views/pages/user/notifications/notifications_list.dart new file mode 100644 index 0000000..2df3171 --- /dev/null +++ b/lib/views/pages/user/notifications/notifications_list.dart @@ -0,0 +1,43 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/app_notification.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/pages/user/requests/future_request_service_details.dart'; +import 'package:test_sa/views/widgets/loaders/lazy_loading.dart'; +import 'package:test_sa/views/widgets/loaders/no_item_found.dart'; +import 'package:test_sa/views/widgets/notifications/notification_item.dart'; +import 'package:flutter/material.dart'; +class NotificationsList extends StatelessWidget { + final List notifications; + final bool nextPage; + final Future Function() onLazyLoad; + + const NotificationsList({Key key, this.notifications, this.nextPage, this.onLazyLoad}) : super(key: key); + + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + if(notifications.length == 0){ + return NoItemFound(message: _subtitle.notificationsNotFound,); + } + return LazyLoading( + nextPage: nextPage, + onLazyLoad: onLazyLoad, + child: ListView.builder( + physics: BouncingScrollPhysics(), + itemCount: notifications.length, + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 8), + itemBuilder: (context,itemIndex){ + return NotificationItem( + notification: notifications[itemIndex], + onPressed: (notification){ + Navigator.of(context).pushNamed( + FutureRequestServiceDetails.id, + arguments: notification.requestId + ); + }, + ); + } + ), + ); + } +} diff --git a/lib/views/pages/user/notifications/notifications_page.dart b/lib/views/pages/user/notifications/notifications_page.dart new file mode 100644 index 0000000..900fb46 --- /dev/null +++ b/lib/views/pages/user/notifications/notifications_page.dart @@ -0,0 +1,96 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/notifications_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/pages/user/notifications/notifications_list.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class NotificationsPage extends StatefulWidget { + static final String id = "/notifications"; + @override + _NotificationsPageState createState() => _NotificationsPageState(); +} + +class _NotificationsPageState extends State + with TickerProviderStateMixin{ + NotificationsProvider _notificationsProvider; + UserProvider _userProvider; + SettingProvider _settingProvider; + Subtitle _subtitle; + @override + Widget build(BuildContext context) { + _notificationsProvider = Provider.of(context); + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + body: SafeArea( + child: LoadingManager( + isLoading: _notificationsProvider.isLoading, + isFailedLoading: _notificationsProvider.notifications == null, + stateCode: _notificationsProvider.stateCode, + onRefresh: () async { + _notificationsProvider.reset(); + await _notificationsProvider.getNotifications( + user: _userProvider.user, + host: _settingProvider.host, + hospitalId: _userProvider.user.hospital.id, + ); + }, + child: Stack( + children: [ + Column( + children: [ + Container( + color:AColors.primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Column( + children: [ + Row( + children: [ + ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.notifications, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + SizedBox(width: 48,) + ], + ), + + ], + ), + ), + Expanded( + child: NotificationsList( + nextPage: _notificationsProvider.nextPage, + onLazyLoad: () async { + await _notificationsProvider.getNotifications( + user: _userProvider.user, + host: _settingProvider.host, + hospitalId: _userProvider.user.hospital.id, + ); + }, + notifications: _notificationsProvider.notifications, + ), + ), + ], + ), + + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/profile_page.dart b/lib/views/pages/user/profile_page.dart new file mode 100644 index 0000000..514655e --- /dev/null +++ b/lib/views/pages/user/profile_page.dart @@ -0,0 +1,216 @@ +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/departments/department_button.dart'; +import 'package:test_sa/views/widgets/hospitals/hospital_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; +class ProfilePage extends StatefulWidget { + static final String id = "/user/profile"; + @override + _ProfilePageState createState() => _ProfilePageState(); +} + +class _ProfilePageState extends State { + UserProvider _userProvider; + SettingProvider _settingProvider; + double _width; + double _height; + User _user = User(); + bool _firstTime = true; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + @override + Widget build(BuildContext context) { + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _width = MediaQuery.of(context).size.width; + _height = MediaQuery.of(context).size.height; + Subtitle _subtitle = AppLocalization.of(context).subtitle; + if(_firstTime){ + _user = User.fromJson(_userProvider.user.toJson()); + _firstTime = false; + } + return Scaffold( + key: _scaffoldKey, + body:LoadingManager( + isLoading: _userProvider.isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: SafeArea( + child: Stack( + children: [ + Form( + key: _formKey, + child: ListView( + children: [ + //AppNameBar(), + Hero( + tag: "logo", + child: Image( + height: _height/4, + image: AssetImage("assets/images/logo.png"), + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 16), + margin: EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: AColors.primaryColor, + borderRadius: BorderRadius.circular(AppStyle.getBorderRadius(context)), + boxShadow: [ + BoxShadow( + color: AColors.grey, + offset: Offset(0,-1), + ) + ] + ), + child: Column( + children: [ + ATextFormField( + initialValue: _user.userName, + hintText: _subtitle.name, + enable: false, + prefixIconData: Icons.account_circle, + style: Theme.of(context).textTheme.headline6, + validator: (value) => Validator.hasValue(value) + ? null : _subtitle.nameValidateMessage, + onSaved: (value){ + _user.userName = value; + }, + ), + SizedBox(height: 8,), + ATextFormField( + initialValue: _user.email, + hintText: _subtitle.email, + enable: false, + prefixIconData: Icons.email, + textInputType: TextInputType.emailAddress, + style: Theme.of(context).textTheme.headline6, + validator: (value) => Validator.isEmail(value) + ? null : _subtitle.emailValidateMessage, + onSaved: (value){ + _user.email = value; + }, + ), + SizedBox(height: 8,), + AbsorbPointer( + child: HospitalButton( + hospital: _user.hospital, + onHospitalPick: (hospital){ + _user.hospital = hospital; + setState(() {}); + }, + ), + ), + SizedBox(height: 8,), + DepartmentButton( + department: _user.department, + onDepartmentPick: (department){ + _user.department = department; + setState(() {}); + }, + ), + SizedBox(height: 8,), + ATextFormField( + initialValue: _user.phoneNumber, + hintText: _subtitle.phoneNumber, + style: Theme.of(context).textTheme.headline6, + prefixIconData: Icons.phone_android, + validator: (value) => + Validator.isPhoneNumber(value) ? null : _subtitle.phoneNumberValidateMessage, + textInputType: TextInputType.phone, + onSaved: (value){ + _user.phoneNumber = value; + }, + ), + SizedBox(height: 8,), + ATextFormField( + initialValue: _user.whatsApp, + hintText: _subtitle.whatsApp, + style: Theme.of(context).textTheme.headline6, + prefixIconData: FontAwesomeIcons.whatsapp, + prefixIconSize: 36, + validator: (value) => + Validator.isPhoneNumber(value) ? null : _subtitle.phoneNumberValidateMessage, + textInputType: TextInputType.phone, + onSaved: (value){ + _user.whatsApp = value; + }, + ), + ], + ), + ), + SizedBox(height: 16,), + Center( + child: SizedBox( + height: _width / 8, + width: _width/1.2, + child: AButton( + text: _subtitle.update, + onPressed: () async { + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + if(_user.department?.id == null){ + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + _subtitle.unitRequired + ), + ) + ); + return; + } + int status = await _userProvider.updateProfile( + user: _user, + host: _settingProvider.host, + ); + if(status >= 200 && status < 300){ + _settingProvider.setUser(_userProvider.user); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + _subtitle.requestCompleteSuccessfully + ), + ) + ); + }else{ + String errorMessage = HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + }, + ), + ), + ), + SizedBox(height: 32,), + ], + ), + ), + ABackButton(), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/report_issues_page.dart b/lib/views/pages/user/report_issues_page.dart new file mode 100644 index 0000000..d9a91be --- /dev/null +++ b/lib/views/pages/user/report_issues_page.dart @@ -0,0 +1,190 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/issue.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; + +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/issues/report_issue_item.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +class ReportIssuesPage extends StatefulWidget { + static final String id = "/report-issue"; + final ServiceRequest serviceRequest; + + const ReportIssuesPage({Key key, this.serviceRequest}) : super(key: key); + @override + _ReportIssuesPageState createState() => _ReportIssuesPageState(); +} + +class _ReportIssuesPageState extends State { + List _issues = []; + Issue _issue = Issue(reports: []); + double _height; + bool _isLoading = false; + ServiceRequestsProvider _serviceRequestsProvider; + UserProvider _userProvider; + SettingProvider _settingProvider; + final GlobalKey _formKey = GlobalKey(); + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + _serviceRequestsProvider = Provider.of(context); + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _height = MediaQuery.of(context).size.height; + _subtitle.setIssues(_issues); + return Scaffold( + body: SafeArea( + child: Form( + key: _formKey, + child: LoadingManager( + onRefresh: () async {}, + stateCode: 200, + isFailedLoading: false, + isLoading: _isLoading, + child: Stack( + children: [ + SingleChildScrollView( + child: Column( + children: [ + Center( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 16 * AppStyle.getScaleFactor(context), + vertical: 24 * AppStyle.getScaleFactor(context), + ), + child: Text( + _subtitle.reportIssue, + style: Theme.of(context).textTheme.headline5.copyWith( + color: AColors.cyan, + fontWeight: FontWeight.bold + ), + ), + ), + ), + Image( + height: _height/8, + image: AssetImage("assets/images/logo.png"), + ), + + Container( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + decoration: BoxDecoration( + color: AColors.grey, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(AppStyle.getBorderRadius(context)), + topRight: Radius.circular(AppStyle.getBorderRadius(context)), + ), + boxShadow: [ + BoxShadow( + color: AColors.grey, + offset: Offset(0,-1), + ) + ] + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ATextFormField( + initialValue: _issue?.title, + hintText: _subtitle.title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headline6, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.titleValidateMessage, + textInputType: TextInputType.name, + onSaved: (value){ + _issue.title = value; + }, + ), + SizedBox(height: 8,), + Column( + children: List.generate( + _issues.length, + (index) => ReportIssueItem( + isSelected: _issue.reports.contains(index), + issueInfo: _issues[index], + onChange: (info,value){ + if(value){ + _issue.reports.add(index); + } else { + _issue.reports.remove(index); + } + setState(() {}); + }, + ) + ) + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "${_subtitle.shareAntherIssue} :", + style: Theme.of(context).textTheme.headline6, + ), + ), + ATextFormField( + hintText: _subtitle.description, + style: Theme.of(context).textTheme.subtitle1, + textInputType: TextInputType.multiline, + onSaved: (value){ + _issue.description = value; + }, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: AButton( + text: _subtitle.submit, + onPressed: () async { + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + _issue.serviceRequestId = widget.serviceRequest.id; + _isLoading =true; + setState(() {}); + int status = await _serviceRequestsProvider.createIssueReport( + user: _userProvider.user, + host: _settingProvider.host, + issue: _issue, + ); + _isLoading =false; + setState(() {}); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.requestCompleteSuccessfully, + ); + Navigator.of(context).pop(); + } + _isLoading = false; + setState(() {}); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ABackButton(), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/requests/create_request.dart b/lib/views/pages/user/requests/create_request.dart new file mode 100644 index 0000000..104ce32 --- /dev/null +++ b/lib/views/pages/user/requests/create_request.dart @@ -0,0 +1,232 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/equipment/device_button.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/sound/record_sound.dart'; +import 'package:test_sa/views/widgets/speech_to_text/speech_to_text.dart'; +class CreateRequestPage extends StatefulWidget { + static final String id = "/create-request"; + @override + _CreateRequestPageState createState() => _CreateRequestPageState(); +} + +class _CreateRequestPageState extends State { + + double _height; + UserProvider _userProvider; + SettingProvider _settingProvider; + ServiceRequestsProvider _serviceRequestsProvider; + ServiceRequest _serviceRequest = ServiceRequest(); + List _deviceImages = []; + bool _isLoading = false; + Device _device; + Subtitle _subtitle; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + TextEditingController _controller; + + @override + void initState() { + _controller = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + @override + Widget build(BuildContext context) { + _height = MediaQuery.of(context).size.height; + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _serviceRequestsProvider = Provider.of(context); + _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + key: _scaffoldKey, + body: SafeArea( + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Form( + key: _formKey, + child: Stack( + children: [ + ListView( + children: [ + //AppNameBar(), + SizedBox(height: 16,), + Hero( + tag: "logo", + child: Image( + height: _height/6, + image: AssetImage("assets/images/logo.png"), + ), + ), + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _subtitle.newServiceRequest, + style: Theme.of(context).textTheme.headline5.copyWith( + color: AColors.cyan, + fontWeight: FontWeight.bold + ), + ), + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 16), + margin: EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: AColors.grey, + borderRadius: BorderRadius.circular(AppStyle.getBorderRadius(context)), + boxShadow: const [ + BoxShadow( + color: AColors.grey, + offset: Offset(0,-1), + ) + ] + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 8,), + _userProvider.user.hospital == null ? SizedBox.shrink() : + ATextFormField( + enable: false, + initialValue: _userProvider.user.hospital?.name ?? _subtitle.noHospitalFound, + hintText: _subtitle.hospital, + prefixIconData: FontAwesomeIcons.hospitalAlt, + prefixIconSize: 36, + style: Theme.of(context).textTheme.headline6, + ), + const SizedBox(height: 8,), + _userProvider.user.department == null ? SizedBox.shrink() : + ATextFormField( + enable: false, + initialValue: _userProvider.user.department?.name ?? _subtitle.noUniteFound, + hintText: _subtitle.unite, + prefixIconData: FontAwesomeIcons.hospitalUser, + prefixIconSize: 36, + style: Theme.of(context).textTheme.headline6, + ), + const SizedBox(height: 8,), + DeviceButton( + device: _device, + onDevicePick: (device){ + _device = device; + setState(() {}); + }, + ), + MultiImagesPicker( + label: _subtitle.deviceImages, + images: _deviceImages, + ), + const SizedBox(height: 8,), + SpeechToTextButton( + controller: _controller, + ), + const SizedBox(height: 8,), + ATextFormField( + controller: _controller, + initialValue: _serviceRequest.maintenanceIssue, + hintText: _subtitle.maintenanceIssue, + prefixIconData: FontAwesomeIcons.exclamationTriangle, + prefixIconSize: 36, + style: Theme.of(context).textTheme.headline6, + textInputType: TextInputType.multiline, + validator: (value) => Validator.hasValue(value) + ? null : _subtitle.maintenanceIssueRequired, + onSaved: (value){ + _serviceRequest.maintenanceIssue = value; + }, + ), + const SizedBox(height: 8,), + RecordSound( + onRecord: (audio){ + _serviceRequest.audio = audio; + } + ), + const SizedBox(height: 8,), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: AButton( + text: _subtitle.submit, + onPressed: () async { + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + _serviceRequest.deviceId = _device?.id ?? ""; + _isLoading =true; + setState(() {}); + _serviceRequest.devicePhotos = _deviceImages.map( + (e) => base64Encode(e.readAsBytesSync())).toList(); + if(_serviceRequest.audio != null){ + final file = File(_serviceRequest.audio); + _serviceRequest.audio = base64Encode(file.readAsBytesSync()); + } + print("pass data"); + int status = await _serviceRequestsProvider.createRequest( + user: _userProvider.user, + host: _settingProvider.host, + serviceRequest: _serviceRequest, + ); + _isLoading =false; + setState(() {}); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.requestCompleteSuccessfully, + ); + Navigator.of(context).pop(); + }else{ + String errorMessage = HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + }, + ), + ), + ], + ), + ABackButton(), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/requests/future_request_service_details.dart b/lib/views/pages/user/requests/future_request_service_details.dart new file mode 100644 index 0000000..7f56dd5 --- /dev/null +++ b/lib/views/pages/user/requests/future_request_service_details.dart @@ -0,0 +1,53 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/pages/user/requests/request_details.dart'; +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; +import 'package:test_sa/views/widgets/loaders/failed_loading.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class FutureRequestServiceDetails extends StatefulWidget { + static final String id = "/service-request-details"; + + @override + _FutureRequestServiceDetailsState createState() => _FutureRequestServiceDetailsState(); +} + +class _FutureRequestServiceDetailsState extends State { + + UserProvider _userProvider; + SettingProvider _settingProvider; + @override + Widget build(BuildContext context) { + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + String requestId = ModalRoute.of(context).settings.arguments; + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + body: FutureBuilder( + future: ServiceRequestsProvider().getSingleServiceRequest( + requestId: requestId, + user: _userProvider.user, + host: _settingProvider.host, + subtitle: _subtitle + ), + builder: (BuildContext context, AsyncSnapshot snapshot){ + if(snapshot.hasError) + return FailedLoading( + message: snapshot.error.toString(), + onReload: (){setState(() {});}, + ); + if(snapshot.hasData){ + return RequestDetailsPage( + serviceRequest: snapshot.data, + ); + } + return Center(child: ALoading()); + }, + ), + ); + } +} diff --git a/lib/views/pages/user/requests/report/create_service_report.dart b/lib/views/pages/user/requests/report/create_service_report.dart new file mode 100644 index 0000000..b509b58 --- /dev/null +++ b/lib/views/pages/user/requests/report/create_service_report.dart @@ -0,0 +1,661 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_report_last_calls_provider.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/part.dart'; +import 'package:test_sa/models/service_report.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; +import 'package:test_sa/views/widgets/equipment/auto_complete_devices_field.dart'; +import 'package:test_sa/views/widgets/images/mini_one_image_picker.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/parts/auto_complete_parts_field.dart'; +import 'package:test_sa/views/widgets/parts/part_item.dart'; +import 'package:test_sa/views/widgets/speech_to_text/speech_to_text.dart'; +import 'package:test_sa/views/widgets/status/report/service_report_last_call.dart'; +import 'package:test_sa/views/widgets/status/report/service_report_reasons.dart'; +import 'package:test_sa/views/widgets/status/report/service_report_status.dart'; +import 'package:test_sa/views/widgets/status/report/service_report_type.dart'; +import 'package:test_sa/views/widgets/status/report/service_status.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +class CreateServiceReport extends StatefulWidget { + static final String id = "/create-service-report"; + final ServiceRequest request ; + + const CreateServiceReport({Key key, this.request}) : super(key: key); + @override + _CreateServiceReportState createState() => _CreateServiceReportState(); +} + +class _CreateServiceReportState extends State with TickerProviderStateMixin{ + + + UserProvider _userProvider; + SettingProvider _settingProvider; + ServiceRequestsProvider _serviceRequestsProvider; + + bool _validate = false; + ServiceReport _serviceReport; + bool _isLoading = false; + + Subtitle _subtitle; + File _image; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + TextEditingController _faultController = TextEditingController(); + TextEditingController _workPreformedController = TextEditingController(); + + @override + void initState() { + _serviceReport = ServiceReport( + visitDate: DateTime.now(), + type: const Status(id: 2), + device: Device( + id: widget.request.deviceId, + serialNumber: widget.request.deviceSerialNumber, + ), + parts: [] + ); + super.initState(); + } + + @override + void dispose() { + _faultController.dispose(); + _workPreformedController.dispose(); + super.dispose(); + } + @override + Widget build(BuildContext context) { + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _serviceRequestsProvider = Provider.of(context); + _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + key: _scaffoldKey, + body: SafeArea( + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Form( + key: _formKey, + child: Stack( + children: [ + ListView( + children: [ + //AppNameBar(), + const SizedBox(height: 16,), + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _subtitle.newServiceReport, + style: Theme.of(context).textTheme.headline5.copyWith( + color: AColors.cyan, + fontSize: 28, + fontWeight: FontWeight.bold + ), + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + margin: const EdgeInsets.symmetric(horizontal: 16,vertical: 16), + decoration: BoxDecoration( + color: AColors.grey, + borderRadius: BorderRadius.circular(AppStyle.getBorderRadius(context)), + boxShadow: [ + const BoxShadow( + color: AColors.grey, + offset: Offset(0,-1), + ) + ] + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 8,), + Wrap( + spacing: 10, + children: [ + ASubTitle("${_subtitle.callId}: ${widget.request.requestCode}",font: 14,), + widget.request.deviceSerialNumber == null ? const SizedBox(): + ASubTitle("${_subtitle.deviceSN}: ${widget.request.deviceSerialNumber}",font: 14,), + Text( + "${_subtitle.customer}: ${widget.request.hospitalName}", + style: Theme.of(context).textTheme.subtitle1.copyWith( + fontWeight: FontWeight.bold, + fontSize: 12, + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ) + + ], + ), + const Divider(), + + // Report type and Reasons + Row( + children: [ + // Report Status + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.reportType), + _validate && _serviceReport.type == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + ServiceReportTypeMenu( + initialValue: _serviceReport.type, + onSelect: (status){ + _serviceReport.type = status; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + // visit date + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.visitDate), + _validate && _serviceReport.visitDate == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + Row( + children: [ + Expanded( + child: ADatePicker( + date: _serviceReport.visitDate, + from: DateTime.now().subtract(const Duration(days: 365)), + to: DateTime.now().add(const Duration(days: 365)), + onDatePicker: (date){ + _serviceReport.visitDate = date; + setState(() {}); + }, + ), + ), + ], + ), + ], + ), + ), + + ], + ), + const SizedBox(height: 8,), + // device sn + Visibility( + visible: widget.request.deviceSerialNumber == null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.deviceSN), + _validate && _serviceReport.device?.id == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + AutoCompleteDeviceField( + hospitalId: widget.request.hospitalId, + initialValue: _serviceReport.device, + onPick: (id){ + _serviceReport.device.id = id; + }, + ), + const SizedBox(height: 8,), + ], + ), + ), + const SizedBox(height: 8,), + ASubTitle(_subtitle.serviceType), + _validate && _serviceReport.serviceType == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + ServiceStatusMenu( + initialValue: _serviceReport.serviceType, + onSelect: (status){ + _serviceReport.serviceType = status; + }, + ), + const SizedBox(height: 8,), + // Report status and Service Type + Row( + children: [ + // report status + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.reportStatus), + _validate && _serviceReport.status == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + ServiceReportStatusMenu( + report: _serviceReport, + onSelect: (status){ + _serviceReport.status = status; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + Provider.of(context).isLoading == null + ? const SizedBox.shrink(): + // Call's last Situation + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + ASubTitle(_subtitle.callLastSituation), + _validate && _serviceReport.callLastSituation == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + ServiceReportLastCallsMenu( + report: _serviceReport, + onSelect: (status){ + if(status?.id == 12 + || _serviceReport.callLastSituation?.id == 12){ + _serviceReport.callLastSituation = status; + setState(() {}); + } else { + _serviceReport.callLastSituation = status; + } + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8,), + // invoice number & code + _serviceReport.callLastSituation?.id != 12 ? const SizedBox.shrink(): + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.invoiceNumber), + const SizedBox(height: 8,), + ATextFormField( + initialValue: _serviceReport?.invoiceNumber, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.number, + onSaved: (value){ + _serviceReport.invoiceNumber = value; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.invoiceCode), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.invoiceCode, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.text, + onSaved: (value){ + _serviceReport.invoiceCode = value; + }, + ), + ], + ), + ), + ], + ), + + const SizedBox(height: 8,), + Row( + children: [ + ASubTitle(_subtitle.faultDescription), + Expanded( + child: SizedBox( + height: 32 * AppStyle.getScaleFactor(context), + child: SpeechToTextButton( + controller: _faultController, + mini: true, + ), + ), + ), + ], + ), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.faultDescription, + textAlign: TextAlign.center, + controller: _faultController, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.multiline, + onSaved: (value){ + _serviceReport.faultDescription = value; + }, + ), + const SizedBox(height: 8,), + Row( + children: [ + ASubTitle(_subtitle.workPreformed), + Expanded( + child: SizedBox( + height: 32 * AppStyle.getScaleFactor(context), + child: SpeechToTextButton( + controller: _workPreformedController, + mini: true, + ), + ), + ), + ], + ), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.workPreformed, + textAlign: TextAlign.center, + controller: _workPreformedController, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.multiline, + onSaved: (value){ + _serviceReport.workPreformed = value; + }, + ), + const SizedBox(height: 8,), + + const SizedBox(height: 8,), + Row( + children: [ + // reasons + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.reasons), + const SizedBox(height: 4,), + ServiceReportReasonsMenu( + initialValue: _serviceReport.reason, + onSelect: (status){ + _serviceReport.reason = status; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.attachImage), + AMiniOneImagePicker( + //error: _validate && _serviceReport.image == null, + image: _image, + onPick: (image){ + _image =image; + _serviceReport.image = base64Encode(image.readAsBytesSync()); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8,), + // Traveling Hours & Working Hours + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.workingHours), + const SizedBox(height: 8,), + AppTimer( + timer: _serviceReport.timer, + onChange: (timer) async{ + _serviceReport.timer = timer; + return true; + }, + ), + // ATextFormField( + // initialValue: _serviceReport?.workHours, + // textAlign: TextAlign.center, + // hintText: "i.e 3, 3.5, 4", + // style: Theme.of(context).textTheme.subtitle1, + // validator: (value) => + // Validator.isNumeric(value) + // ? null : _subtitle.requiredWord, + // textInputType: TextInputType.number, + // onSaved: (value){ + // _serviceReport.workHours = value; + // }, + // ), + ], + ), + ), + const SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.travelingHours), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.travelingHours, + textAlign: TextAlign.center, + hintText: "i.e 3, 3.5, 4", + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.isNumeric(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.number, + onSaved: (value){ + _serviceReport.travelingHours = value; + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8,), + // Operating Hours and Job Sheet Number + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.operatingHours), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.operatingHours, + textAlign: TextAlign.center, + hintText: "i.e 3, 3.5, 4", + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.isNumeric(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.number, + onSaved: (value){ + _serviceReport.operatingHours = value; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.jobSheetNumber), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.jobSheetNumber, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + textInputType: TextInputType.name, + onSaved: (value){ + _serviceReport.jobSheetNumber = value; + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8,), + // Part Number and Quantity + Row( + children: [ + Expanded( + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.partNumber), + _validate && _serviceReport.parts == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + AutoCompletePartsField( + onPick: (part){ + _serviceReport.parts.add(part); + }, + ), + ], + ), + ), + + ], + ), + SizedBox(height: 8*AppStyle.getScaleFactor(context),), + Row( + children: [ + Expanded(flex:3,child: Text(_subtitle.number)), + Expanded(flex: 1,child: Text(_subtitle.quantity)), + ], + ), + Column( + children: List.generate( + _serviceReport.parts.length, + (index) { + Part _part = _serviceReport.parts[index]; + return PartItem( + part: _part, + onDelete: (part){ + _serviceReport.parts.remove(part); + setState(() {}); + }, + ); + } + ), + ), + + const SizedBox(height: 16,), + + ], + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: AButton( + text: _subtitle.submit, + onPressed: () async { + _validate = true; + if(!_formKey.currentState.validate()){ + setState(() {}); + return; + } + if(!_serviceReport.validate()) return; + _formKey.currentState.save(); + + _isLoading =true; + setState(() {}); + + int status = await _serviceRequestsProvider.createServiceReport( + user: _userProvider.user, + host: _settingProvider.host, + report: _serviceReport, + request: widget.request + ); + _isLoading =false; + setState(() {}); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.requestCompleteSuccessfully, + ); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }else{ + String errorMessage = HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + }, + ), + ), + const SizedBox(height: 300,) + ], + ), + const ABackButton(), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/requests/report/edit_service_report.dart b/lib/views/pages/user/requests/report/edit_service_report.dart new file mode 100644 index 0000000..aeed77b --- /dev/null +++ b/lib/views/pages/user/requests/report/edit_service_report.dart @@ -0,0 +1,658 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_report_last_calls_provider.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/part.dart'; +import 'package:test_sa/models/service_report.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; +import 'package:test_sa/views/widgets/equipment/auto_complete_devices_field.dart'; +import 'package:test_sa/views/widgets/images/mini_one_image_picker.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/parts/auto_complete_parts_field.dart'; +import 'package:test_sa/views/widgets/parts/part_item.dart'; +import 'package:test_sa/views/widgets/speech_to_text/speech_to_text.dart'; +import 'package:test_sa/views/widgets/status/report/service_report_last_call.dart'; +import 'package:test_sa/views/widgets/status/report/service_report_reasons.dart'; +import 'package:test_sa/views/widgets/status/report/service_report_status.dart'; +import 'package:test_sa/views/widgets/status/report/service_report_type.dart'; +import 'package:test_sa/views/widgets/status/report/service_status.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +class EditServiceReport extends StatefulWidget { + static final String id = "/edit-service-report"; + final ServiceRequest request ; + final ServiceReport report; + + const EditServiceReport({Key key, this.request, this.report}) : super(key: key); + @override + _EditServiceReportState createState() => _EditServiceReportState(); +} + +class _EditServiceReportState extends State with TickerProviderStateMixin{ + + + UserProvider _userProvider; + SettingProvider _settingProvider; + ServiceRequestsProvider _serviceRequestsProvider; + + bool _validate = false; + ServiceReport _serviceReport; + bool _isLoading = false; + + Subtitle _subtitle; + File _image; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + TextEditingController _faultController = TextEditingController(); + TextEditingController _workPreformedController = TextEditingController(); + + @override + void initState() { + _serviceReport = widget.report; + super.initState(); + } + + @override + void dispose() { + _faultController.dispose(); + _workPreformedController.dispose(); + super.dispose(); + } + @override + Widget build(BuildContext context) { + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _serviceRequestsProvider = Provider.of(context); + _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + key: _scaffoldKey, + body: SafeArea( + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Form( + key: _formKey, + child: Stack( + children: [ + ListView( + children: [ + //AppNameBar(), + const SizedBox(height: 16,), + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _subtitle.editServiceReport, + style: Theme.of(context).textTheme.headline5.copyWith( + color: AColors.cyan, + fontSize: 28, + fontWeight: FontWeight.bold + ), + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + margin: const EdgeInsets.symmetric(horizontal: 16,vertical: 16), + decoration: BoxDecoration( + color: AColors.grey, + borderRadius: BorderRadius.circular(AppStyle.getBorderRadius(context)), + boxShadow: [ + const BoxShadow( + color: AColors.grey, + offset: Offset(0,-1), + ) + ] + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 8,), + Wrap( + spacing: 10, + children: [ + ASubTitle("${_subtitle.callId}: ${widget.request.requestCode}",font: 14,), + widget.request.deviceSerialNumber == null ? const SizedBox(): + ASubTitle("${_subtitle.deviceSN}: ${widget.request.deviceSerialNumber}",font: 14,), + Text( + "${_subtitle.customer}: ${widget.request.hospitalName}", + style: Theme.of(context).textTheme.subtitle1.copyWith( + fontWeight: FontWeight.bold, + fontSize: 12, + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ) + + ], + ), + const Divider(), + + // Report type and Reasons + Row( + children: [ + // Report Status + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.reportType), + _validate && _serviceReport.type == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + ServiceReportTypeMenu( + initialValue: _serviceReport.type, + onSelect: (status){ + _serviceReport.type = status; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + // visit date + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.visitDate), + _validate && _serviceReport.visitDate == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + Row( + children: [ + Expanded( + child: ADatePicker( + date: _serviceReport.visitDate, + from: DateTime.now().subtract(const Duration(days: 365)), + to: DateTime.now().add(const Duration(days: 365)), + onDatePicker: (date){ + _serviceReport.visitDate = date; + setState(() {}); + }, + ), + ), + ], + ), + ], + ), + ), + + ], + ), + const SizedBox(height: 8,), + // device sn + Visibility( + visible: true,//widget.report.device == null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.deviceSN), + _validate && _serviceReport.device?.id == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + AutoCompleteDeviceField( + hospitalId: widget.request.hospitalId, + initialValue: _serviceReport.device, + onPick: (id){ + _serviceReport.device.id = id; + }, + ), + const SizedBox(height: 8,), + ], + ), + ), + const SizedBox(height: 8,), + ASubTitle(_subtitle.serviceType), + _validate && _serviceReport.serviceType == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + ServiceStatusMenu( + initialValue: _serviceReport.serviceType, + onSelect: (status){ + _serviceReport.serviceType = status; + }, + ), + const SizedBox(height: 8,), + // Report status and Service Type + Row( + children: [ + // report status + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.reportStatus), + _validate && _serviceReport.status == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + ServiceReportStatusMenu( + report: _serviceReport, + onSelect: (status){ + _serviceReport.status = status; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + Provider.of(context).isLoading == null + ? const SizedBox.shrink(): + // Call's last Situation + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + ASubTitle(_subtitle.callLastSituation), + _validate && _serviceReport.callLastSituation == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + ServiceReportLastCallsMenu( + report: _serviceReport, + onSelect: (status){ + if(status?.id == 12 + || _serviceReport.callLastSituation?.id == 12){ + _serviceReport.callLastSituation = status; + setState(() {}); + } else { + _serviceReport.callLastSituation = status; + } + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8,), + // invoice number & code + _serviceReport.callLastSituation?.id != 12 ? const SizedBox.shrink(): + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.invoiceNumber), + const SizedBox(height: 8,), + ATextFormField( + initialValue: _serviceReport?.invoiceNumber, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.number, + onSaved: (value){ + _serviceReport.invoiceNumber = value; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.invoiceCode), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.invoiceCode, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.text, + onSaved: (value){ + _serviceReport.invoiceCode = value; + }, + ), + ], + ), + ), + ], + ), + + const SizedBox(height: 8,), + Row( + children: [ + ASubTitle(_subtitle.faultDescription), + Expanded( + child: SizedBox( + height: 32 * AppStyle.getScaleFactor(context), + child: SpeechToTextButton( + controller: _faultController, + mini: true, + ), + ), + ), + ], + ), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.faultDescription, + textAlign: TextAlign.center, + controller: _faultController, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.multiline, + onSaved: (value){ + _serviceReport.faultDescription = value; + }, + ), + const SizedBox(height: 8,), + Row( + children: [ + ASubTitle(_subtitle.workPreformed), + Expanded( + child: SizedBox( + height: 32 * AppStyle.getScaleFactor(context), + child: SpeechToTextButton( + controller: _workPreformedController, + mini: true, + ), + ), + ), + ], + ), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.workPreformed, + textAlign: TextAlign.center, + controller: _workPreformedController, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.hasValue(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.multiline, + onSaved: (value){ + _serviceReport.workPreformed = value; + }, + ), + const SizedBox(height: 8,), + + const SizedBox(height: 8,), + Row( + children: [ + // reasons + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.reasons), + const SizedBox(height: 4,), + ServiceReportReasonsMenu( + initialValue: _serviceReport.reason, + onSelect: (status){ + _serviceReport.reason = status; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.attachImage), + AMiniOneImagePicker( + //error: _validate && _serviceReport.image == null, + image: _image, + onPick: (image){ + _image =image; + _serviceReport.image = base64Encode(image.readAsBytesSync()); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8,), + // Traveling Hours & Working Hours + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.workingHours), + const SizedBox(height: 8,), + AppTimer( + timer: _serviceReport.timer, + onChange: (timer) async{ + _serviceReport.timer = timer; + return true; + }, + ), + // ATextFormField( + // initialValue: _serviceReport?.workHours, + // textAlign: TextAlign.center, + // hintText: "i.e 3, 3.5, 4", + // style: Theme.of(context).textTheme.subtitle1, + // validator: (value) => + // Validator.isNumeric(value) + // ? null : _subtitle.requiredWord, + // textInputType: TextInputType.number, + // onSaved: (value){ + // _serviceReport.workHours = value; + // }, + // ), + ], + ), + ), + const SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.travelingHours), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.travelingHours, + textAlign: TextAlign.center, + hintText: "i.e 3, 3.5, 4", + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.isNumeric(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.number, + onSaved: (value){ + _serviceReport.travelingHours = value; + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8,), + // Operating Hours and Job Sheet Number + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.operatingHours), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.operatingHours, + textAlign: TextAlign.center, + hintText: "i.e 3, 3.5, 4", + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.isNumeric(value) + ? null : _subtitle.requiredWord, + textInputType: TextInputType.number, + onSaved: (value){ + _serviceReport.operatingHours = value; + }, + ), + ], + ), + ), + const SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.jobSheetNumber), + const SizedBox(height: 4,), + ATextFormField( + initialValue: _serviceReport?.jobSheetNumber, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + textInputType: TextInputType.name, + onSaved: (value){ + _serviceReport.jobSheetNumber = value; + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8,), + // Part Number and Quantity + Row( + children: [ + Expanded( + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(_subtitle.partNumber), + _validate && _serviceReport.parts == null ? + ASubTitle(_subtitle.requiredWord,color: Colors.red,): + const SizedBox.shrink(), + const SizedBox(height: 4,), + AutoCompletePartsField( + onPick: (part){ + _serviceReport.parts.add(part); + setState(() {}); + }, + ), + ], + ), + ), + + ], + ), + SizedBox(height: 8*AppStyle.getScaleFactor(context),), + Row( + children: [ + Expanded(flex:3,child: Text(_subtitle.number)), + Expanded(flex: 1,child: Text(_subtitle.quantity)), + ], + ), + Column( + children: List.generate( + _serviceReport.parts.length, + (index) { + Part _part = _serviceReport.parts[index]; + return PartItem( + part: _part, + onDelete: (part){ + _serviceReport.parts.remove(part); + setState(() {}); + }, + ); + } + ), + ), + + const SizedBox(height: 16,), + + ], + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: AButton( + text: _subtitle.update, + onPressed: () async { + _validate = true; + if(!_formKey.currentState.validate()){ + setState(() {}); + return; + } + if(!_serviceReport.validate()) { + setState(() {}); + return; + } + _formKey.currentState.save(); + + _isLoading =true; + setState(() {}); + + int status = await _serviceRequestsProvider.updateServiceReport( + user: _userProvider.user, + host: _settingProvider.host, + report: _serviceReport, + request: widget.request + ); + _isLoading =false; + setState(() {}); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.requestCompleteSuccessfully, + ); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }else{ + String errorMessage = HttpStatusManger.getStatusMessage( + status: status, subtitle: _subtitle); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + errorMessage + ), + ) + ); + } + }, + ), + ), + const SizedBox(height: 300,) + ], + ), + const ABackButton(), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/requests/report/future_service_report.dart b/lib/views/pages/user/requests/report/future_service_report.dart new file mode 100644 index 0000000..a2e1897 --- /dev/null +++ b/lib/views/pages/user/requests/report/future_service_report.dart @@ -0,0 +1,58 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/service_report.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/pages/user/requests/request_details.dart'; +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; +import 'package:test_sa/views/widgets/loaders/failed_loading.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'edit_service_report.dart'; +class FutureServiceReport extends StatefulWidget { + final ServiceRequest request; + + const FutureServiceReport({Key key, this.request}) : super(key: key); + + @override + _FutureServiceReportState createState() => _FutureServiceReportState(); +} + +class _FutureServiceReportState extends State { + + UserProvider _userProvider; + SettingProvider _settingProvider; + @override + Widget build(BuildContext context) { + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + body: FutureBuilder( + future: ServiceRequestsProvider().getSingleServiceReport( + reportId: widget.request.reportID, + user: _userProvider.user, + host: _settingProvider.host, + subtitle: _subtitle + ), + builder: (BuildContext context, AsyncSnapshot snapshot){ + if(snapshot.hasError) + return FailedLoading( + message: snapshot.error.toString(), + onReload: (){setState(() {});}, + ); + if(snapshot.hasData){ + return EditServiceReport( + report: snapshot.data, + request: widget.request, + ); + } + return Center(child: ALoading()); + }, + ), + ); + } +} diff --git a/lib/views/pages/user/requests/request_details.dart b/lib/views/pages/user/requests/request_details.dart new file mode 100644 index 0000000..c9e7858 --- /dev/null +++ b/lib/views/pages/user/requests/request_details.dart @@ -0,0 +1,356 @@ +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/enums/user_types.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/pages/user/requests/report/create_service_report.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/dialogs/dialog.dart'; +import 'package:test_sa/views/widgets/images/images_list.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:test_sa/views/widgets/requests/info_row.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; +import 'package:test_sa/views/widgets/requests/service_request_update_dialog.dart'; +import 'package:test_sa/views/widgets/sound/sound_player.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +import '../report_issues_page.dart'; +import 'report/future_service_report.dart'; +class RequestDetailsPage extends StatelessWidget { + static final String id = "/call-details"; + final ServiceRequest serviceRequest; + + const RequestDetailsPage({Key key, this.serviceRequest}) : super(key: key); + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + UserProvider _userProvider = Provider.of(context); + SettingProvider _settingProvider = Provider.of(context); + ServiceRequestsProvider _serviceRequestsProvider = Provider.of(context); + return DefaultTabController( + length: 2, + child: Scaffold( + body: SafeArea( + child: Column( + children: [ + Container( + color:AColors.primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Row( + children: [ + ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.details, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + Visibility( + visible: _userProvider.user.type == UsersTypes.normal_user, + replacement: AIconButton( + iconData: Icons.edit, + color: AColors.white, + buttonSize: 42, + backgroundColor: AColors.green, + onPressed: () async { + showModalBottomSheet( + context: context, + builder: (context){ + return ServiceRequestsUpdateDialog(request: serviceRequest,); + }); + // DateTime picked = await showDatePicker( + // context: context, + // initialDate: DateTime.now(), + // firstDate: DateTime.now(), + // lastDate: DateTime.now().add(Duration(days: 182)) + // ); + // if(picked == null){return;} + // showDialog( + // context: context, + // barrierDismissible: false, + // builder: (BuildContext context) { + // return CupertinoAlertDialog( + // title: Text(_subtitle.updatingDots), + // content: Center(child: CircularProgressIndicator()), + // ); + // }, + // ); + // int status = await _serviceRequestsProvider.updateDate( + // user: _userProvider.user, + // host: _settingProvider.host, + // request: serviceRequest, + // newDate: picked.toString().split(" ").first + // ); + // Navigator.of(context).pop(); + // Fluttertoast.showToast( + // msg: HttpStatusManger.getStatusMessage(status: status, subtitle: _subtitle), + // ); + }, + ), + child: AIconButton( + iconData: Icons.warning_amber_rounded, + color: AColors.white, + buttonSize: 42, + backgroundColor: AColors.deepOrange, + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => ReportIssuesPage(serviceRequest: serviceRequest,) + ) + ); + }, + ), + ), + SizedBox(width: 16,) + ], + ), + ), + + serviceRequest.devicePhotos.isEmpty ? SizedBox.shrink(): + Column( + children: [ + SizedBox(height: 8,), + MaterialButton( + padding: EdgeInsets.zero, + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: InteractiveViewer( + child: Center( + child: ImageLoader( + url: serviceRequest.devicePhotos.first, + boxFit: BoxFit.contain, + ), + ), + ), + ) + ) + ); + }, + child: SizedBox( + height: 140 * AppStyle.getScaleFactor(context), + width: MediaQuery.of(context).size.width, + child: ImageLoader( + url: serviceRequest.devicePhotos.first, + boxFit: BoxFit.cover, + ), + ), + ), + SizedBox(height: 8,), + SizedBox( + height: 60* AppStyle.getScaleFactor(context), + child: ImagesList( + images: serviceRequest.devicePhotos, + ), + ), + ], + ), + + TabBar( + labelColor: AColors.primaryColor, + tabs: [ + Tab(text: _subtitle.general,), + Tab(text: _subtitle.serviceRequestInformation,), + ]), + SizedBox(height: 8,), + Expanded( + child: TabBarView( + + children: [ + ListView( + padding: EdgeInsets.symmetric(horizontal: 16), + children: [ + RequestInfoRow( + title: _subtitle.code, + info: serviceRequest.requestCode, + ), + RequestInfoRow( + title: _subtitle.deviceSN, + info: serviceRequest.deviceSerialNumber, + ), + RequestInfoRow( + title: _subtitle.deviceModel, + info : serviceRequest.deviceModel, + ), + RequestInfoRow( + title: _subtitle.engineerName, + info: serviceRequest.engineerName, + ), + RequestInfoRow( + title: _subtitle.engineerPhone, + info: serviceRequest.engineerMobile, + ), + RequestInfoRow( + title: _subtitle.date, + info: serviceRequest.date, + ), + serviceRequest.nextVisitDate == null ? SizedBox.shrink() : + RequestInfoRow( + title: _subtitle.nextVisitDate, + info: DateFormat('EE dd/MM/yyyy').format(serviceRequest.nextVisitDate), + ), + Row( + children: [ + Expanded( + child: Text( + "${_subtitle.status} : ", + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + StatusLabel(label: serviceRequest.statusLabel, + color: AColors.getRequestStatusColor(serviceRequest.statusValue) + ), + ], + ), + Divider(color: Theme.of(context).primaryColor,), + RequestInfoRow( + title: _subtitle.hospital, + info: serviceRequest.hospitalName, + ), + RequestInfoRow( + title: _subtitle.unite, + info: serviceRequest.departmentName, + ), + RequestInfoRow( + title: _subtitle.deviceArName, + content: serviceRequest.deviceArName, + ), + RequestInfoRow( + title: _subtitle.deviceEnName, + content: serviceRequest.deviceEnName, + ), + RequestInfoRow( + title: _subtitle.maintenanceIssue, + content: serviceRequest.maintenanceIssue, + ), + if(serviceRequest.audio?.isNotEmpty == true) + ASoundPlayer( + audio: serviceRequest.audio, + ), + Center( + child: Padding( + padding: EdgeInsets.all(32), + child: AButton( + text: _subtitle.duplicateRequest, + onPressed: () async { + bool result = await showDialog( + context: context, + builder: (_) => AAlertDialog( + title: _subtitle.duplicateAlert, + content: _subtitle.duplicateAlertMessage, + ) + ); + if(result == true){ + showDialog( + context: context, + builder: (context){ + return Center(child: CircularProgressIndicator()); + } + ); + int status = await _serviceRequestsProvider.createDuplicatedReport( + host: _settingProvider.host, + user: _userProvider.user, + request: serviceRequest + ); + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + HttpStatusManger.getStatusMessage(status: status, subtitle: _subtitle) + ) + ) + ); + } + }, + ), + ), + ) + ], + ), + serviceRequest.viewReport ? + ListView( + padding: EdgeInsets.symmetric(horizontal: 16), + children: [ + RequestInfoRow( + title: _subtitle.faultDescription, + content: serviceRequest.faultDescription, + ), + RequestInfoRow( + title: _subtitle.workPerformed, + content: serviceRequest.workPerformed, + ), + RequestInfoRow( + title: _subtitle.visitDate, + info: serviceRequest.visitDate, + ), + RequestInfoRow( + title: _subtitle.jobSheetNumber, + info: serviceRequest.jobSheetNumber, + ), + _userProvider.user.type == UsersTypes.engineer ? + Padding( + padding: EdgeInsets.all(32), + child: AButton( + text: _subtitle.editServiceReport, + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => FutureServiceReport( + request: serviceRequest, + ) + ), + ); + }, + ), + ): SizedBox.shrink(), + ], + ): + _userProvider.user.type == UsersTypes.engineer ? + Center( + child: Padding( + padding: EdgeInsets.all(32), + child: AButton( + text: "Create Report", + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => CreateServiceReport( + request: serviceRequest, + ) + ), + ); + }, + ), + ), + ): Center(child: ASubTitle(_subtitle.noDateFound),), + + + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/requests/requests_page.dart b/lib/views/pages/user/requests/requests_page.dart new file mode 100644 index 0000000..af926f2 --- /dev/null +++ b/lib/views/pages/user/requests/requests_page.dart @@ -0,0 +1,127 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/service_request/service_request_search.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/widgets/requests/service_request_list.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/search/service_request_search_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class ServiceRequestsPage extends StatefulWidget { + static final String id = "/service-requests"; + @override + _ServiceRequestsPageState createState() => _ServiceRequestsPageState(); +} + +class _ServiceRequestsPageState extends State + with TickerProviderStateMixin{ + ServiceRequestsProvider _serviceRequestsProvider; + UserProvider _userProvider; + SettingProvider _settingProvider; + bool _expandedSearch = false; + bool _firstTime = true; + @override + Widget build(BuildContext context) { + _serviceRequestsProvider = Provider.of(context); + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + Subtitle _subtitle = AppLocalization.of(context).subtitle; + if(_firstTime){ + _serviceRequestsProvider.reset(); + _firstTime = false; + } + + return Scaffold( + body: SafeArea( + child: LoadingManager( + isLoading: _serviceRequestsProvider.isLoading, + isFailedLoading: _serviceRequestsProvider.serviceRequests == null, + stateCode: _serviceRequestsProvider.stateCode, + onRefresh: () async { + _serviceRequestsProvider.reset(); + await _serviceRequestsProvider.getRequests( + user: _userProvider.user, + host: _settingProvider.host, + hospitalId: _userProvider.user.hospital.id, + ); + }, + child: Stack( + children: [ + Column( + children: [ + Container( + color:AColors.primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Column( + children: [ + Row( + children: [ + ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.serviceRequests, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + AIconButton( + key: ValueKey(_expandedSearch), + iconData: Icons.search, + color: AColors.secondaryColor, + buttonSize: 42, + backgroundColor: AColors.white, + onPressed: () async { + ServiceRequestSearch _temp = await showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context){ + return ServiceRequestsSearchDialog( + initialSearchValue: _serviceRequestsProvider.search, + ); + }); + if(_temp != null){ + _serviceRequestsProvider.search = _temp; + _serviceRequestsProvider.reset(); + setState(() {}); + } + }, + ), + SizedBox(width: 16,) + ], + ), + + ], + ), + ), + Expanded( + child: ServiceRequestsList( + nextPage: _serviceRequestsProvider.nextPage, + onLazyLoad: () async { + await _serviceRequestsProvider.getRequests( + user: _userProvider.user, + host: _settingProvider.host, + hospitalId: _userProvider.user.hospital.id, + ); + }, + requests: _serviceRequestsProvider.serviceRequests, + ), + ), + ], + ), + + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/visits/preventive_maintenance_visits_page.dart b/lib/views/pages/user/visits/preventive_maintenance_visits_page.dart new file mode 100644 index 0000000..b4eec47 --- /dev/null +++ b/lib/views/pages/user/visits/preventive_maintenance_visits_page.dart @@ -0,0 +1,190 @@ +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/preventive_maintenance_visits_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/visits/visits_group.dart'; +import 'package:test_sa/models/visits/visits_search.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/pages/user/visits/update_visits_group_sheet.dart'; +import 'package:test_sa/views/widgets/visits/visits_list.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/search/visits_search_bar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +class PreventiveMaintenanceVisitsPage extends StatefulWidget { + static final String id = "/preventive-maintenance-visits"; + @override + _PreventiveMaintenanceVisitsPageState createState() => _PreventiveMaintenanceVisitsPageState(); +} + +class _PreventiveMaintenanceVisitsPageState extends State + with TickerProviderStateMixin{ + PreventiveMaintenanceVisitsProvider _visitsProvider; + UserProvider _userProvider; + SettingProvider _settingProvider; + Subtitle _subtitle; + + @override + Widget build(BuildContext context) { + _visitsProvider = Provider.of(context); + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + body: SafeArea( + child: LoadingManager( + isLoading: _visitsProvider.isLoading, + isFailedLoading: _visitsProvider.visits == null, + stateCode: _visitsProvider.stateCode, + onRefresh: () async { + //_visitsProvider.visitsSearch = VisitsSearch(); + _visitsProvider.reset(); + await _visitsProvider.getVisits( + user: _userProvider.user, + host: _settingProvider.host, + //visitsSearch: _visitsSearch + ); + }, + child: Stack( + children: [ + Column( + children: [ + Container( + color:AColors.primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Column( + children: [ + Row( + children: [ + ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.preventiveMaintenance, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + AIconButton( + iconData: Icons.search, + color: AColors.secondaryColor, + buttonSize: 42, + backgroundColor: AColors.white, + onPressed: () async { + VisitsSearch _temp = await showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context){ + return VisitsSearchDialog( + initialSearchValue: _visitsProvider.visitsSearch, + ); + }); + if(_temp != null){ + _visitsProvider.visitsSearch = _temp; + _visitsProvider.reset(); + setState(() {}); + await _visitsProvider.getVisits( + user: _userProvider.user, + host: _settingProvider.host, + //visitsSearch: _visitsSearch + ); + } + }, + ), + SizedBox(width: 16,) + ], + ), + + ], + ), + ), + Visibility( + visible: _visitsProvider.visitsSearch.toSearchString().isNotEmpty, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: AButton( + text: _subtitle.clearSearch, + onPressed: (){ + _visitsProvider.visitsSearch = VisitsSearch(); + _visitsProvider.reset(); + setState(() {}); + }, + ), + ), + ), + Expanded( + child: VisitsList( + nextPage: _visitsProvider.nextPage, + onLazyLoad: () async { + await _visitsProvider.getVisits( + user: _userProvider.user, + host: _settingProvider.host, + //visitsSearch: _visitsSearch + ); + }, + onEditGroup: (visits) async { + VisitsGroup _group = await showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) { + return UpdateVisitsGroupSheet(visits: visits,title:_subtitle.updatePreventiveMaintenance,); + }, + )as VisitsGroup; + if(_group != null){ + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text(_subtitle.updatingDots), + content: Center(child: CircularProgressIndicator()), + ); + }, + ); + int status = await _visitsProvider.updateGroupOfVisits( + user: _userProvider.user, + host: _settingProvider.host, + group: _group + ); + Navigator.of(context).pop(); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.preventiveMaintenanceUpdatedSuccessfully, + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + ); + }else{ + Fluttertoast.showToast( + msg: HttpStatusManger.getStatusMessage( + status: status, + subtitle: _subtitle + ), + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + ); + } + } + }, + visits: _visitsProvider.visits, + ), + ), + ], + ), + + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/visits/regular_visits_page.dart b/lib/views/pages/user/visits/regular_visits_page.dart new file mode 100644 index 0000000..d1bd69b --- /dev/null +++ b/lib/views/pages/user/visits/regular_visits_page.dart @@ -0,0 +1,187 @@ +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/regular_visits_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/visits/visits_group.dart'; +import 'package:test_sa/models/visits/visits_search.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/pages/user/visits/update_visits_group_sheet.dart'; +import 'package:test_sa/views/widgets/visits/visits_list.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/search/visits_search_bar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +class RegularVisitsPage extends StatefulWidget { + static final String id = "/Regular-visits"; + @override + _RegularVisitsPageState createState() => _RegularVisitsPageState(); +} + +class _RegularVisitsPageState extends State + with TickerProviderStateMixin{ + RegularVisitsProvider _visitsProvider; + UserProvider _userProvider; + SettingProvider _settingProvider; + bool _expandedSearch = false; + Subtitle _subtitle; + @override + Widget build(BuildContext context) { + _visitsProvider = Provider.of(context); + _settingProvider = Provider.of(context); + _userProvider = Provider.of(context); + _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + body: SafeArea( + child: LoadingManager( + isLoading: _visitsProvider.isLoading, + isFailedLoading: _visitsProvider.visits == null, + stateCode: _visitsProvider.stateCode, + onRefresh: () async { + _visitsProvider.reset(); + //_visitsProvider.visitsSearch = VisitsSearch(); + await _visitsProvider.getVisits( + user: _userProvider.user, + host: _settingProvider.host, + ); + }, + child: Stack( + children: [ + Column( + children: [ + Container( + color:AColors.primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Column( + children: [ + Row( + children: [ + ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.preventiveMaintenance, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + AnimatedSwitcher( + duration: Duration(milliseconds: 400), + child: AIconButton( + key: ValueKey(_expandedSearch), + iconData: _expandedSearch ? Icons.keyboard_arrow_up :Icons.search, + color: AColors.secondaryColor, + buttonSize: 42, + backgroundColor: AColors.white, + onPressed: () async { + VisitsSearch _temp = await showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context){ + return VisitsSearchDialog( + initialSearchValue: _visitsProvider.visitsSearch, + ); + }); + if(_temp != null){ + _visitsProvider.visitsSearch.fromSearch(_temp) ; + _visitsProvider.reset(); + setState(() {}); + } + }, + ), + ), + SizedBox(width: 16,) + ], + ), + + ], + ), + ), + Visibility( + visible: _visitsProvider.visitsSearch.toSearchString().isNotEmpty, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: AButton( + text: _subtitle.clearSearch, + onPressed: (){ + _visitsProvider.visitsSearch = VisitsSearch(); + _visitsProvider.reset(); + setState(() {}); + }, + ), + ), + ), + Expanded( + child: VisitsList( + nextPage: _visitsProvider.nextPage, + onLazyLoad: () async { + await _visitsProvider.getVisits( + user: _userProvider.user, + host: _settingProvider.host, + ); + }, + onEditGroup: (visits) async { + VisitsGroup _group = await showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) { + return UpdateVisitsGroupSheet(visits: visits,title: _subtitle.updateRegularVisits,); + }, + )as VisitsGroup; + if(_group != null){ + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text(_subtitle.updatingDots), + content: Center(child: CircularProgressIndicator()), + ); + }, + ); + int status = await _visitsProvider.updateGroupOfVisits( + user: _userProvider.user, + host: _settingProvider.host, + group: _group + ); + Navigator.of(context).pop(); + if(status >= 200 && status < 300){ + Fluttertoast.showToast( + msg: _subtitle.regularVisitsUpdatedSuccessfully, + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + ); + }else{ + Fluttertoast.showToast( + msg: HttpStatusManger.getStatusMessage( + status: status, + subtitle: _subtitle + ), + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + ); + } + } + }, + visits: _visitsProvider.visits, + ), + ), + ], + ), + + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pages/user/visits/update_visits_group_sheet.dart b/lib/views/pages/user/visits/update_visits_group_sheet.dart new file mode 100644 index 0000000..50d925d --- /dev/null +++ b/lib/views/pages/user/visits/update_visits_group_sheet.dart @@ -0,0 +1,251 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/visits/visit.dart'; +import 'package:test_sa/models/visits/visits_group.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_small_button.dart'; +import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; +import 'package:test_sa/views/widgets/images/one_image_picker.dart'; +import 'package:test_sa/views/widgets/search/filter_item.dart'; +import 'package:test_sa/views/widgets/visits/visit_status.dart'; +import 'package:flutter/material.dart'; +class UpdateVisitsGroupSheet extends StatefulWidget { + final List visits; + final String title; + const UpdateVisitsGroupSheet({Key key, this.visits, this.title}) : super(key: key); + + @override + _UpdateVisitsGroupSheetState createState() => _UpdateVisitsGroupSheetState(); +} + +class _UpdateVisitsGroupSheetState extends State { + List status = [ + Status(label: "Done", id: 0,), + Status(label: "Not Yet", id: 1), + Status(label: "On Hold", id: 2,), + ]; + + List taskStatus = [ + Status(label: "Passed", id: 0,), + Status(label: "Failed", id: 1), + ]; + VisitsGroup _group = VisitsGroup(); + File _image; + Subtitle _subtitle; + + @override + void initState() { + super.initState(); + _group.visits = widget.visits; + _group.date = DateTime.now(); + } + + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + return Container( + height: MediaQuery.of(context).size.height / 1.3, + padding: EdgeInsets.symmetric( + horizontal: 16.0 * AppStyle.getScaleFactor(context), + vertical: 8.0 * AppStyle.getScaleFactor(context), + ), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + widget.title ?? _subtitle.updateVisitsGroup, + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ASmallButton( + text: _subtitle.cancel, + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + ], + ), + + AOneImagePicker( + image: _image, + onPick: (image){ + _image =image; + _group.image = base64Encode(image.readAsBytesSync()); + }, + ), + SizedBox(height: 12 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _group.jobSheetNumber, + hintText: _subtitle.jobSheetNumber, + style: Theme.of(context).textTheme.headline6, + onSaved: (value){ + _group.jobSheetNumber = value; + }, + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _group.workingHours, + hintText: _subtitle.workingHours, + style: Theme.of(context).textTheme.headline6, + onSaved: (value){ + _group.workingHours = value; + }, + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _group.travelingHours, + hintText: _subtitle.travelingHours, + style: Theme.of(context).textTheme.headline6, + onSaved: (value){ + _group.travelingHours = value; + }, + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + Row( + children: [ + Expanded( + child: Text( + "${_subtitle.date} :", + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ADatePicker( + date: _group.date ?? DateTime.now(), + onDatePicker: (date){ + _group.date = date; + setState(() {}); + }, + ), + ], + ), + // SizedBox(height: 8 * AStyling.getScaleFactor(context),), + Text( + "${_subtitle.status} :", + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + Center( + child: Wrap( + spacing: 10, + runSpacing: 10, + children: List.generate( + status.length, + (index) { + bool isSelected = _group.status == status[index]; + return FilterItem( + isSelected: isSelected, + onSelected: (){ + if(isSelected) + _group.status = null; + else + _group.status = status[index]; + + setState(() {}); + }, + status: status[index], + ); + } + + ), + ), + ), + Text( + "${_subtitle.taskStatus} :", + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + Center( + child: Wrap( + spacing: 10, + runSpacing: 10, + children: List.generate( + taskStatus.length, + (index) { + bool isSelected = _group.taskStatus == taskStatus[index]; + return FilterItem( + isSelected: isSelected, + onSelected: (){ + if(isSelected) + _group.taskStatus = null; + else + _group.taskStatus = taskStatus[index]; + + setState(() {}); + }, + status: taskStatus[index], + ); + } + + ), + ), + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + Expanded( + child: ListView.builder( + itemCount: widget.visits.length, + itemBuilder: (context,index){ + Visit visit = widget.visits[index]; + return Container( + padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + margin: EdgeInsets.symmetric(vertical: 4), + decoration: BoxDecoration( + color: AColors.primaryColor, + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: Row( + children: [ + Expanded( + child: Text( + "${_subtitle.sn}: "+visit.deviceSerialNumber ?? _subtitle.noSerialNumberFound, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontSize: 16, + fontWeight: FontWeight.bold + ), + ), + ), + VisitStatusLabel(visit: visit,) + ], + ), + ); + }, + ), + ), + AButton( + text: _subtitle.update, + onPressed: (){ + Navigator.of(context).pop(_group); + }, + ), + ], + ), + Align( + alignment: Alignment.topRight, + + ), + ], + ), + ); + } +} diff --git a/lib/views/pages/user/visits/visit_details.dart b/lib/views/pages/user/visits/visit_details.dart new file mode 100644 index 0000000..98738c3 --- /dev/null +++ b/lib/views/pages/user/visits/visit_details.dart @@ -0,0 +1,147 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/visits/visit.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:test_sa/views/widgets/images/images_list.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:test_sa/views/widgets/requests/info_row.dart'; +import 'package:test_sa/views/widgets/visits/visit_status.dart'; +import 'package:flutter/material.dart'; + +class VisitDetailsPage extends StatelessWidget { + static final String id = "/visit-details"; + final Visit visit; + + const VisitDetailsPage({Key key, this.visit}) : super(key: key); + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + body: SafeArea( + child: Column( + children: [ + Container( + color:AColors.primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 0,vertical: 4), + child: Row( + children: [ + ABackButton(), + Expanded( + child: Center( + child: Text( + _subtitle.visitInformation, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ), + SizedBox(width: 42,) + ], + ), + ), + Expanded( + child: ListView( + padding: EdgeInsets.symmetric(horizontal: 16), + children: [ + SizedBox(height: 8,), + MaterialButton( + padding: EdgeInsets.zero, + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: InteractiveViewer( + child: Center( + child: ImageLoader( + url: visit.images.first, + boxFit: BoxFit.contain, + ), + ), + ), + ) + ) + ); + }, + child: SizedBox( + height: 140 * AppStyle.getScaleFactor(context), + width: MediaQuery.of(context).size.width, + child: ImageLoader( + url: visit.images.isEmpty ? " ":visit.images.first, + boxFit: BoxFit.cover, + ), + ), + ), + SizedBox(height: 8,), + SizedBox( + height: 60* AppStyle.getScaleFactor(context), + child: ImagesList( + images: visit.images, + ), + ), + SizedBox(height: 8,), + RequestInfoRow( + title: _subtitle.code, + info: visit.serialNumber, + ), + RequestInfoRow( + title: _subtitle.deviceSN, + info: visit.deviceSerialNumber, + ), + + RequestInfoRow( + title: _subtitle.expectDate, + info: visit.expectDate, + ), + RequestInfoRow( + title: _subtitle.actualDate, + info: visit.actualDate, + ), + Row( + children: [ + Expanded( + child: Text( + "${_subtitle.status} : ", + style: Theme.of(context).textTheme.subtitle1.copyWith( + fontWeight: FontWeight.bold + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + VisitStatusLabel(visit: visit,), + ], + ), + + Divider(color: Theme.of(context).primaryColor,), + RequestInfoRow( + title: _subtitle.contactStatus, + info: visit.assignTo, + ), + RequestInfoRow( + title: _subtitle.engineerName, + info: visit.employName, + ), + RequestInfoRow( + title: _subtitle.hospital, + content: visit.hospitalName, + ), + RequestInfoRow( + title: _subtitle.deviceArName, + content: visit.deviceArabicName, + ), + RequestInfoRow( + title: _subtitle.deviceEnName, + content: visit.deviceEnglishName, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/app_name_bar.dart b/lib/views/widgets/app_name_bar.dart new file mode 100644 index 0000000..35a382a --- /dev/null +++ b/lib/views/widgets/app_name_bar.dart @@ -0,0 +1,22 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class AppNameBar extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + height: 50 * AppStyle.getScaleFactor(context), + color:AColors.primaryColor, + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + "Test SA", + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontStyle: FontStyle.italic + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/app_text_form_field.dart b/lib/views/widgets/app_text_form_field.dart new file mode 100644 index 0000000..407b351 --- /dev/null +++ b/lib/views/widgets/app_text_form_field.dart @@ -0,0 +1,114 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class ATextFormField extends StatefulWidget { + final Function(String) onSaved; + final Function(String) validator; + final Function(String) onChange; + final bool obscureText; + final VoidCallback showPassword; + final String hintText; + final String labelText; + final TextInputType textInputType; + final String initialValue; + final TextStyle style; + final bool enable; + final TextAlign textAlign; + final FocusNode node; + final Widget suffixIcon; + final IconData prefixIconData; + final double prefixIconSize; + final TextEditingController controller; + final TextInputAction textInputAction; + final VoidCallback onAction; + + const ATextFormField({ + Key key, + this.onSaved, + this.validator, + this.node, + this.onChange, + this.obscureText, + this.showPassword, + this.hintText, + this.labelText, + this.textInputType = TextInputType.text, + this.initialValue, + this.enable = true, + this.style, + this.textAlign, + this.suffixIcon, + this.prefixIconData, + this.prefixIconSize, + this.controller, + this.textInputAction, + this.onAction + }) : super(key: key); + + @override + State createState() => _ATextFormFieldState(); +} + +class _ATextFormFieldState extends State { + + @override + void initState() { + if(widget.controller != null) + widget.controller.text = widget.initialValue; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric( + horizontal: 16 + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color:AColors.black), + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: TextFormField( + focusNode: widget.node, + enabled: widget.enable, + onSaved: widget.onSaved, + initialValue: widget.controller != null ? null : widget.initialValue, + validator: widget.validator, + onChanged: widget.onChange, + textAlign: widget.textAlign ?? TextAlign.center, + obscureText: widget.obscureText ?? false, + keyboardType: widget.textInputType, + maxLines: widget.textInputType == TextInputType.multiline ? null : 1, + obscuringCharacter: "●", + controller: widget.controller, + textInputAction: + widget.textInputType == TextInputType.multiline ? null : widget.textInputAction ?? TextInputAction.next, + onEditingComplete: widget.onAction ?? () => FocusScope.of(context).nextFocus(), + style: widget.style, + decoration: InputDecoration( + border: InputBorder.none, + disabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + + hintText: widget.hintText, + labelText: widget.labelText, + suffixIcon: widget.suffixIcon, + prefixIcon: widget.prefixIconData == null ? null : Icon( + widget.prefixIconData, + size: widget.prefixIconSize == null + ? 32 * AppStyle.getScaleFactor(context) + : (widget.prefixIconSize - 10) * AppStyle.getScaleFactor(context), + color: AColors.black, + ) + ), + ), + ); + } +} diff --git a/lib/views/widgets/buttons/app_back_button.dart b/lib/views/widgets/buttons/app_back_button.dart new file mode 100644 index 0000000..c7c72ce --- /dev/null +++ b/lib/views/widgets/buttons/app_back_button.dart @@ -0,0 +1,30 @@ + +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +import 'app_icon_button.dart'; +class ABackButton extends StatelessWidget { + final VoidCallback onPressed; + + const ABackButton({Key key, this.onPressed}) : super(key: key); + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 16 + ), + child: AIconButton( + color: AColors.white, + backgroundColor: AColors.green, + iconData: FontAwesomeIcons.reply, + iconSize: 24, + buttonSize: 42, + onPressed: onPressed ?? (){ + Navigator.of(context).pop(); + }, + ), + ); + } +} diff --git a/lib/views/widgets/buttons/app_button.dart b/lib/views/widgets/buttons/app_button.dart new file mode 100644 index 0000000..231e1d8 --- /dev/null +++ b/lib/views/widgets/buttons/app_button.dart @@ -0,0 +1,50 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +class AButton extends StatelessWidget { + final String text; + final Color color; + final EdgeInsets padding; + + final TextStyle textStyle; + final VoidCallback onPressed; + + const AButton({ + Key key, + this.color = AColors.primaryColor, + this.text, + this.padding, + this.onPressed, + this.textStyle + }) : super(key: key); + @override + Widget build(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: color.computeLuminance() > 0.5 + ? AColors.black : Colors.white, + backgroundColor: color, + padding: padding ?? const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ) + ), + ), + onPressed: onPressed, + child: Text( + text??"", + style: textStyle + ?? Theme.of(context).textTheme.subtitle2.copyWith( + color: AColors.white, + fontSize: 18 + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ) + ), + ); + } +} diff --git a/lib/views/widgets/buttons/app_flat_button.dart b/lib/views/widgets/buttons/app_flat_button.dart new file mode 100644 index 0000000..903b05e --- /dev/null +++ b/lib/views/widgets/buttons/app_flat_button.dart @@ -0,0 +1,26 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +class AFlatButton extends StatelessWidget { + final String text; + final Color textColor; + final TextStyle style; + final EdgeInsets padding; + final VoidCallback onPressed; + + const AFlatButton({Key key, this.text, this.textColor,this.style ,this.onPressed,this.padding}) : super(key: key); + @override + Widget build(BuildContext context) { + return TextButton( + style: TextButton.styleFrom( + foregroundColor: this.textColor ?? Colors.black, padding: padding, + ), + onPressed: onPressed, + child: Text( + text??"", + style: style ?? Theme.of(context).textTheme.bodyText1, + textScaleFactor: AppStyle.getScaleFactor(context), + ) + ); + } +} diff --git a/lib/views/widgets/buttons/app_icon_button.dart b/lib/views/widgets/buttons/app_icon_button.dart new file mode 100644 index 0000000..8c1da58 --- /dev/null +++ b/lib/views/widgets/buttons/app_icon_button.dart @@ -0,0 +1,49 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +class AIconButton extends StatelessWidget { + + final IconData iconData; + final Color color; + final Color backgroundColor; + final VoidCallback onPressed; + final double iconSize; + final double buttonSize; + + + const AIconButton({ + Key key, + this.iconData, + this.onPressed, + this.color, + this.iconSize, + this.buttonSize = 54, + this.backgroundColor + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: buttonSize * AppStyle.getScaleFactor(context), + width: buttonSize * AppStyle.getScaleFactor(context), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + (AppStyle.borderRadius - 4) * AppStyle.getScaleFactor(context) + ) + ), + backgroundColor: backgroundColor ?? AColors.onPrimaryColor, + ), + onPressed: onPressed, + child: FaIcon( + iconData, + color: color ?? AColors.primaryColor, + size: iconSize ?? 32, + ), + ), + ); + } +} diff --git a/lib/views/widgets/buttons/app_icon_button2.dart b/lib/views/widgets/buttons/app_icon_button2.dart new file mode 100644 index 0000000..1a9bc78 --- /dev/null +++ b/lib/views/widgets/buttons/app_icon_button2.dart @@ -0,0 +1,32 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +class AIconButton2 extends StatelessWidget { + + final IconData iconData; + final Color color; + final VoidCallback onPressed; + + const AIconButton2({ + Key key, + this.iconData, + this.onPressed, + this.color, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: IconButton( + highlightColor: color?.withOpacity(.5) ?? Theme.of(context).colorScheme.secondary.withOpacity(.5), + color: color ?? Theme.of(context).colorScheme.secondary, + icon: FaIcon( + iconData, + size: 24 * AppStyle.getScaleFactor(context), + ), + onPressed: onPressed, + ), + ); + } +} diff --git a/lib/views/widgets/buttons/app_outlined_button.dart b/lib/views/widgets/buttons/app_outlined_button.dart new file mode 100644 index 0000000..7497d39 --- /dev/null +++ b/lib/views/widgets/buttons/app_outlined_button.dart @@ -0,0 +1,50 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +class AOutLinedButton extends StatelessWidget { + final String text; + final Color color; + final EdgeInsets padding; + final TextStyle textStyle; + final VoidCallback onPressed; + + const AOutLinedButton({ + Key key, + this.color = AColors.primaryColor, + this.text, + this.padding, + this.onPressed, + this.textStyle + }) : super(key: key); + @override + Widget build(BuildContext context) { + return OutlinedButton( + style: ElevatedButton.styleFrom( + padding: padding ?? EdgeInsets.symmetric(vertical: 12), + textStyle: textStyle + ?? Theme.of(context).textTheme.subtitle2.copyWith( + fontSize: 18 + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ) + ), + ), + onPressed: onPressed, + child: Row( + children: [ + Expanded( + child: Text( + text??"", + + textAlign: TextAlign.center, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ], + ) + ); + } +} diff --git a/lib/views/widgets/buttons/app_small_button.dart b/lib/views/widgets/buttons/app_small_button.dart new file mode 100644 index 0000000..b2f7162 --- /dev/null +++ b/lib/views/widgets/buttons/app_small_button.dart @@ -0,0 +1,33 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +class ASmallButton extends StatelessWidget { + final String text; + final TextStyle style; + final Color color; + final EdgeInsets padding; + final VoidCallback onPressed; + + const ASmallButton({Key key, this.text, this.style ,this.onPressed,this.padding, this.color}) : super(key: key); + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + padding: padding, + primary: color ?? Theme.of(context).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8) + ), + ), + child: Text( + text??"", + style: style ?? Theme.of(context).textTheme.bodyText1.copyWith( + color: color == Colors.white + ? Theme.of(context).primaryColor : Colors.white + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + onPressed: onPressed + ); + } +} diff --git a/lib/views/widgets/custom_clip_path/carve_in_image.dart b/lib/views/widgets/custom_clip_path/carve_in_image.dart new file mode 100644 index 0000000..a9f72b0 --- /dev/null +++ b/lib/views/widgets/custom_clip_path/carve_in_image.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class CarveInImage extends CustomClipper { + final double gab; + var radius=10.0; + + CarveInImage(this.gab); + + @override + Path getClip(Size size) { + Path path = Path(); + path.lineTo(0, size.height); + path.lineTo(size.width/2 - gab / 2, size.height); + path.arcToPoint(Offset(size.width/2 + gab / 2, size.height), + radius: Radius.circular(gab/2)); + path.lineTo(size.width, size.height); + path.lineTo(size.width, 0); + path.lineTo(0, 0); + return path; + } + @override + bool shouldReclip(CustomClipper oldClipper) => true; +} \ No newline at end of file diff --git a/lib/views/widgets/date_and_time/date_picker.dart b/lib/views/widgets/date_and_time/date_picker.dart new file mode 100644 index 0000000..4ce1dd9 --- /dev/null +++ b/lib/views/widgets/date_and_time/date_picker.dart @@ -0,0 +1,42 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +class ADatePicker extends StatelessWidget { + final DateTime date; + final DateTime from; + final DateTime to; + final Function(DateTime) onDatePicker; + + const ADatePicker({Key key, this.date, this.onDatePicker, this.from, this.to}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 12 * AppStyle.getScaleFactor(context) + ) + ), + primary: AColors.white, + onPrimary: AColors.primaryColor, + ), + child: Text( + date == null ? "Pick Date" : + date.toString().split(" ").first, + style: Theme.of(context).textTheme.subtitle2, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + onPressed: () async { + DateTime picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: from ?? DateTime.now(), + lastDate: to ?? DateTime.now().add(Duration(days: 365)) + ); + onDatePicker(picked); + }, + ); + } +} diff --git a/lib/views/widgets/date_and_time/from_to_date_bar.dart b/lib/views/widgets/date_and_time/from_to_date_bar.dart new file mode 100644 index 0000000..422dcbf --- /dev/null +++ b/lib/views/widgets/date_and_time/from_to_date_bar.dart @@ -0,0 +1,86 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +import 'date_picker.dart'; +class FromToDateBar extends StatefulWidget { + final DateTime from; + final DateTime to; + final Function(DateTime) onPickFrom; + final Function(DateTime) onPickTo; + + const FromToDateBar({Key key, this.from, this.to, this.onPickFrom, this.onPickTo}) : super(key: key); + @override + _FromToDateBarState createState() => _FromToDateBarState(); +} + +class _FromToDateBarState extends State { + DateTime _from; + DateTime _to; + + @override + void initState() { + _from = widget.from; + _to = widget.to; + super.initState(); + } + + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _subtitle.from, + style: Theme.of(context).textTheme.bodyText1.copyWith( + fontSize: 12, + fontWeight: FontWeight.normal + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ADatePicker( + date: _from, + from: DateTime(1950), + onDatePicker: (date){ + _from = date; + setState(() {}); + widget.onPickFrom(date); + }, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _subtitle.to, + style: Theme.of(context).textTheme.bodyText1.copyWith( + fontSize: 12, + fontWeight: FontWeight.normal + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ADatePicker( + date: _to, + from: DateTime(1950), + onDatePicker: (date){ + _to = date; + setState(() {}); + widget.onPickTo(date); + }, + ), + ], + ), + SizedBox.shrink(), + SizedBox.shrink(), + SizedBox.shrink(), + ], + ); + } +} diff --git a/lib/views/widgets/departments/department_button.dart b/lib/views/widgets/departments/department_button.dart new file mode 100644 index 0000000..811f089 --- /dev/null +++ b/lib/views/widgets/departments/department_button.dart @@ -0,0 +1,64 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/department.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/departments/single_department_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +class DepartmentButton extends StatelessWidget { + final Function(Department) onDepartmentPick; + final Department department; + + const DepartmentButton({ + Key key, + this.department, + this.onDepartmentPick + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + side: BorderSide( + color: AColors.black + ) + ), + foregroundColor: AColors.primaryColor, + backgroundColor: AColors.white, + ), + child: Row( + children: [ + FaIcon( + FontAwesomeIcons.hospitalUser, + size: 28, + color: AColors.black, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Text( + department?.name ?? _subtitle.pickUnite, + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + textDirection: TextDirection.rtl, + textAlign: TextAlign.center, + ), + ), + ), + + ], + ), + onPressed: () async { + Department _department = await Navigator.of(context).pushNamed(SingleDepartmentPicker.id) as Department; + onDepartmentPick(_department); + } + ); + } +} diff --git a/lib/views/widgets/departments/department_item.dart b/lib/views/widgets/departments/department_item.dart new file mode 100644 index 0000000..fd37b41 --- /dev/null +++ b/lib/views/widgets/departments/department_item.dart @@ -0,0 +1,39 @@ +import 'package:test_sa/models/department.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class DepartmentItem extends StatelessWidget { + final Department department; + final Function(Department) onPressed; + + const DepartmentItem({Key key, this.department, this.onPressed}) : super(key: key); + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 6), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AColors.primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + ), + ), + onPressed: (){ + onPressed(department); + }, + child: ListTile( + title: Text( + department.name ?? "", + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: AColors.white + ), + textDirection: TextDirection.rtl, + ), + + ), + ), + ); + } +} diff --git a/lib/views/widgets/departments/single_department_picker.dart b/lib/views/widgets/departments/single_department_picker.dart new file mode 100644 index 0000000..fb07b39 --- /dev/null +++ b/lib/views/widgets/departments/single_department_picker.dart @@ -0,0 +1,88 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/departments_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/department.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/widgets/departments/department_item.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/loaders/no_item_found.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../app_text_form_field.dart'; +class SingleDepartmentPicker extends StatefulWidget { + static final String id = "/single-Department-Picker"; + @override + _SingleDepartmentPickerState createState() => _SingleDepartmentPickerState(); +} + +class _SingleDepartmentPickerState extends State { + DepartmentsProvider _departmentsProvider; + SettingProvider _settingProvider; + List _searchableList = []; + bool _firstTime = true; + @override + Widget build(BuildContext context) { + _departmentsProvider = Provider.of(context); + _settingProvider = Provider.of(context); + Subtitle _subtitle = AppLocalization.of(context).subtitle; + if(_firstTime && _departmentsProvider.departments != null){ + _searchableList.addAll(_departmentsProvider.departments); + _firstTime = false; + } + + return Scaffold( + resizeToAvoidBottomInset: false, + body: LoadingManager( + isLoading: _departmentsProvider.isLoading, + stateCode: _departmentsProvider.stateCode, + isFailedLoading: _departmentsProvider.departments == null, + onRefresh: () async { + _departmentsProvider.reset(); + await _departmentsProvider.getDepartment( + _settingProvider.host, + ); + }, + child: Column( + children: [ + SizedBox(height: 48,), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8,horizontal: 16), + child: ATextFormField( + hintText: _subtitle.searchByName, + style: Theme.of(context).textTheme.headline6, + suffixIcon: Icon(Icons.search_rounded), + onChange: (value){ + _searchableList.clear(); + _searchableList.addAll(_departmentsProvider.departments.where( + (element) => element.name.toLowerCase().contains( + value.toLowerCase() + ) + ).toList()); + setState(() {}); + }, + ), + ), + Expanded( + child: _searchableList.isEmpty ? + NoItemFound(message: _subtitle.noUniteFound,): + ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: _searchableList.length, + itemBuilder: (listContext,itemIndex){ + return DepartmentItem( + department: _searchableList[itemIndex], + onPressed: (hospital){ + Navigator.of(context).pop(hospital); + }, + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/device_trancfer/device_transfer_info_section.dart b/lib/views/widgets/device_trancfer/device_transfer_info_section.dart new file mode 100644 index 0000000..a042d72 --- /dev/null +++ b/lib/views/widgets/device_trancfer/device_transfer_info_section.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/device/device_transfer_info.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:test_sa/views/widgets/requests/info_row.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; +class DeviceTransferInfoSection extends StatelessWidget { + final DeviceTransferInfo info; + final VoidCallback onEdit; + const DeviceTransferInfoSection({Key key, this.info, this.onEdit}) : super(key: key); + + @override + Widget build(BuildContext context) { + final subtitle = AppLocalization.of(context).subtitle; + return Column( + children: [ + RequestInfoRow( + title: subtitle.hospital, + info: info.client.name, + ), + RequestInfoRow( + title: subtitle.unite, + info: info.department.name, + ), + RequestInfoRow( + title: subtitle.engineerName, + info: info.name, + ), + RequestInfoRow( + title: subtitle.workingHours, + info: info.workingHours, + ), + RequestInfoRow( + title: subtitle.travelingHours, + info: info.travelingHours, + ), + RequestInfoRow( + title: "Comment", + info: info.comment, + ), + RequestInfoRow( + title: "Signature", + info: info.signature?.isEmpty != false + ? subtitle.noDateFound : null, + contentWidget: info.signature?.isEmpty != false ? null : + ImageLoader( + url: info.signature, + ), + ), + RequestInfoRow( + title: subtitle.status, + infoWidget: StatusLabel( + label: info.status?.label, + color: AColors.getGasStatusColor(info.status?.id) + ), + ), + ], + ); + } +} diff --git a/lib/views/widgets/device_trancfer/device_transfer_item.dart b/lib/views/widgets/device_trancfer/device_transfer_item.dart new file mode 100644 index 0000000..0cd2adb --- /dev/null +++ b/lib/views/widgets/device_trancfer/device_transfer_item.dart @@ -0,0 +1,159 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/models/device/device_transfer.dart'; +import 'package:test_sa/models/enums/user_types.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_model.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/pages/user/requests/report/create_service_report.dart'; +import 'package:test_sa/views/pages/user/requests/report/future_service_report.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +class DeviceTransferItem extends StatelessWidget { + final int index; + final DeviceTransfer item; + final Function(DeviceTransfer) onPressed; + const DeviceTransferItem({Key key, this.item, this.onPressed, this.index}) : super(key: key); + + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + User _user = Provider.of(context,listen: false).user; + Color itemColor = index % 2 == 0 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onPrimary; + Color onItemColor = index % 2 != 0 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onPrimary; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + backgroundColor: itemColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + ), + ), + //padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + onPressed: (){ + onPressed(item); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title ?? "-----", + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 16, + fontWeight: FontWeight.bold + ), + ), + Divider(color: onItemColor,), + Row( + children: [ + Expanded( + child: Text( + _subtitle.from, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + ), + ), + ), + StatusLabel( + color: AColors.getRequestStatusColor(item.sender.status?.id), + label: item.sender.status?.label, + ) + + ], + ), + //const SizedBox(height: 8,), + Row( + children: [ + Expanded( + child: Text( + item.sender.client.name, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + ), + ), + ), + + + ], + ), + Text( + item.sender.department.name, + style: Theme.of(context).textTheme.bodySmall.copyWith( + color: onItemColor, + ), + ), + Divider(color: onItemColor,), + Row( + children: [ + Expanded( + child: Text( + _subtitle.to, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + ), + ), + ), + StatusLabel( + color: AColors.getRequestStatusColor(item.receiver.status?.id), + label: item.receiver.status?.label, + ) + + ], + ), + //const SizedBox(height: 8,), + Row( + children: [ + Expanded( + child: Text( + item.receiver.client.name, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + ), + ), + ), + + + ], + ), + Text( + item.receiver.department.name, + style: Theme.of(context).textTheme.bodySmall.copyWith( + color: onItemColor, + ), + ), + //Divider(color: onItemColor,), + ], + ), + ), + + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/device_trancfer/device_transfer_list.dart b/lib/views/widgets/device_trancfer/device_transfer_list.dart new file mode 100644 index 0000000..ede0bc6 --- /dev/null +++ b/lib/views/widgets/device_trancfer/device_transfer_list.dart @@ -0,0 +1,52 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/device/device_transfer.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_model.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/pages/device_transfer/device_transfer_details.dart'; +import 'package:test_sa/views/pages/user/gas_refill/gas_refill_details.dart'; +import 'package:test_sa/views/pages/user/requests/request_details.dart'; +import 'package:test_sa/views/widgets/device_trancfer/device_transfer_item.dart'; +import 'package:test_sa/views/widgets/gas_refill/gas_refill_item.dart'; +import 'package:test_sa/views/widgets/loaders/lazy_loading.dart'; +import 'package:test_sa/views/widgets/loaders/no_item_found.dart'; +import 'package:test_sa/views/widgets/requests/service_request_item.dart'; +import 'package:flutter/material.dart'; +class DeviceTransferList extends StatelessWidget { + final List items; + final bool nextPage; + final Future Function() onLazyLoad; + + const DeviceTransferList({Key key, this.items, this.nextPage, this.onLazyLoad}) : super(key: key); + + @override + Widget build(BuildContext context) { + + if(items.length == 0){ + Subtitle subtitle = AppLocalization.of(context).subtitle; + return NoItemFound(message: subtitle.noServiceRequestFound,); + } + return LazyLoading( + nextPage: nextPage, + onLazyLoad: onLazyLoad, + child: ListView.builder( + //physics: const BouncingScrollPhysics(), + itemCount: items.length, + padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 8), + itemBuilder: (context,itemIndex){ + return DeviceTransferItem( + index: itemIndex, + item: items[itemIndex], + onPressed: (model){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_)=> DeviceTransferDetails(model: model,) + ) + ); + }, + ); + } + ), + ); + } +} diff --git a/lib/views/widgets/dialogs/dialog.dart b/lib/views/widgets/dialogs/dialog.dart new file mode 100644 index 0000000..fa20a8f --- /dev/null +++ b/lib/views/widgets/dialogs/dialog.dart @@ -0,0 +1,37 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +class AAlertDialog extends StatelessWidget { + final String title; + final String content; + + const AAlertDialog({Key key, this.title, this.content}) : super(key: key); + + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return CupertinoAlertDialog( + title: title != null + ? Text(title) + : null, + content: content != null + ? Text(content) + : null, + actions: [ + TextButton( + child: Text(_subtitle.confirm), + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + TextButton( + child: Text(_subtitle.cancel), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + ], + ); + } +} diff --git a/lib/views/widgets/drawer/drawer_item.dart b/lib/views/widgets/drawer/drawer_item.dart new file mode 100644 index 0000000..88f027d --- /dev/null +++ b/lib/views/widgets/drawer/drawer_item.dart @@ -0,0 +1,49 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class DrawerItem extends StatelessWidget { + final String title; + final IconData icon; + final VoidCallback onPressed; + + const DrawerItem({Key key, this.title, this.icon, this.onPressed}) : super(key: key); + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ) + ), + primary: Theme.of(context).colorScheme.onPrimary, + ), + + onPressed: onPressed, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: 48, + child: Icon( + icon, color: Theme.of(context).colorScheme.primary + ), + ), + ), + Text( + title, + style: Theme.of(context).textTheme.headline6.copyWith( + fontSize: 16, + color: Theme.of(context).colorScheme.primary + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/e_signature/e_signature.dart b/lib/views/widgets/e_signature/e_signature.dart new file mode 100644 index 0000000..72f9d65 --- /dev/null +++ b/lib/views/widgets/e_signature/e_signature.dart @@ -0,0 +1,118 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:signature/signature.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; + +class ESignature extends StatefulWidget { + final String oldSignature; + final Uint8List newSignature; + final Function(Uint8List) onSaved; + const ESignature({Key key, this.oldSignature, this.onSaved, this.newSignature}) : super(key: key); + + @override + State createState() => _ESignatureState(); +} + +class _ESignatureState extends State { + // Initialise a controller. It will contains signature points, stroke width and pen color. + final SignatureController _controller = SignatureController( + penStrokeWidth: 2, + penColor: Colors.black, + exportBackgroundColor: Colors.white, + ); + + Uint8List signature; + + bool _unpaint = false; + @override + void initState() { + if(widget.newSignature != null) { + signature = widget.newSignature; + } + // TODO: implement initState + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + if(widget.oldSignature != null || signature != null) + Container( + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.only(bottom: 8), + height: 90 * AppStyle.getScaleFactor(context), + child: signature != null ? + Image.memory(signature): + ImageLoader( + boxFit: BoxFit.contain, + url: widget.oldSignature) + ), + FormField( + onSaved: (_) async { + widget.onSaved(signature); + }, + builder: (FormFieldState state) { + return Column( + children: [ + Container( + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.symmetric( + horizontal: 16 + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color:AColors.black), + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + boxShadow: const [ + AppStyle.boxShadow + ] + ), + child: AbsorbPointer( + absorbing: _unpaint, + child: Signature( + controller: _controller, + height: 160 * AppStyle.getScaleFactor(context), + backgroundColor: Colors.transparent, + ), + ), + ), + Row( + children: [ + IconButton(onPressed: (){_controller.clear();}, icon: const Icon(Icons.clear)), + IconButton(onPressed: (){_controller.undo();}, icon: const Icon(Icons.undo)), + IconButton(onPressed: (){_controller.redo();}, icon: const Icon(Icons.redo)), + + IconButton(onPressed: (){ + _unpaint = !_unpaint; + setState(() {}); + }, icon: Icon( + _unpaint ? Icons.draw : Icons.ac_unit, + color: _unpaint ? AColors.orange : null,)), + const Spacer(), + IconButton(onPressed: () async { + signature = await _controller.toPngBytes(); + setState(() {}); + }, icon: const Icon(Icons.check)), + ], + ) + ], + ); + } + ), + ], + ); + } +} diff --git a/lib/views/widgets/equipment/auto_complete_devices_field.dart b/lib/views/widgets/equipment/auto_complete_devices_field.dart new file mode 100644 index 0000000..c9e1187 --- /dev/null +++ b/lib/views/widgets/equipment/auto_complete_devices_field.dart @@ -0,0 +1,107 @@ +import 'package:test_sa/controllers/providers/api/devices_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:provider/provider.dart'; +class AutoCompleteDeviceField extends StatefulWidget { + final Device initialValue; + final String hospitalId; + final Function(String) onPick; + + const AutoCompleteDeviceField({Key key, this.initialValue, this.onPick, this.hospitalId}) : super(key: key); + + @override + _AutoCompleteDeviceFieldState createState() => _AutoCompleteDeviceFieldState(); +} + +class _AutoCompleteDeviceFieldState extends State { + + SettingProvider _settingProvider; + DevicesProvider _devicesProvider; + UserProvider _userProvider; + TextEditingController _controller; + + @override + void initState() { + _controller = TextEditingController(text: widget.initialValue.serialNumber); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + @override + Widget build(BuildContext context) { + _settingProvider = Provider.of(context); + _userProvider = Provider.of(context); + _devicesProvider = Provider.of(context); + //Subtitle _subtitle = AppLocalization.of(context).subtitle; + return LoadingManager( + isLoading: _devicesProvider.isLoading, + isFailedLoading: _devicesProvider.devices == null, + stateCode: _devicesProvider.stateCode, + onRefresh: () async { + _devicesProvider.reset(); + await _devicesProvider.getEquipment( + host: _settingProvider.host, + user: _userProvider.user, + hospitalId: widget.hospitalId + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 16 + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color:AColors.black), + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: TypeAheadField( + textFieldConfiguration: TextFieldConfiguration( + style: Theme.of(context).textTheme.headline6, + controller: _controller, + textAlign: TextAlign.center, + decoration: InputDecoration( + border: InputBorder.none, + disabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + textInputAction: TextInputAction.search, + ), + suggestionsCallback: (vale) async { + return await _devicesProvider.getDevicesList( + host: _settingProvider.host, + user: _userProvider.user, + hospitalId: widget.hospitalId, + title: vale, + ); + }, + itemBuilder: (context, device) { + return ListTile( + title: Text(device.serialNumber), + subtitle: Text(device.model+"/"+device.brand), + ); + }, + onSuggestionSelected: (device) { + _controller.text = device.serialNumber; + widget.onPick(device.id); + }, + ), + ), + ); + } +} diff --git a/lib/views/widgets/equipment/device_button.dart b/lib/views/widgets/equipment/device_button.dart new file mode 100644 index 0000000..78617fc --- /dev/null +++ b/lib/views/widgets/equipment/device_button.dart @@ -0,0 +1,86 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/equipment/single_device_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +class DeviceButton extends StatelessWidget { + final Function(Device) onDevicePick; + final Device device; + + const DeviceButton({ + Key key, + this.device, + this.onDevicePick + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 16,vertical: device == null ? 12 : 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + side: BorderSide( + color: AColors.black + ) + ), + onPrimary: AColors.primaryColor, + primary: AColors.white, + ), + + child: Row( + children: [ + FaIcon( + FontAwesomeIcons.hardDrive, + size: 28, + color: AColors.black, + ), + device == null ? + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Text( + _subtitle.pickDevice, + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + textDirection: TextDirection.rtl, + textAlign: TextAlign.center, + ), + ), + ): + Expanded( + child: ListTile( + title: Text("${_subtitle.sn} : " + device.serialNumber, + style: Theme.of(context).textTheme.subtitle1, + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Divider(color: Theme.of(context).textTheme.subtitle1.color,), + Text("${_subtitle.brand} : " + device.brand, + style: Theme.of(context).textTheme.subtitle2, + ), + Divider(color: Theme.of(context).textTheme.subtitle1.color,), + Text("${_subtitle.model} : " + device.model, + style: Theme.of(context).textTheme.subtitle2, + ), + ], + ), + ) + ) + + ], + ), + onPressed: () async { + Device _device = await Navigator.of(context).pushNamed(SingleDevicePicker.id) as Device; + onDevicePick(_device); + } + ); + } +} diff --git a/lib/views/widgets/equipment/device_item.dart b/lib/views/widgets/equipment/device_item.dart new file mode 100644 index 0000000..aaa3112 --- /dev/null +++ b/lib/views/widgets/equipment/device_item.dart @@ -0,0 +1,58 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class DeviceItem extends StatelessWidget { + final Device device; + final Function(Device) onPressed; + + const DeviceItem({Key key, this.device, this.onPressed}) : super(key: key); + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 6), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + primary: AColors.primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + ), + ), + + onPressed: (){ + onPressed(device); + }, + child: ListTile( + title: Text("${_subtitle.sn} : " + device.serialNumber, + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white + ), + + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Divider(color: Theme.of(context).scaffoldBackgroundColor,), + Text("${_subtitle.brand} : " + device.brand, + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: AColors.white + ), + ), + Divider(color: Theme.of(context).scaffoldBackgroundColor,), + Text("${_subtitle.model} : " + device.model, + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: AColors.white + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/equipment/single_device_picker.dart b/lib/views/widgets/equipment/single_device_picker.dart new file mode 100644 index 0000000..9ecd0bb --- /dev/null +++ b/lib/views/widgets/equipment/single_device_picker.dart @@ -0,0 +1,141 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/devices_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/device/device.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/widgets/equipment/device_item.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/loaders/no_item_found.dart'; +import 'package:test_sa/views/widgets/qr/scan_qr.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; + +import '../app_text_form_field.dart'; +class SingleDevicePicker extends StatefulWidget { + static final String id = "/single-device-Picker"; + final bool sandraChoice = true; + @override + _SingleDevicePickerState createState() => _SingleDevicePickerState(); +} + +class _SingleDevicePickerState extends State { + DevicesProvider _devicesProvider; + UserProvider _userProvider; + SettingProvider _settingProvider; + List _searchableList = []; + bool _firstTime = true; + Subtitle _subtitle; + + _getDevice(String result) async { + if(result == null) return; + showDialog( + barrierDismissible: false, + context: context, + builder: (dialogContext){ + return const Center(child: CircularProgressIndicator()); + } + ); + List devices = await _devicesProvider.getDevicesListBySN( + host: _settingProvider.host, + user: _userProvider.user, + hospitalId: _userProvider.user.hospital.id, + sn: result + ); + Navigator.of(context).pop(); + if(devices.isEmpty){ + Fluttertoast.showToast(msg: _subtitle.noDeviceFound); + return; + } + Navigator.of(context).pop(devices.first); + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _devicesProvider = Provider.of(context); + _userProvider = Provider.of(context); + _settingProvider = Provider.of(context); + + if(_firstTime && _devicesProvider.devices != null){ + _searchableList.addAll(_devicesProvider.devices); + _firstTime = false; + } + _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + resizeToAvoidBottomInset: false, + body: LoadingManager( + isLoading: _devicesProvider.isLoading, + stateCode: _devicesProvider.stateCode, + isFailedLoading: _devicesProvider.devices == null, + onRefresh: () async { + _devicesProvider.reset(); + await _devicesProvider.getEquipment( + user: _userProvider.user, + host: _settingProvider.host, + hospitalId: _userProvider.user.hospital.id + ); + }, + child: Column( + children: [ + SizedBox(height: 48,), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8,horizontal: 16), + child: ATextFormField( + hintText: _subtitle.searchBySn, + style: Theme.of(context).textTheme.headline6, + suffixIcon: const Icon(Icons.search_rounded), + onChange: (value){ + _searchableList.clear(); + _searchableList.addAll(_devicesProvider.devices.where( + (element) => element.serialNumber.toLowerCase().contains( + value.toLowerCase() + ) + ).toList()); + setState(() {}); + }, + ), + ), + Expanded( + child: _searchableList.isEmpty ? + NoItemFound(message: _subtitle.noDeviceFound,): + ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: _searchableList.length, + itemBuilder: (listContext,itemIndex){ + return DeviceItem( + device: _searchableList[itemIndex], + onPressed: (device){ + Navigator.of(context).pop(device); + }, + ); + }, + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + heroTag: "some tag 2", + child: const Icon(Icons.qr_code_scanner), + onPressed: () async { + String result = await Navigator.of(context).push( + MaterialPageRoute(builder: (_)=> const ScanQr()), + ) as String; + _getDevice(result); + }, + ), + ); + } +} diff --git a/lib/views/widgets/gas_refill/gas_refill_create_details_item.dart b/lib/views/widgets/gas_refill/gas_refill_create_details_item.dart new file mode 100644 index 0000000..d246d05 --- /dev/null +++ b/lib/views/widgets/gas_refill/gas_refill_create_details_item.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_details.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +class GasRefillCreateDetailsItem extends StatelessWidget { + + final GasRefillDetails model; + final VoidCallback onDelete; + + const GasRefillCreateDetailsItem({Key key, this.model, this.onDelete}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: Text(model.type.label)), + IconButton( + onPressed: onDelete, + color: AColors.red, + icon: const Icon(Icons.delete) + ) + ], + ), + Row( + children: [ + Text(model.requestedQuantity.toStringAsFixed(0)), + ], + ), + if(model.deliveredQuantity != null) + Row( + children: [ + const Text("Delivered Quantity"), + Text(model.deliveredQuantity.toStringAsFixed(0)), + ], + ), + const Divider(), + ], + ); + } +} diff --git a/lib/views/widgets/gas_refill/gas_refill_item.dart b/lib/views/widgets/gas_refill/gas_refill_item.dart new file mode 100644 index 0000000..e9bf3d0 --- /dev/null +++ b/lib/views/widgets/gas_refill/gas_refill_item.dart @@ -0,0 +1,116 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/models/enums/user_types.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_model.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/pages/user/requests/report/create_service_report.dart'; +import 'package:test_sa/views/pages/user/requests/report/future_service_report.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +class GasRefillItem extends StatelessWidget { + final int index; + final GasRefillModel item; + final Function(GasRefillModel) onPressed; + const GasRefillItem({Key key, this.item, this.onPressed, this.index}) : super(key: key); + + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + User _user = Provider.of(context,listen: false).user; + Color itemColor = index % 2 == 0 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onPrimary; + Color onItemColor = index % 2 != 0 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onPrimary; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + backgroundColor: itemColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + ), + ), + //padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + onPressed: (){ + onPressed(item); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title ?? "-----", + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 16, + fontWeight: FontWeight.bold + ), + ), + Row( + children: [ + Expanded( + child: Text( + _subtitle.hospital, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + ), + ), + ), + Text( + item.clientName, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + ), + ), + + ], + ), + Divider(color: onItemColor,), + Row( + children: [ + Expanded( + child: Text( + _subtitle.status, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + ), + ), + ), + StatusLabel(label: item.status.label, + color: AColors.getGasStatusColor(item.status.id) + ), + ], + ), + //Divider(color: onItemColor,), + + ], + ), + ), + + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/gas_refill/gas_refill_list.dart b/lib/views/widgets/gas_refill/gas_refill_list.dart new file mode 100644 index 0000000..b54c2ee --- /dev/null +++ b/lib/views/widgets/gas_refill/gas_refill_list.dart @@ -0,0 +1,49 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_model.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/pages/user/gas_refill/gas_refill_details.dart'; +import 'package:test_sa/views/pages/user/requests/request_details.dart'; +import 'package:test_sa/views/widgets/gas_refill/gas_refill_item.dart'; +import 'package:test_sa/views/widgets/loaders/lazy_loading.dart'; +import 'package:test_sa/views/widgets/loaders/no_item_found.dart'; +import 'package:test_sa/views/widgets/requests/service_request_item.dart'; +import 'package:flutter/material.dart'; +class GasRefillList extends StatelessWidget { + final List items; + final bool nextPage; + final Future Function() onLazyLoad; + + const GasRefillList({Key key, this.items, this.nextPage, this.onLazyLoad}) : super(key: key); + + @override + Widget build(BuildContext context) { + + if(items.length == 0){ + Subtitle subtitle = AppLocalization.of(context).subtitle; + return NoItemFound(message: subtitle.noServiceRequestFound,); + } + return LazyLoading( + nextPage: nextPage, + onLazyLoad: onLazyLoad, + child: ListView.builder( + //physics: const BouncingScrollPhysics(), + itemCount: items.length, + padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 8), + itemBuilder: (context,itemIndex){ + return GasRefillItem( + index: itemIndex, + item: items[itemIndex], + onPressed: (model){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_)=> GasRefillDetails(model: model,) + ) + ); + }, + ); + } + ), + ); + } +} diff --git a/lib/views/widgets/gas_refill/gas_refill_update_details_item.dart b/lib/views/widgets/gas_refill/gas_refill_update_details_item.dart new file mode 100644 index 0000000..2a94a9b --- /dev/null +++ b/lib/views/widgets/gas_refill/gas_refill_update_details_item.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/validator/validator.dart'; +import 'package:test_sa/models/gas_refill/gas_refill_details.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/requests/info_row.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; +import 'package:test_sa/views/widgets/titles/app_title.dart'; +class GasRefillUpdateDetailsItem extends StatelessWidget { + + final GasRefillDetails details; + final bool enableEdit; + final bool validate; + + const GasRefillUpdateDetailsItem({Key key, this.details, this.enableEdit, this.validate}) : super(key: key); + + @override + Widget build(BuildContext context) { + Subtitle subtitle = AppLocalization.of(context).subtitle; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ATitle(details.type.label), + RequestInfoRow( + title: "Cylinder Size", + info: details.cylinderSize.label, + ), + RequestInfoRow( + title: "Requested Quantity", + info: details.requestedQuantity.toStringAsFixed(0), + ), + enableEdit ? + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASubTitle(subtitle.quantity), + if(validate && details.deliveredQuantity == null) + ASubTitle(subtitle.requiredWord,color: Colors.red,), + SizedBox(height: 4,), + ATextFormField( + initialValue: (details.deliveredQuantity ?? "").toString(), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle1, + validator: (value) => + Validator.isNumeric(value) + ? null : "allow numbers only", + textInputType: TextInputType.number, + onSaved: (value){ + details.deliveredQuantity = int.tryParse(value); + }, + ), + ], + ): + RequestInfoRow( + title: "Delivered Quantity", + info: details.deliveredQuantity.toStringAsFixed(0), + ), + //SizedBox(height: 16,) + ], + ); + } +} diff --git a/lib/views/widgets/hospitals/auto_complete_field.dart b/lib/views/widgets/hospitals/auto_complete_field.dart new file mode 100644 index 0000000..6c51d23 --- /dev/null +++ b/lib/views/widgets/hospitals/auto_complete_field.dart @@ -0,0 +1,94 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/hospitals_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/hospital.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/hospitals/hospital_item.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:provider/provider.dart'; +class AutoCompleteField extends StatefulWidget { + final String initialValue; + final Function(String) onSearch; + final Function(String) onSave; + + const AutoCompleteField({Key key, this.onSearch, this.initialValue, this.onSave}) : super(key: key); + + @override + _AutoCompleteFieldState createState() => _AutoCompleteFieldState(); +} + +class _AutoCompleteFieldState extends State { + + SettingProvider _settingProvider; + TextEditingController _controller; + + @override + void initState() { + _controller = TextEditingController(text: widget.initialValue); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + @override + Widget build(BuildContext context) { + _settingProvider = Provider.of(context); + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Container( + padding: EdgeInsets.symmetric( + horizontal: 16 + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color:AColors.black), + + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: TypeAheadField( + + textFieldConfiguration: TextFieldConfiguration( + style: Theme.of(context).textTheme.headline6, + onSubmitted: widget.onSave, + controller: _controller, + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: _subtitle.hospital, + border: InputBorder.none, + disabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + textInputAction: TextInputAction.search, + onEditingComplete:(){ + widget.onSearch(_controller.text); + } + ), + suggestionsCallback: (vale) async { + return await HospitalsProvider().getHospitalsList( + host: _settingProvider.host, + title: vale + ); + }, + itemBuilder: (context, hospital) { + return HospitalItem( + hospital: hospital, + ); + }, + onSuggestionSelected: (hospital) { + widget.onSearch(hospital.name); + }, + ), + ); + } +} diff --git a/lib/views/widgets/hospitals/hospital_button.dart b/lib/views/widgets/hospitals/hospital_button.dart new file mode 100644 index 0000000..81c7d9e --- /dev/null +++ b/lib/views/widgets/hospitals/hospital_button.dart @@ -0,0 +1,65 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/hospital.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/hospitals/single_hospital_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +class HospitalButton extends StatelessWidget { + final Function(Hospital) onHospitalPick; + final Hospital hospital; + + const HospitalButton({ + Key key, + this.hospital, + this.onHospitalPick + }) : super(key: key); + + @override + Widget build(BuildContext context) { + + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: AColors.primaryColor, + backgroundColor: AColors.white, + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + side: const BorderSide( + color: AColors.black + ) + ), + ), + child: Row( + children: [ + const FaIcon( + FontAwesomeIcons.solidHospital, + size: 28, + color: AColors.black, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Text( + hospital?.name ?? _subtitle.pickHospital, + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + textDirection: TextDirection.rtl, + textAlign: TextAlign.center, + ), + ), + ), + + ], + ), + onPressed: () async { + Hospital _hospital = await Navigator.of(context).pushNamed(SingleHospitalPicker.id) as Hospital; + onHospitalPick(_hospital); + } + ); + } +} diff --git a/lib/views/widgets/hospitals/hospital_item.dart b/lib/views/widgets/hospitals/hospital_item.dart new file mode 100644 index 0000000..523dace --- /dev/null +++ b/lib/views/widgets/hospitals/hospital_item.dart @@ -0,0 +1,40 @@ +import 'package:test_sa/models/hospital.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class HospitalItem extends StatelessWidget { + final Hospital hospital; + final Function(Hospital) onPressed; + + const HospitalItem({Key key, this.hospital, this.onPressed}) : super(key: key); + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 6), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AColors.primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + ), + ), + + onPressed: onPressed == null ? null : (){ + onPressed(hospital); + }, + child: ListTile( + title: Text( + hospital.name ?? "", + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: AColors.white + ), + textDirection: TextDirection.rtl, + ), + + ), + ), + ); + } +} diff --git a/lib/views/widgets/hospitals/single_hospital_picker.dart b/lib/views/widgets/hospitals/single_hospital_picker.dart new file mode 100644 index 0000000..f2ee3c7 --- /dev/null +++ b/lib/views/widgets/hospitals/single_hospital_picker.dart @@ -0,0 +1,86 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/hospitals_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/hospital.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/widgets/hospitals/hospital_item.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/loaders/no_item_found.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../app_text_form_field.dart'; +class SingleHospitalPicker extends StatefulWidget { + static final String id = "/single-Hospital-Picker"; + final bool sandraChoice = true; + @override + _SingleHospitalPickerState createState() => _SingleHospitalPickerState(); +} + +class _SingleHospitalPickerState extends State { + HospitalsProvider _hospitalsProvider; + SettingProvider _settingProvider; + List _searchableList = []; + bool _firstTime = true; + @override + Widget build(BuildContext context) { + _hospitalsProvider = Provider.of(context); + _settingProvider = Provider.of(context); + if(_firstTime && _hospitalsProvider.hospitals != null){ + _searchableList.addAll(_hospitalsProvider.hospitals); + _firstTime = false; + } + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Scaffold( + resizeToAvoidBottomInset: false, + body: LoadingManager( + isLoading: _hospitalsProvider.isLoading, + stateCode: _hospitalsProvider.stateCode, + isFailedLoading: _hospitalsProvider.hospitals == null, + onRefresh: () async { + _hospitalsProvider.reset(); + await _hospitalsProvider.getHospitals(host: _settingProvider.host,); + }, + child: Column( + children: [ + SizedBox(height: 48,), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8,horizontal: 16), + child: ATextFormField( + hintText: _subtitle.searchByName, + style: Theme.of(context).textTheme.headline6, + suffixIcon: Icon(Icons.search_rounded), + onChange: (value){ + _searchableList.clear(); + _searchableList.addAll(_hospitalsProvider.hospitals.where( + (element) => element.name.toLowerCase().contains( + value.toLowerCase() + ) + ).toList()); + setState(() {}); + }, + ), + ), + Expanded( + child: _searchableList.isEmpty ? + NoItemFound(message: _subtitle.noHospitalFound,): + ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: _searchableList.length, + itemBuilder: (listContext,itemIndex){ + return HospitalItem( + hospital: _searchableList[itemIndex], + onPressed: (hospital){ + Navigator.of(context).pop(hospital); + }, + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/images/image_item.dart b/lib/views/widgets/images/image_item.dart new file mode 100644 index 0000000..8478ae9 --- /dev/null +++ b/lib/views/widgets/images/image_item.dart @@ -0,0 +1,65 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +class ImageItem extends StatelessWidget { + final String url; + final bool isVideo; + final VoidCallback onPressed; + + const ImageItem({ + Key key, + this.url, + this.isVideo = false, + this.onPressed + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: 80 * AppStyle.getScaleFactor(context), + height: 40 * AppStyle.getScaleFactor(context), + margin: EdgeInsets.symmetric( + horizontal: 4 * AppStyle.getScaleFactor(context), + ), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).dividerColor, + width: 2 * AppStyle.getScaleFactor(context), + ), + borderRadius: BorderRadius.circular(8 * AppStyle.getScaleFactor(context)), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(6 * AppStyle.getScaleFactor(context)), + child: Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + ImageLoader(url: url,), + MaterialButton( + onPressed: onPressed, + padding: EdgeInsets.zero, + child: Visibility( + visible: isVideo, + child: Center( + child: Container( + decoration: BoxDecoration( + color: Colors.black45, + shape: BoxShape.circle, + ), + child: FaIcon( + FontAwesomeIcons.playCircle, + size: 32 * AppStyle.getScaleFactor(context), + color: AColors.orange, + ), + ), + ) + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/images/images_list.dart b/lib/views/widgets/images/images_list.dart new file mode 100644 index 0000000..0186834 --- /dev/null +++ b/lib/views/widgets/images/images_list.dart @@ -0,0 +1,33 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +import 'image_item.dart'; +import 'images_viewer.dart'; +class ImagesList extends StatelessWidget { + final List images; + final EdgeInsets padding; + + const ImagesList({Key key, this.images, this.padding}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: padding?? EdgeInsets.symmetric(horizontal: 32 * AppStyle.getScaleFactor(context)), + scrollDirection: Axis.horizontal, + itemCount: images.length, + itemBuilder: (context,itemIndex){ + return ImageItem( + url: images[itemIndex], + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute(builder: (_)=> ImagesViewer( + initialIndex: itemIndex, + images: images, + )) + ); + }, + ); + } + ); + } +} diff --git a/lib/views/widgets/images/images_viewer.dart b/lib/views/widgets/images/images_viewer.dart new file mode 100644 index 0000000..76c7981 --- /dev/null +++ b/lib/views/widgets/images/images_viewer.dart @@ -0,0 +1,78 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:flutter/material.dart'; + +class ImagesViewer extends StatelessWidget { + final List images; + final int initialIndex; + const ImagesViewer({ + Key key, + this.images, + this.initialIndex = 0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: DefaultTabController( + length: images.length, + initialIndex: initialIndex, + child: SafeArea( + child: Column( + children: [ + images.length == 1 ? SizedBox.shrink() : + Column( + children: [ + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + TabBar( + isScrollable: images.length * 84 > MediaQuery.of(context).size.width, + indicator: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.only( + topRight: Radius.circular(24 * AppStyle.getScaleFactor(context)), + bottomLeft: Radius.circular(24 * AppStyle.getScaleFactor(context)), + ), + ), + indicatorSize: TabBarIndicatorSize.label, + tabs: images.map( + (imagePath) => InteractiveViewer( + child: Container( + height: 60, + width: 80, + padding: EdgeInsets.all(AppStyle.getScaleFactor(context),), + child: ClipRRect( + borderRadius: BorderRadius.only( + topRight: Radius.circular(22 * AppStyle.getScaleFactor(context)), + bottomLeft: Radius.circular(22 * AppStyle.getScaleFactor(context)), + ), + child: ImageLoader( + url: imagePath, + boxFit: BoxFit.cover, + ), + ), + ), + ) + ).toList(), + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + ], + ), + Expanded( + child: TabBarView( + children: images.map( + (imagePath) => InteractiveViewer( + child: ImageLoader( + url: imagePath, + boxFit: BoxFit.contain, + ), + ) + ).toList(), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/images/mini_one_image_picker.dart b/lib/views/widgets/images/mini_one_image_picker.dart new file mode 100644 index 0000000..0f69603 --- /dev/null +++ b/lib/views/widgets/images/mini_one_image_picker.dart @@ -0,0 +1,130 @@ +import 'dart:io'; + +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; + +class AMiniOneImagePicker extends StatefulWidget { + final Function(File) onPick; + final File image; + final String label; + final bool error; + const AMiniOneImagePicker({Key key, this.label, this.error,this.image, this.onPick}) : super(key: key); + + @override + _AMiniOneImagePickerState createState() => _AMiniOneImagePickerState(); +} + +class _AMiniOneImagePickerState extends State { + File _image; + Subtitle _subtitle; + @override + void initState() { + super.initState(); + _image = widget.image; + } + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Visibility( + visible: widget.label != null, + child: Column( + children: [ + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + Text( + widget.label?? '', + style: Theme.of(context).textTheme.headline6, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ], + ), + ), + Visibility( + visible: _image == null && widget.error == true, + child: Column( + children: [ + SizedBox(height: 4,), + Text( + _subtitle.requiredImage, + style: Theme.of(context).textTheme.headline6.copyWith( + color: Colors.red + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ], + ), + ), + SizedBox(height: 8,), + Container( + width: MediaQuery.of(context).size.width, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ) + ), + primary: Colors.grey[200], + padding: _image == null ? null : EdgeInsets.zero, + ), + + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _image == null ? _subtitle.pickImage : _image.path.split("/").last, + style: Theme.of(context).textTheme.overline, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + onPressed: () async { + ImageSource source = await showDialog( + context: context, + builder: (_) => CupertinoAlertDialog( + actions: [ + TextButton( + child: Text("pick from camera"), + onPressed: () { + Navigator.of(context).pop(ImageSource.camera); + }, + ), + TextButton( + child: Text("pick from gallery"), + onPressed: () { + Navigator.of(context).pop(ImageSource.gallery); + }, + ), + ], + ) + ); + if(source == null) + return; + + final pickedFile = await ImagePicker().pickImage( + source: source, + imageQuality: 70, + maxWidth: 1000, + maxHeight: 1000 + ); + + + setState(() { + if (pickedFile != null) { + _image = File(pickedFile.path); + widget.onPick(_image); + } else { + + } + }); + }, + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart new file mode 100644 index 0000000..ae0dddb --- /dev/null +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -0,0 +1,165 @@ +import 'dart:io'; + +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_flat_button.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:image_picker/image_picker.dart'; + +import 'multi_image_picker_item.dart'; +class MultiImagesPicker extends StatefulWidget { + final String label; + final bool error; + final List images; + const MultiImagesPicker({Key key, this.images, this.label, this.error = false}) : super(key: key); + + @override + _MultiImagesPickerState createState() => _MultiImagesPickerState(); +} + +class _MultiImagesPickerState extends State + with TickerProviderStateMixin{ + Size _size; + @override + Widget build(BuildContext context) { + _size = MediaQuery.of(context).size; + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + Row( + children: [ + Expanded( + child: Text( + widget.label ?? _subtitle.images, + style: Theme.of(context).textTheme + .headline6.copyWith(fontSize: 18,), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + AFlatButton( + text: _subtitle.add, + onPressed: (){onImagePick(_subtitle);}, + ), + ], + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + AnimatedSize( + duration: Duration(milliseconds: 400), + child: !widget.error ? SizedBox.shrink() : + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _subtitle.imagesRequired, + style: Theme.of(context).textTheme.headline6.copyWith( + fontSize: 14, + color: AColors.red, + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + ], + ), + ), + AnimatedSwitcher( + duration: Duration(milliseconds: 400), + child: Container( + key: ValueKey(widget.images.length), + width: _size.width, + height: 200 * AppStyle.getScaleFactor(context), + padding: EdgeInsets.all(8 * AppStyle.getScaleFactor(context),), + alignment: Alignment.topLeft, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).primaryColor, + width: 2 + ), + borderRadius: BorderRadius.circular( + 8 * AppStyle.getScaleFactor(context)), + ), + child: widget.images.isEmpty? + MaterialButton( + onPressed: (){onImagePick(_subtitle);}, + child: Center( + child: Icon( + Icons.add_a_photo_outlined, + size: 48 * AppStyle.getScaleFactor(context), + color: Theme.of(context).primaryColor, + ) + ), + ) : + GridView.count( + crossAxisCount: 2,//_size.width ~/ 80, + scrollDirection: Axis.horizontal, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + children: List.generate( + widget.images.length, + (index) { + File _image = widget.images[index]; + return MultiImagesPickerItem( + image: _image, + onRemoveTap: (image){ + widget.images.remove(image); + setState(() {}); + }, + ); + } + ), + ), + ), + ), + ], + ); + } + onImagePick( Subtitle _subtitle) async { + if(widget.images.length >= 5){ + Fluttertoast.showToast(msg: _subtitle.maxImagesNumberIs5); + return; + } + ImageSource source = await showDialog( + context: context, + builder: (dialogContext) => CupertinoAlertDialog( + actions: [ + TextButton( + child: Text(_subtitle.pickFromCamera), + onPressed: () { + Navigator.of(dialogContext).pop(ImageSource.camera); + }, + ), + TextButton( + child: Text(_subtitle.pickFromGallery), + onPressed: () { + Navigator.of(dialogContext).pop(ImageSource.gallery); + }, + ), + ], + ) + ); + if(source == null) + return; + + final pickedFile = await ImagePicker().pickImage( + source: source, + imageQuality: 70, + maxWidth: 800, + maxHeight: 800 + ); + + if (pickedFile != null) { + File _fileImage = File(pickedFile.path); + if(_fileImage != null){ + widget.images.insert(0, _fileImage); + setState(() {}); + } + } + + setState(() {}); + } +} diff --git a/lib/views/widgets/images/multi_image_picker_item.dart b/lib/views/widgets/images/multi_image_picker_item.dart new file mode 100644 index 0000000..9294303 --- /dev/null +++ b/lib/views/widgets/images/multi_image_picker_item.dart @@ -0,0 +1,76 @@ +import 'dart:io'; + +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; +import 'package:flutter/material.dart'; + + +class MultiImagesPickerItem extends StatelessWidget { + + final File image; + final Function(File) onRemoveTap; + + const MultiImagesPickerItem({Key key, this.image, this.onRemoveTap}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: 80 * AppStyle.getScaleFactor(context), + height: 80 * AppStyle.getScaleFactor(context), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black38, + blurRadius: 2 + ) + ], + image: DecorationImage( + image:FileImage(image), + fit: BoxFit.cover + ), + borderRadius: BorderRadius.circular(8) + ), + child: MaterialButton( + padding: EdgeInsets.zero, + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_)=> Scaffold( + body: SafeArea( + child: Stack( + children: [ + Center( + child: InteractiveViewer( + child: Image(image:FileImage(image),) + ), + ), + ABackButton(), + ], + ), + ), + ) + ) + ); + }, + child: Align( + alignment: Alignment.topRight, + child: IconButton( + padding: const EdgeInsets.all(2.0), + icon: Container( + padding: EdgeInsets.all(1), + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor.withOpacity(.3), + borderRadius: BorderRadius.circular(8) + ), + child: Icon(Icons.remove_circle,color: AColors.red,) + ), + onPressed: (){ + onRemoveTap(image); + }, + ) + ), + ), + ); + } +} diff --git a/lib/views/widgets/images/one_image_picker.dart b/lib/views/widgets/images/one_image_picker.dart new file mode 100644 index 0000000..4220f33 --- /dev/null +++ b/lib/views/widgets/images/one_image_picker.dart @@ -0,0 +1,138 @@ +import 'dart:io'; + +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; + +class AOneImagePicker extends StatefulWidget { + final Function(File) onPick; + final File image; + final String label; + final bool error; + const AOneImagePicker({Key key, this.label, this.error,this.image, this.onPick}) : super(key: key); + + @override + _AOneImagePickerState createState() => _AOneImagePickerState(); +} + +class _AOneImagePickerState extends State { + File _image; + Subtitle _subtitle; + @override + void initState() { + super.initState(); + _image = widget.image; + } + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Visibility( + visible: widget.label != null, + child: Column( + children: [ + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + Text( + widget.label?? '', + style: Theme.of(context).textTheme.headline6, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ], + ), + ), + Visibility( + visible: _image == null && widget.error == true, + child: Column( + children: [ + SizedBox(height: 4,), + Text( + _subtitle.requiredImage, + style: Theme.of(context).textTheme.headline6.copyWith( + color: Colors.red + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ], + ), + ), + SizedBox(height: 8,), + Container( + height: MediaQuery.of(context).size.height / 8, + width: MediaQuery.of(context).size.width, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ) + ), + primary: Colors.grey[200], + padding: _image == null ? null : EdgeInsets.zero, + ), + + child: _image == null ? + Text( + _subtitle.pickImage, + style: Theme.of(context).textTheme.headline6, + textScaleFactor: AppStyle.getScaleFactor(context), + ): + ClipRRect( + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + child: Image( + height: MediaQuery.of(context).size.height / 6, + width: MediaQuery.of(context).size.width, + image: FileImage(_image), + fit: BoxFit.cover, + ), + ), + onPressed: () async { + ImageSource source = await showDialog( + context: context, + builder: (_) => CupertinoAlertDialog( + actions: [ + TextButton( + child: Text("pick from camera"), + onPressed: () { + Navigator.of(context).pop(ImageSource.camera); + }, + ), + TextButton( + child: Text("pick from gallery"), + onPressed: () { + Navigator.of(context).pop(ImageSource.gallery); + }, + ), + ], + ) + ); + if(source == null) + return; + + final pickedFile = await ImagePicker().pickImage( + source: source, + imageQuality: 70, + maxWidth: 1000, + maxHeight: 1000 + ); + + + setState(() { + if (pickedFile != null) { + _image = File(pickedFile.path); + widget.onPick(_image); + } + }); + }, + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/views/widgets/issues/report_issue_item.dart b/lib/views/widgets/issues/report_issue_item.dart new file mode 100644 index 0000000..0d04910 --- /dev/null +++ b/lib/views/widgets/issues/report_issue_item.dart @@ -0,0 +1,60 @@ +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class ReportIssueItem extends StatelessWidget { + final bool isSelected; + final String issueInfo; + final Function(String,bool) onChange; + + const ReportIssueItem({ + Key key, + this.isSelected, + this.issueInfo, + this.onChange + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialButton( + splashColor: AColors.secondaryColor.withOpacity(.5), + padding: EdgeInsets.symmetric(vertical: 4), + onPressed: (){ + onChange(issueInfo,!isSelected); + }, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + issueInfo ?? "", + style: Theme.of(context).textTheme.subtitle2, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8 + ), + child: Checkbox( + value: isSelected, + onChanged: (value){ + onChange(issueInfo,value); + }, + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Divider(), + ), + ], + ), + ); + } +} diff --git a/lib/views/widgets/land_page/land_page_item.dart b/lib/views/widgets/land_page/land_page_item.dart new file mode 100644 index 0000000..489d67f --- /dev/null +++ b/lib/views/widgets/land_page/land_page_item.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +class LandPageItem extends StatelessWidget { + + final String text; + final IconData icon; + final VoidCallback onPressed; + + const LandPageItem({Key key, this.text, this.icon, this.onPressed}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.all(10 * AppStyle.getScaleFactor(context),), + textStyle: Theme.of(context).textTheme.subtitle2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ) + ), + //foregroundColor: Colors.white, + ), + onPressed: onPressed, + child: Column( + children: [ + Expanded( + child: Center( + child: Icon(icon,size: 58 * AppStyle.getScaleFactor(context),) + ), + ), + Text(text,textAlign: TextAlign.center,), + ], + ) + ); + } +} diff --git a/lib/views/widgets/loaders/app_lazy_loading.dart b/lib/views/widgets/loaders/app_lazy_loading.dart new file mode 100644 index 0000000..7f2676c --- /dev/null +++ b/lib/views/widgets/loaders/app_lazy_loading.dart @@ -0,0 +1,24 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +import 'app_loading.dart'; +class ALazyLoading extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Center( + child: Container( + height: 36 * AppStyle.getScaleFactor(context), + width: 36 * AppStyle.getScaleFactor(context), + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: ALoading(), + ), + ); + } +} diff --git a/lib/views/widgets/loaders/app_loading.dart b/lib/views/widgets/loaders/app_loading.dart new file mode 100644 index 0000000..7162305 --- /dev/null +++ b/lib/views/widgets/loaders/app_loading.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +class ALoading extends StatelessWidget { + const ALoading({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const Center( + child: CircularProgressIndicator(), + ); + } +} diff --git a/lib/views/widgets/loaders/failed_loading.dart b/lib/views/widgets/loaders/failed_loading.dart new file mode 100644 index 0000000..42e047e --- /dev/null +++ b/lib/views/widgets/loaders/failed_loading.dart @@ -0,0 +1,33 @@ + +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class FailedLoading extends StatelessWidget { + final String message; + final VoidCallback onReload; + + const FailedLoading({ + Key key, + this.message, + this.onReload + }) : super(key: key); + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + message?? "Error Request Failed", + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + SizedBox(height: 16,), + OutlinedButton( + child: Text("try again"), + onPressed: onReload, + ) + ], + ), + ); + } +} diff --git a/lib/views/widgets/loaders/image_loader.dart b/lib/views/widgets/loaders/image_loader.dart new file mode 100644 index 0000000..7550de1 --- /dev/null +++ b/lib/views/widgets/loaders/image_loader.dart @@ -0,0 +1,26 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +class ImageLoader extends StatelessWidget { + final String url; + final BoxFit boxFit; + final Color color; + final Alignment alignment; + const ImageLoader({ + Key key, + @required this.url, + this.boxFit, + this.color, + this.alignment + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return CachedNetworkImage( + imageUrl: url ?? "", + fit: boxFit ?? BoxFit.cover, + alignment: Alignment.center, + placeholder: (context, url) => const Center(child: CircularProgressIndicator()), + errorWidget: (context, url, error) => const Icon(Icons.broken_image_rounded), + ); + } +} diff --git a/lib/views/widgets/loaders/lazy_loading.dart b/lib/views/widgets/loaders/lazy_loading.dart new file mode 100644 index 0000000..d5649c3 --- /dev/null +++ b/lib/views/widgets/loaders/lazy_loading.dart @@ -0,0 +1,100 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +import 'app_loading.dart'; +class LazyLoading extends StatefulWidget { + final Widget child; + final bool nextPage; + final Future Function() onLazyLoad; + final VoidCallback onLoadingEnd; + + const LazyLoading({ + Key key, + this.nextPage = false, + this.child, + this.onLazyLoad, + this.onLoadingEnd, + }) : super(key: key); + + @override + _LazyLoadingState createState() => _LazyLoadingState(); +} + +class _LazyLoadingState extends State with TickerProviderStateMixin { + + AnimationController _animationController; + Animation _offsetAnimation; + + _scrollListener() async { + if (!_animationController.isAnimating && !_animationController.isCompleted && widget.nextPage) { + _animationController.forward(); + setState(() {}); + await widget.onLazyLoad(); + await Future.delayed(Duration(milliseconds: 600)); + setState(() {}); + _animationController.reverse(); + } + } + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 400), + vsync: this, + ); + + _offsetAnimation = Tween( + begin: const Offset(0.0, .8), + end: const Offset(0, .4), + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOutBack, + )); + + } + + @override + void dispose() { + super.dispose(); + _animationController.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + NotificationListener( + onNotification: (notification) { + if(notification.metrics.pixels >= notification.metrics.maxScrollExtent){ + _scrollListener(); + } + return false; + }, + child: widget.child + ), + SlideTransition( + position: _offsetAnimation, + child: Center( + child: Visibility( + visible: _animationController.isAnimating || _animationController.isCompleted, + child: Container( + height: 36 * AppStyle.getScaleFactor(context), + width: 36 * AppStyle.getScaleFactor(context), + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: ALoading(), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/views/widgets/loaders/loading_manager.dart b/lib/views/widgets/loaders/loading_manager.dart new file mode 100644 index 0000000..2a0c9bd --- /dev/null +++ b/lib/views/widgets/loaders/loading_manager.dart @@ -0,0 +1,81 @@ +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:flutter/material.dart'; + +import 'app_loading.dart'; +import 'failed_loading.dart'; +class LoadingManager extends StatefulWidget { + final bool isLoading; + final bool isFailedLoading; + final bool isNotPage; + final int progress; + final bool askOnBack; + final int stateCode; + final Future Function() onRefresh; + final Widget child; + + LoadingManager({ + Key key, + @required this.isLoading, + @required this.isFailedLoading, + @required this.stateCode, + @required this.onRefresh, + @required this.child, + this.progress, + this.isNotPage = false, + this.askOnBack = false, + }) : super(key: key); + + @override + State createState() => _LoadingManagerState(); +} + +class _LoadingManagerState extends State { + + @override + void initState() { + if(widget.onRefresh != null && widget.stateCode == null){ + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + widget.onRefresh(); + }); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + Subtitle subtitle = AppLocalization.of(context).subtitle; + Widget placeHolder; + // to load data if load not start + if(widget.isLoading == false && widget.stateCode == null){ + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + widget.onRefresh(); + }); + } + + // if loading of still not start in loading (true or null) + // return loading widget + if(widget.isLoading != false || widget.stateCode == null){ + placeHolder = ALoading(); + }else if(widget.isFailedLoading && !widget.isNotPage){ + // if failed return failed widget + placeHolder = FailedLoading( + message: HttpStatusManger.getStatusMessage( + status: widget.stateCode, subtitle: subtitle), + onReload: widget.onRefresh, + ); + } + + // if load end successfully return loaded widget + return RefreshIndicator( + onRefresh: () async{ + await widget.onRefresh(); + }, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 400), + child: placeHolder ?? widget.child, + ), + ); + } +} diff --git a/lib/views/widgets/loaders/no_item_found.dart b/lib/views/widgets/loaders/no_item_found.dart new file mode 100644 index 0000000..ca71c28 --- /dev/null +++ b/lib/views/widgets/loaders/no_item_found.dart @@ -0,0 +1,26 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class NoItemFound extends StatelessWidget { + final String message; + + const NoItemFound({ + Key key, + this.message, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Center( + child: Text( + message ?? "no item found", + style: Theme.of(context).textTheme.headline6, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ListView(), + ], + ); + } +} diff --git a/lib/views/widgets/notifications/notification_item.dart b/lib/views/widgets/notifications/notification_item.dart new file mode 100644 index 0000000..e29cc3c --- /dev/null +++ b/lib/views/widgets/notifications/notification_item.dart @@ -0,0 +1,69 @@ +import 'package:test_sa/models/app_notification.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +class NotificationItem extends StatelessWidget { + final AppNotification notification; + final Function(AppNotification) onPressed; + const NotificationItem({Key key, this.notification, this.onPressed}) : super(key: key); + + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + primary: AColors.primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + ), + ), + + onPressed: (){ + onPressed(notification); + }, + + child:Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + notification.title ?? "No Hospital Found", + style: Theme.of(context).textTheme.headline6.copyWith( + color: AColors.white, + fontSize: 16, + fontWeight: FontWeight.bold + ), + ), + ), + Text( + notification.date ?? "complaint not available", + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: AColors.white, + fontSize: 12, + fontWeight: FontWeight.bold + ), + ), + ], + ), + Divider(color: AColors.white,), + Text( + notification.description ?? "complaint not available", + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: AColors.white, + fontSize: 14, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/parts/auto_complete_parts_field.dart b/lib/views/widgets/parts/auto_complete_parts_field.dart new file mode 100644 index 0000000..ef2be95 --- /dev/null +++ b/lib/views/widgets/parts/auto_complete_parts_field.dart @@ -0,0 +1,103 @@ +import 'package:test_sa/controllers/providers/api/parts_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/part.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:provider/provider.dart'; +class AutoCompletePartsField extends StatefulWidget { + final String initialValue; + final Function(Part) onPick; + + const AutoCompletePartsField({Key key, this.initialValue, this.onPick}) : super(key: key); + + @override + _AutoCompletePartsFieldState createState() => _AutoCompletePartsFieldState(); +} + +class _AutoCompletePartsFieldState extends State { + + SettingProvider _settingProvider; + PartsProvider _partsProvider; + UserProvider _userProvider; + TextEditingController _controller; + + @override + void initState() { + _controller = TextEditingController(text: widget.initialValue); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + @override + Widget build(BuildContext context) { + _settingProvider = Provider.of(context); + _userProvider = Provider.of(context); + _partsProvider = Provider.of(context); + //Subtitle _subtitle = AppLocalization.of(context).subtitle; + return LoadingManager( + isLoading: _partsProvider.isLoading, + isFailedLoading: _partsProvider.parts == null, + stateCode: _partsProvider.stateCode, + onRefresh: () async { + _partsProvider.reset(); + await _partsProvider.getParts( + host: _settingProvider.host, + user: _userProvider.user, + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 16 + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color:AColors.black), + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: TypeAheadField( + textFieldConfiguration: TextFieldConfiguration( + style: Theme.of(context).textTheme.subtitle1, + controller: _controller, + textAlign: TextAlign.center, + decoration: InputDecoration( + border: InputBorder.none, + disabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + textInputAction: TextInputAction.search, + ), + suggestionsCallback: (vale) async { + return await _partsProvider.getPartsList( + host: _settingProvider.host, + title: vale + ); + }, + itemBuilder: (context, part) { + return ListTile( + title: Text(part.code), + subtitle: Text(part.name, style: Theme.of(context).textTheme.caption,), + ); + }, + onSuggestionSelected: (part) { + _controller.clear(); + widget.onPick(part); + }, + ), + ), + ); + } +} diff --git a/lib/views/widgets/parts/part_item.dart b/lib/views/widgets/parts/part_item.dart new file mode 100644 index 0000000..354e430 --- /dev/null +++ b/lib/views/widgets/parts/part_item.dart @@ -0,0 +1,90 @@ +import 'package:test_sa/models/part.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button2.dart'; +import 'package:flutter/material.dart'; +class PartItem extends StatefulWidget { + + final Part part; + final Function(Part) onDelete; + + const PartItem({Key key, this.part, this.onDelete}) : super(key: key); + + @override + _PartItemState createState() => _PartItemState(); +} + +class _PartItemState extends State { + @override + Widget build(BuildContext context) { + //final _subtitle = AppLocalization.of(context).subtitle; + return Column( + children: [ + Divider(), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text(widget.part.code, + style: Theme.of(context).textTheme.bodyText1.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold + ), + ), + ), + AIconButton2( + iconData: Icons.add, + color: Theme.of(context).primaryColor, + onPressed: (){ + widget.part.quantity++; + setState(() {}); + }, + ), + AIconButton2( + iconData: Icons.remove, + color: Theme.of(context).primaryColor, + onPressed: widget.part.quantity < 2 ? null : (){ + widget.part.quantity--; + setState(() {}); + }, + ), + SizedBox(width: 8*AppStyle.getScaleFactor(context),), + Text(widget.part.quantity.toString(), + style: Theme.of(context).textTheme.headline6.copyWith( + //fontSize: 12, + //fontWeight: FontWeight.bold + ), + ), + SizedBox(width: 8*AppStyle.getScaleFactor(context),), + ], + ), + widget.part.name == null ? SizedBox.shrink() : + Text(widget.part.name, + style: Theme.of(context).textTheme.caption.copyWith( + fontSize: 11, + fontWeight: FontWeight.bold + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + )), + AIconButton2( + iconData: Icons.close, + color: Colors.red, + onPressed: (){ + widget.onDelete(widget.part); + }, + ), + + + ], + ), + ], + ); + } +} diff --git a/lib/views/widgets/qr/scan_qr.dart b/lib/views/widgets/qr/scan_qr.dart new file mode 100644 index 0000000..0c1cf10 --- /dev/null +++ b/lib/views/widgets/qr/scan_qr.dart @@ -0,0 +1,80 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; + +import '../buttons/app_icon_button.dart'; +class ScanQr extends StatefulWidget { + const ScanQr({Key key}) : super(key: key); + + @override + _ScanQrState createState() => _ScanQrState(); +} + +class _ScanQrState extends State { + + Barcode result; + QRViewController _controller; + bool _scanDone = false; + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR_scanner'); + + // In order to get hot reload to work we need to pause the camera if the platform + // is android, or resume the camera if the platform is iOS. + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + _controller?.pauseCamera(); + } else if (Platform.isIOS) { + _controller?.resumeCamera(); + } + } + + @override + void dispose() { + super.dispose(); + _controller?.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + QRView( + key: qrKey, + onQRViewCreated:(QRViewController controller) { + setState(() { + _controller = controller; + }); + controller.scannedDataStream.listen((scanData) { + if(!_scanDone){ + _scanDone = true; + Navigator.of(context).pop(scanData.code); + } + + }); + }, + overlay: QrScannerOverlayShape( + borderColor: Colors.red, + borderRadius: 10, + borderLength: 30, + borderWidth: 10, + cutOutSize: 280), + ), + SafeArea( + child: Padding( + padding: EdgeInsets.all(12.0), + child: AIconButton( + iconData:Icons.arrow_back, + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/views/widgets/requests/info_row.dart b/lib/views/widgets/requests/info_row.dart new file mode 100644 index 0000000..e7f8ed4 --- /dev/null +++ b/lib/views/widgets/requests/info_row.dart @@ -0,0 +1,74 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class RequestInfoRow extends StatelessWidget { + final String title; + final String info; + final String content; + final Widget contentWidget; + final Widget infoWidget; + + const RequestInfoRow({Key key, this.title, this.info,this.content, this.contentWidget, this.infoWidget}) : super(key: key); + @override + Widget build(BuildContext context) { + if(info != null && info.isEmpty){ + return SizedBox.shrink(); + } + if(content != null && content.isEmpty){ + return SizedBox.shrink(); + } + return Column( + children: [ + Row( + children: [ + Text( + title + " : ", + style: Theme.of(context).textTheme.subtitle2.copyWith( + //fontSize: 12 + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + + if(info != null) + Expanded( + child: Text( + info, + style: Theme.of(context).textTheme.bodyText2, + textAlign: TextAlign.right, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + if(infoWidget != null) + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + infoWidget, + ], + ), + ), + ], + ), + + if(content != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + children: [ + Expanded( + child: Text( + content ?? 'No data found', + style: Theme.of(context).textTheme.bodyText2, + textAlign: TextAlign.center, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ], + ), + ), + if(contentWidget != null) + contentWidget, + Divider(color: Theme.of(context).primaryColor,), + ], + ); + } +} \ No newline at end of file diff --git a/lib/views/widgets/requests/request_status.dart b/lib/views/widgets/requests/request_status.dart new file mode 100644 index 0000000..8414234 --- /dev/null +++ b/lib/views/widgets/requests/request_status.dart @@ -0,0 +1,37 @@ +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class StatusLabel extends StatelessWidget { + final String label; + final Color color; + + const StatusLabel({Key key, this.label, this.color}) : super(key: key); + + + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(vertical: 2,horizontal: 8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: color ?? Colors.green, + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: Text( + label ?? "no status", + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: color.computeLuminance() > 0.5 + ? AColors.black : Colors.white, + ), + ) + ); + } +} diff --git a/lib/views/widgets/requests/service_request_item.dart b/lib/views/widgets/requests/service_request_item.dart new file mode 100644 index 0000000..6cffb28 --- /dev/null +++ b/lib/views/widgets/requests/service_request_item.dart @@ -0,0 +1,317 @@ +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/enums/user_types.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/user.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/pages/user/requests/report/create_service_report.dart'; +import 'package:test_sa/views/pages/user/requests/report/future_service_report.dart'; +import 'package:test_sa/views/widgets/loaders/image_loader.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; + +import '../../../controllers/http_status_manger/http_status_manger.dart'; +class ServiceRequestItem extends StatelessWidget { + final int index; + final ServiceRequest request; + final Function(ServiceRequest) onPressed; + const ServiceRequestItem({Key key, this.request, this.onPressed, this.index}) : super(key: key); + + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + User _user = Provider.of(context,listen: false).user; + final servicesProvider = Provider.of(context,listen: false); + final settingProvider = Provider.of(context,listen: false); + Color itemColor = index % 2 == 0 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onPrimary; + Color onItemColor = index % 2 != 0 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onPrimary; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + primary: itemColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + ), + ), + //padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + onPressed: (){ + onPressed(request); + }, + child: Row( + children: [ + //Placeholder(color: onItemColor,fallbackWidth: 80,fallbackHeight: 80,), + _user.type == UsersTypes.normal_user && request.devicePhotos.isEmpty ? SizedBox.shrink(): + SizedBox( + width: 80, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + request.devicePhotos.isEmpty ? SizedBox.shrink(): + Column( + children: [ + SizedBox( + height: 80, + width: 80, + child: ImageLoader( + url: request.devicePhotos.first, + boxFit: BoxFit.cover, + ), + ), + SizedBox(height: 24,), + ], + ), + + _user.type == UsersTypes.engineer ? + Material( + color: onItemColor, + elevation: 6, + shape: CircleBorder(), + child: IconButton( + icon: Icon( + Icons.description, + color: itemColor, + size: 32, + ), + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => CreateServiceReport( + request: request, + ) + ), + ); + }, + ), + ) :SizedBox.shrink(), + //SizedBox(height: 8,), + + ], + ), + ), + SizedBox(width: 8,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: request.requestCode == null ? SizedBox.shrink(): + Text( + request.requestCode ?? "-----", + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 16, + fontWeight: FontWeight.bold + ), + ), + ), + request.engineerName == null ? SizedBox.shrink(): + Text( + request.engineerName ?? "", + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + fontSize: 12, + fontWeight: FontWeight.normal + ), + ), + + ], + ), + Divider(color: onItemColor,), + Row( + children: [ + Expanded( + child: request.deviceModel == null ? SizedBox.shrink(): + Text( + request.deviceModel ?? "Model not available", + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + fontSize: 12, + fontWeight: FontWeight.normal + ), + ), + ), + request.engineerMobile == null ? SizedBox.shrink(): + Text( + request.engineerMobile, + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: onItemColor, + fontSize: 12, + fontWeight: FontWeight.normal + ), + ), + ], + ), + Divider(color: onItemColor,), + Row( + children: [ + Expanded( + child: request.deviceSerialNumber == null ? SizedBox.shrink(): + Text( + request.deviceSerialNumber, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor, + fontSize: 12, + fontWeight: FontWeight.normal + ), + ), + ), + ], + ), + request.deviceEnName == null ? SizedBox.shrink(): + Row( + children: [ + Expanded( + child: Text( + request.deviceEnName, + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: onItemColor, + fontSize: 12, + fontWeight: FontWeight.normal + ), + ), + ), + ], + ), + ], + ), + ), + + ], + ), + + Divider(color: onItemColor,), + Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + request.maintenanceIssue ?? "No maintenance issue found", + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: onItemColor + ), + textAlign: TextAlign.center, + ), + ), + ), + Divider(color: onItemColor,), + Row( + children: [ + Text( + request.date ?? "Date not available", + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor + ), + ), + Spacer(), + StatusLabel(label: request.statusLabel, + color: AColors.getRequestStatusColor(request.statusValue)), + ], + ), + request.nextVisitDate == null ? SizedBox.shrink() : + Column( + children: [ + Divider(color: onItemColor,), + Row( + children: [ + Text( + _subtitle.nextVisitDate, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor + ), + ), + Spacer(), + Text( + DateFormat('EE dd/MM/yyyy').format(request.nextVisitDate), + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: onItemColor + ), + ), + + ], + ), + ], + ), + if(request.viewReport) + Column( + children: [ + const SizedBox(height: 8,), + Row( + children: [ + Expanded( + child: Text( + "Work Duration", + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 14, + fontWeight: FontWeight.bold + ), + ), + ), + // AppTimer( + // timer: request.timer, + // onChange: (timeModel) async { + // + // request.timer = timeModel; + // if(timeModel.endAt == null) return true; + // int status = await servicesProvider.updateServiceReportTimer( + // host: settingProvider.host, + // user: _user, + // timer: timeModel, + // request: request + // ); + // if(status >= 200 && status < 300){ + // return true; + // }else{ + // String errorMessage = HttpStatusManger.getStatusMessage( + // status: status, subtitle: _subtitle); + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text( + // errorMessage + // ), + // ) + // ); + // return false; + // } + // + // + // }, + // ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/requests/service_request_list.dart b/lib/views/widgets/requests/service_request_list.dart new file mode 100644 index 0000000..7672a5d --- /dev/null +++ b/lib/views/widgets/requests/service_request_list.dart @@ -0,0 +1,46 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/pages/user/requests/request_details.dart'; +import 'package:test_sa/views/widgets/loaders/lazy_loading.dart'; +import 'package:test_sa/views/widgets/loaders/no_item_found.dart'; +import 'package:test_sa/views/widgets/requests/service_request_item.dart'; +import 'package:flutter/material.dart'; +class ServiceRequestsList extends StatelessWidget { + final List requests; + final bool nextPage; + final Future Function() onLazyLoad; + + const ServiceRequestsList({Key key, this.requests, this.nextPage, this.onLazyLoad}) : super(key: key); + + @override + Widget build(BuildContext context) { + + Subtitle _subtitle = AppLocalization.of(context).subtitle; + if(requests.length == 0){ + return NoItemFound(message: _subtitle.noServiceRequestFound,); + } + return LazyLoading( + nextPage: nextPage, + onLazyLoad: onLazyLoad, + child: ListView.builder( + //physics: BouncingScrollPhysics(), + itemCount: requests.length, + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 8), + itemBuilder: (context,itemIndex){ + return ServiceRequestItem( + index: itemIndex, + request: requests[itemIndex], + onPressed: (request){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_)=> RequestDetailsPage(serviceRequest: request,) + ) + ); + }, + ); + } + ), + ); + } +} diff --git a/lib/views/widgets/requests/service_request_update_dialog.dart b/lib/views/widgets/requests/service_request_update_dialog.dart new file mode 100644 index 0000000..ccea885 --- /dev/null +++ b/lib/views/widgets/requests/service_request_update_dialog.dart @@ -0,0 +1,149 @@ +import 'package:flutter/cupertino.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/http_status_manger/http_status_manger.dart'; +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/service_request/service_request.dart'; +import 'package:test_sa/models/service_request/service_request_search.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_small_button.dart'; +import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; +import 'package:test_sa/views/widgets/hospitals/auto_complete_field.dart'; +import 'package:flutter/material.dart'; +import 'package:test_sa/views/widgets/status/employee/employee_mune.dart'; + +import '../app_text_form_field.dart'; + +class ServiceRequestsUpdateDialog extends StatefulWidget { + final ServiceRequest request; + const ServiceRequestsUpdateDialog({ + Key key, this.request, + }) : super(key: key); + @override + State createState() => _ServiceRequestsUpdateDialogState(); +} + +class _ServiceRequestsUpdateDialogState extends State + with TickerProviderStateMixin{ + + DateTime _dateTime; + Status _employee; + Subtitle _subtitle; + UserProvider _userProvider; + SettingProvider _settingProvider; + ServiceRequestsProvider _serviceRequestsProvider; + + final GlobalKey _formKey = GlobalKey(); + + _update() async { + if(_dateTime == null && _employee == null){ + Fluttertoast.showToast( + msg: _subtitle.noDateFound, + ); + return; + } + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text(_subtitle.updatingDots), + content: Center(child: CircularProgressIndicator()), + ); + }, + ); + int status = await _serviceRequestsProvider.updateDate( + user: _userProvider.user, + host: _settingProvider.host, + request: widget.request, + newDate: _dateTime?.toString()?.split(" ")?.first, + employee: _employee + ); + if(status == 200) Navigator.of(context).pop(); + Navigator.of(context).pop(); + Fluttertoast.showToast( + msg: HttpStatusManger.getStatusMessage(status: status, subtitle: _subtitle), + ); + } + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + _subtitle = AppLocalization.of(context).subtitle; + _userProvider = Provider.of(context,listen: false); + _settingProvider = Provider.of(context,listen: false); + _serviceRequestsProvider = Provider.of(context,listen: false); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + // height: MediaQuery.of(context).size.height / 1.2, + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ASmallButton( + text: _subtitle.cancel, + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + + ASmallButton( + text: _subtitle.update, + onPressed: _update, + ) + ], + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + Row( + children: [ + Expanded( + child: Text( + _subtitle.date, + style: Theme.of(context).textTheme.subtitle1, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ADatePicker( + date: _dateTime, + from: DateTime.now(), + onDatePicker: (date){ + _dateTime = date; + setState(() {}); + }, + ), + ], + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + EmployeeMenu( + initialValue: _employee, + onSelect: (employee){ + _employee = employee; + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/views/widgets/search/filter_item.dart b/lib/views/widgets/search/filter_item.dart new file mode 100644 index 0000000..7573d69 --- /dev/null +++ b/lib/views/widgets/search/filter_item.dart @@ -0,0 +1,59 @@ +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; + +class FilterItem extends StatelessWidget { + final bool isSelected; + final Status status; + final VoidCallback onSelected; + + const FilterItem({ + Key key, + this.status, + this.isSelected, + this.onSelected + }) : super(key: key); + + Color getStatusColor(){ + switch(status.id){ + case 0: return AColors.green; + case 4: return AColors.deepRed; + case 6: return AColors.green; + case 5: return AColors.orange; + case 8: return AColors.green; + case 9: return AColors.orange; + default : return AColors.grey; + } + } + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: isSelected ? 1 : .5, + child: SizedBox( + height: 30, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ) + ), + primary: getStatusColor(), + ), + child: Text( + status.label??"", + style: Theme.of(context).textTheme.bodyText1.copyWith( + color:getStatusColor().computeLuminance() > 0.5 + ? AColors.black : Colors.white, + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + onPressed:onSelected, + ), + ), + ); + } +} diff --git a/lib/views/widgets/search/service_request_search_bar.dart b/lib/views/widgets/search/service_request_search_bar.dart new file mode 100644 index 0000000..dd05a5f --- /dev/null +++ b/lib/views/widgets/search/service_request_search_bar.dart @@ -0,0 +1,188 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/service_request/service_request_search.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_small_button.dart'; +import 'package:test_sa/views/widgets/hospitals/auto_complete_field.dart'; +import 'package:flutter/material.dart'; + +import '../app_text_form_field.dart'; +import 'filter_item.dart'; +class ServiceRequestsSearchDialog extends StatefulWidget { + final ServiceRequestSearch initialSearchValue; + final bool expandedSearch; + final Function(ServiceRequestSearch) onSearch; + + const ServiceRequestsSearchDialog({ + Key key, + this.initialSearchValue, + this.expandedSearch, + this.onSearch + }) : super(key: key); + @override + _ServiceRequestsSearchDialogState createState() => _ServiceRequestsSearchDialogState(); +} + +class _ServiceRequestsSearchDialogState extends State + with TickerProviderStateMixin{ + ServiceRequestSearch _search; + List status = [ + Status(label: "New", id: 4,), + + Status(label: "Repaired", id: 6,), + Status(label: "Repeated", id: 8), + Status(label: "Closed", id: 9,), + Status(label: "Under Repair", id: 5,), + ]; + + final GlobalKey _formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + _search = ServiceRequestSearch(); + _search.fromSearch(widget.initialSearchValue); + } + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return SizedBox( + height: MediaQuery.of(context).size.height / 1.2, + child: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ASmallButton( + text: _subtitle.cancel, + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + + ASmallButton( + text: _subtitle.search, + onPressed: (){ + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + Navigator.of(context).pop(_search); + }, + ) + ], + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _search.deviceSerialNumber, + hintText: _subtitle.serialNumber, + style: Theme.of(context).textTheme.headline6, + textInputAction: TextInputAction.search, + onAction: (){ + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + Navigator.of(context).pop(_search); + }, + onSaved: (value){ + _search.deviceSerialNumber = value; + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + AutoCompleteField( + initialValue: _search.hospital, + onSave: (value){ + _search.hospital = value; + }, + onSearch: (value){ + _search.hospital = value; + Navigator.of(context).pop(_search); + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _search.deviceName, + hintText: _subtitle.deviceName, + style: Theme.of(context).textTheme.headline6, + textInputAction: TextInputAction.search, + onAction: (){ + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + Navigator.of(context).pop(_search); + }, + onSaved: (value){ + _search.deviceName = value; + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _search.model, + hintText: _subtitle.model, + style: Theme.of(context).textTheme.headline6, + textInputAction: TextInputAction.search, + onAction: (){ + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + Navigator.of(context).pop(_search); + }, + onSaved: (value){ + _search.model = value; + }, + ), + SizedBox(height: 16 * AppStyle.getScaleFactor(context),), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Wrap( + spacing: 10, + runSpacing: 10, + alignment: WrapAlignment.spaceEvenly, + children: List.generate( + status.length, + (index) { + bool isSelected = _search.statusValue == status[index].id; + return FilterItem( + isSelected: isSelected, + onSelected: (){ + if(isSelected) + _search.statusValue = null; + else + _search.statusValue = status[index].id; + + setState(() {}); + }, + status: status[index], + ); + } + + ), + ), + ), + + Visibility( + visible: widget.initialSearchValue.toSearchString().isNotEmpty, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8,horizontal: 16), + child: AButton( + padding: EdgeInsets.zero, + text: _subtitle.clearSearch, + onPressed: (){ + _search = ServiceRequestSearch(); + Navigator.of(context).pop(_search); + }, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/search/visits_search_bar.dart b/lib/views/widgets/search/visits_search_bar.dart new file mode 100644 index 0000000..3e7c642 --- /dev/null +++ b/lib/views/widgets/search/visits_search_bar.dart @@ -0,0 +1,242 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/visits/visits_search.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_small_button.dart'; +import 'package:test_sa/views/widgets/date_and_time/from_to_date_bar.dart'; +import 'package:test_sa/views/widgets/hospitals/auto_complete_field.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../app_text_form_field.dart'; +import 'filter_item.dart'; +class VisitsSearchDialog extends StatefulWidget { + final VisitsSearch initialSearchValue; + final bool expandedSearch; + final Function(VisitsSearch) onSearch; + + const VisitsSearchDialog({ + Key key, + this.initialSearchValue, + this.expandedSearch, + this.onSearch + }) : super(key: key); + @override + _VisitsSearchDialogState createState() => _VisitsSearchDialogState(); +} + +class _VisitsSearchDialogState extends State + with TickerProviderStateMixin{ + VisitsSearch _search; + List status = [ + Status(label: "Done", id: 0,), + Status(label: "Not Yet", id: 1), + Status(label: "On Hold", id: 2,), + ]; + + List contactStatus = [ + Status(label: "Hospital Employee", key: "H",), + Status(label: "Under Warranty", key: "CW"), + Status(label: "Under Maintenance Contract", key: "CC",), + ]; + + + final GlobalKey _formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + _search = VisitsSearch(); + _search.fromSearch(widget.initialSearchValue); + } + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + DateTime today = DateTime.now(); + return SizedBox( + height: MediaQuery.of(context).size.height / 1.3, + child: Form( + key: _formKey, + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 8,horizontal: 16), + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ASmallButton( + text: _subtitle.cancel, + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + + ASmallButton( + text: _subtitle.search, + onPressed: (){ + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + Navigator.of(context).pop(_search); + }, + ) + ], + ), + ATextFormField( + initialValue: _search.deviceSerialNumber, + hintText: _subtitle.serialNumber, + style: Theme.of(context).textTheme.headline6, + textInputAction: TextInputAction.search, + onAction: (){ + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + Navigator.of(context).pop(_search); + }, + onSaved: (value){ + _search.deviceSerialNumber = value; + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + AutoCompleteField( + initialValue: _search.hospitalName, + onSave: (value){ + _search.hospitalName = value; + }, + onSearch: (value){ + _search.hospitalName = value; + Navigator.of(context).pop(_search); + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _search.brand, + hintText: _subtitle.brand, + style: Theme.of(context).textTheme.headline6, + textInputAction: TextInputAction.search, + onAction: (){ + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + Navigator.of(context).pop(_search); + }, + onSaved: (value){ + _search.brand = value; + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ATextFormField( + initialValue: _search.model, + hintText: _subtitle.model, + style: Theme.of(context).textTheme.headline6, + textInputAction: TextInputAction.search, + onAction: (){ + if(!_formKey.currentState.validate()) + return; + _formKey.currentState.save(); + Navigator.of(context).pop(_search); + }, + onSaved: (value){ + _search.model = value; + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ASubTitle(_subtitle.status), + SizedBox(height: 4.0 * AppStyle.getScaleFactor(context),), + Wrap( + spacing: 10, + runSpacing: 10, + children: List.generate( + status.length, + (index) { + bool isSelected = _search.statusValue == status[index].id; + return FilterItem( + isSelected: isSelected, + onSelected: (){ + if(isSelected) + _search.statusValue = null; + else + _search.statusValue = status[index].id; + + setState(() {}); + }, + status: status[index], + ); + } + + ), + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ASubTitle(_subtitle.contactStatus), + SizedBox(height: 4.0 * AppStyle.getScaleFactor(context),), + Wrap( + spacing: 10, + runSpacing: 10, + children: List.generate( + contactStatus.length, + (index) { + bool isSelected = _search.contactStatus == contactStatus[index].key; + return FilterItem( + isSelected: isSelected, + onSelected: (){ + if(isSelected) + _search.contactStatus = null; + else + _search.contactStatus = contactStatus[index].key; + + setState(() {}); + }, + status: contactStatus[index], + ); + } + + ), + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ASubTitle(_subtitle.actualDate), + SizedBox(height: 4.0 * AppStyle.getScaleFactor(context),), + FromToDateBar( + from: _search.actualDateFrom , + to: _search.actualDateTo, + onPickFrom: (date){ + _search.actualDateFrom = date; + }, + onPickTo: (date){ + _search.actualDateTo = date; + }, + ), + SizedBox(height: 8.0 * AppStyle.getScaleFactor(context),), + ASubTitle(_subtitle.expectDate), + SizedBox(height: 4.0 * AppStyle.getScaleFactor(context),), + FromToDateBar( + from: _search.expectedDateFrom ?? DateTime(today.year, today.month, 1), + to: _search.expectedDateTo ?? DateTime(today.year, (today.month +1).clamp(1, 12) , today.month == 12 ? 31 : 0), + onPickFrom: (date){ + _search.expectedDateFrom = date; + }, + onPickTo: (date){ + _search.expectedDateTo = date; + }, + ), + Visibility( + visible: _search.toSearchString().isNotEmpty, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8,horizontal: 16), + child: AButton( + padding: EdgeInsets.zero, + text: _subtitle.clearSearch, + onPressed: (){ + _search = VisitsSearch(); + Navigator.of(context).pop(_search); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/sound/record_sound.dart b/lib/views/widgets/sound/record_sound.dart new file mode 100644 index 0000000..2f93c15 --- /dev/null +++ b/lib/views/widgets/sound/record_sound.dart @@ -0,0 +1,201 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_sound/flutter_sound.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:rive/rive.dart'; +import 'package:test_sa/views/widgets/buttons/app_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button2.dart'; +import 'package:test_sa/views/widgets/buttons/app_small_button.dart'; +import 'package:test_sa/views/widgets/sound/sound_player.dart'; + +import '../../app_style/sizing.dart'; +class RecordSound extends StatefulWidget { + final Function(String) onRecord; + const RecordSound({Key key,@required this.onRecord}) : super(key: key); + + @override + State createState() => _RecordSoundState(); +} + +class _RecordSoundState extends State { + + FlutterSoundRecorder _myRecorder = FlutterSoundRecorder(); + bool _recorderIsOpened = false; + bool _recording = false; + bool _fastTab = false; + String _record; + Artboard _rive; + + @override + void setState(VoidCallback fn) { + if(mounted) super.setState(fn); + } + @override + void initState() { + super.initState(); + _myRecorder.openRecorder().then((value) { + _recorderIsOpened = true; + setState(() {}); + }); + + // Load the animation file from the bundle, note that you could also + // download this. The RiveFile just expects a list of bytes. + rootBundle.load('assets/rives/recording.riv').then( + (data) async { + // Load the RiveFile from the binary data. + final file = RiveFile.import(data); + // The artboard is the root of the animation and gets drawn in the + // Rive widget. + final artboard = file.mainArtboard; + // Add a controller to play back a known animation on the main/default + // artboard.We store a reference to it so we can toggle playback. + artboard.addController(SimpleAnimation('recording')); + _rive = artboard; + + setState(() {}); + + }, + ); + + } + + @override + void dispose() { + // Be careful : you must `close` the audio session when you have finished with it. + _myRecorder.closeRecorder(); + _myRecorder = null; + super.dispose(); + } + + _startRecording() async { + _fastTab = false; + // await Permission.camera + PermissionStatus status = await Permission.microphone.request(); + if(!status.isGranted){ + return; + } + _rive.addController(SimpleAnimation('recording')); + if(!_recorderIsOpened){ + await _myRecorder.openRecorder(); + _recorderIsOpened = true; + } + + await _myRecorder.startRecorder( + toFile: "record_${DateTime.now().millisecondsSinceEpoch}.mp4", + codec: Codec.aacMP4, + sampleRate: 360000, + bitRate: 360000 + ); + + _recording = true; + setState(() {}); + print("onTapDown"); + } + + _stopRecording() async { + if(!_recording){ + _fastTab = true; + setState(() {}); + return; + } + String path = (await _myRecorder.stopRecorder()).toString(); + print("audio path ----> $path <-----"); + _record = path; + widget.onRecord(path); + _recording = false; + setState(() { }); + print("onTapUp"); + } + + _cancelRecording() async { + if(!_recording) return; + String path = await _myRecorder.stopRecorder(); + _myRecorder.deleteRecord(fileName: path); + _rive.addController(SimpleAnimation('delete')); + + // rebuild(); + _recording = false; + await Future.delayed(const Duration(seconds: 1)); + if(!_recording) setState(() { }); + print("onTapCancel"); + // _message.memoryAudio.; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + Expanded( + child: _recording ? + Row( + children: [ + ASmallButton( + text: "done", + onPressed: (){ + _stopRecording(); + }, + ), + Expanded( + child: Stack( + children: [ + SizedBox( + height: 24 * AppStyle.getScaleFactor(context), + child: Rive(artboard: _rive,) + ), + InkWell( + child: SizedBox( + height: 32 * AppStyle.getScaleFactor(context), + width: MediaQuery.of(context).size.width, + ), + onTap: (){ + _cancelRecording(); + }, + ), + ], + ), + ), + ], + ): _record != null + ? Row( + children: [ + Expanded(child: ASoundPlayer(audio: _record)), + AIconButton2( + iconData: Icons.delete, + onPressed: (){ + widget.onRecord(null); + _record = null; + setState(() { }); + }, + ) + ], + ) + : const Text("Record Voice"), + ), + Material( + color: Colors.transparent, + child: GestureDetector( + //key: ValueKey("voice"), + child: const Padding( + padding: EdgeInsets.all(16.0), + child: Icon(Icons.mic), + ), + + onTapDown: (TapDownDetails details) async { + _startRecording(); + }, + onTapUp: (TapUpDetails details) async { + _stopRecording(); + }, + onTapCancel: () async { + _cancelRecording(); + }, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/views/widgets/sound/sound_player.dart b/lib/views/widgets/sound/sound_player.dart new file mode 100644 index 0000000..20dd005 --- /dev/null +++ b/lib/views/widgets/sound/sound_player.dart @@ -0,0 +1,241 @@ +import 'dart:typed_data'; + +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter/material.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; + +import '../../app_style/colors.dart'; + +class ASoundPlayer extends StatefulWidget { + + final String audio; + + const ASoundPlayer({Key key, this.audio}) : super(key: key); + + @override + _ASoundPlayerState createState() => _ASoundPlayerState(); +} + +class _ASoundPlayerState extends State { + + //FlutterSoundPlayer _myPlayer = FlutterSoundPlayer(); + bool _sliderMoving = false; + Duration _audioTime; + Duration _audioPosition; + String _audio; + bool _isLocalFile = false; + bool _failedToLoad = false; + AudioPlayer _audioPlayer; + + Widget _getAudioButton(){ + switch(_audioPlayer.state){ + case PlayerState.playing: + return IconButton( + icon: const Icon(Icons.pause_rounded), + onPressed: () async { + _failedToLoad = false; + await _audioPlayer.pause(); + rebuild(); + } + ); + case PlayerState.paused: + return IconButton( + icon: const Icon(Icons.play_arrow_rounded), + onPressed: () async { + _failedToLoad = false; + await _audioPlayer.resume(); + rebuild(); + } + ); + case PlayerState.completed: + return IconButton( + icon: const Icon(Icons.replay_rounded), + onPressed: () async { + _failedToLoad = false; + await _audioPlayer.stop(); + await _audioPlayer.resume(); + rebuild(); + } + ); + case PlayerState.stopped: + return IconButton( + icon: Icon( _isLocalFile + ? Icons.play_circle_fill_outlined + : Icons.download_rounded + + ), + onPressed: () async { + _failedToLoad = false; + try { + await _audioPlayer.play( + _isLocalFile? + DeviceFileSource(_audio):UrlSource(_audio), + ); + rebuild(); + } on Exception catch (e) { + _failedToLoad = true; + } + } + ); + default: return IconButton( + icon: const Icon( Icons.replay_rounded), + onPressed: () async { + _failedToLoad = false; + print(!Uri.parse(_audio).isAbsolute); + try { + _audioPlayer.seek(const Duration(milliseconds: 0)); + _audioPlayer.stop(); + await _audioPlayer.play( + _isLocalFile? + DeviceFileSource(_audio):UrlSource(_audio), + ); + rebuild(); + } on Exception catch (e) { + _failedToLoad = true; + } + } + ); + } + } + + String format(Duration d) { + if(d == null) + return "00:00"; + return d.toString().substring(2, 7); + } + + rebuild(){ + if (!mounted) return; + setState(() {}); + } + + bool _isLocalUrl(String url) { + if(url?.isEmpty != false) return false; + return url.startsWith("/") || + url.startsWith("file://") || + url.substring(1).startsWith(':\\'); + } + + @override + void initState() { + super.initState(); + _audioPlayer = AudioPlayer(); + _audioPlayer.release(); + _audio = widget.audio; + _isLocalFile = _isLocalUrl(_audio); + _audioPlayer.setReleaseMode(ReleaseMode.stop); + if(_isLocalFile){ + _audioPlayer.setSourceDeviceFile(_audio).then((value) {rebuild();}); + } else{ + _audioPlayer.setReleaseMode(ReleaseMode.stop); + } + // set up listeners + _audioPlayer.onPositionChanged.listen((Duration duration) { + //print('Current position: $p'); + if(!_sliderMoving){ + _audioPosition = duration; + rebuild(); + } + //setState(() => position = p); + }); + _audioPlayer.onPlayerStateChanged.listen((event) { + //_audioPosition = _audioTime; + rebuild(); + }); + _audioPlayer.onDurationChanged.listen((Duration duration) { + _audioTime = duration; + rebuild(); + }); + _audioPlayer.onSeekComplete.listen((event) { + rebuild(); + }); + } + + @override + void dispose() { + super.dispose(); + // _myPlayer.closeAudioSession(); + _audioPlayer.release(); + _audioPlayer.dispose(); + + } + + @override + Widget build(BuildContext context) { + if(_audio != widget.audio){ + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + _audio = widget.audio; + if(_isLocalFile){ + await _audioPlayer.setSourceDeviceFile(_audio); + }else{ + await _audioPlayer.setSourceUrl(_audio); + } + _audioPlayer.seek(const Duration(milliseconds: 0)); + _audioPlayer.stop(); + rebuild(); + }); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + children: [ + Material( + color: Colors.transparent, + child: _getAudioButton() + ), + Expanded( + child: Slider( + value: _audioPosition?.inMilliseconds?.toDouble() ?? 0.0, + min: 0, + max: _audioTime?.inMilliseconds?.toDouble() ?? 60.0, + onChangeStart: (value){ + _sliderMoving = true; + }, + onChanged: (value){ + _audioPosition = Duration(milliseconds: value.round()); + rebuild(); + }, + onChangeEnd: (value){ + _sliderMoving = false; + _audioPlayer.seek(Duration(milliseconds: value.round())); + rebuild(); + } + + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: Visibility( + visible: _failedToLoad, + child: Row( + children: [ + Text( + "Failed to load", + style: Theme.of(context).textTheme.overline.copyWith( + color: AColors.red + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + + ], + ), + ), + ), + Visibility( + visible: _audioPlayer.state != PlayerState.stopped, + child: Text( + "${format(_audioPosition)}/${format(_audioTime)}", + style: Theme.of(context).textTheme.overline, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + ], + ) + ], + ); + } +} diff --git a/lib/views/widgets/speech_to_text/speech_to_text.dart b/lib/views/widgets/speech_to_text/speech_to_text.dart new file mode 100644 index 0000000..03e2eda --- /dev/null +++ b/lib/views/widgets/speech_to_text/speech_to_text.dart @@ -0,0 +1,122 @@ +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button.dart'; +import 'package:test_sa/views/widgets/buttons/app_icon_button2.dart'; +import 'package:test_sa/views/widgets/titles/app_sub_title.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:speech_to_text/speech_recognition_error.dart'; +import 'package:speech_to_text/speech_recognition_result.dart'; +import 'package:speech_to_text/speech_to_text.dart'; + +import '../app_text_form_field.dart'; +class SpeechToTextButton extends StatefulWidget { + + final TextEditingController controller; + final bool mini; + const SpeechToTextButton({ + Key key, this.controller,this.mini = false + }) : super(key: key); + + @override + _SpeechToTextButtonState createState() => _SpeechToTextButtonState(); +} + +class _SpeechToTextButtonState extends State { + bool _speechEnabled = false; + SettingProvider _settingProvider; + SpeechToText _speechToText = SpeechToText(); + + /// This has to happen only once per app + void _initSpeech() async { + _speechEnabled = await _speechToText.initialize( + onError: (SpeechRecognitionError error) async { + Fluttertoast.showToast(msg: "failed to convert text to speech"); + setState(() { }); + }, + ); + } + + /// Each time to start a speech recognition session + void _startListening() async { + _speechEnabled = _speechToText.isAvailable; + if(_speechToText.isListening){ + Fluttertoast.showToast(msg: "Currently in use"); + return; + } + if(!_speechEnabled) return; + await _speechToText.listen( + onResult: (SpeechRecognitionResult result){ + widget.controller.text = result.recognizedWords; + setState(() {}); + }, + localeId: _settingProvider.speechToText + ); + setState(() {}); + } + + /// Manually stop the active speech recognition session + /// Note that there are also timeouts that each platform enforces + /// and the SpeechToText plugin supports setting timeouts on the + /// listen method. + void _stopListening() async { + await _speechToText.stop(); + setState(() {}); + } + + @override + void initState() { + _initSpeech(); + super.initState(); + } + + @override + void setState(VoidCallback fn) { + if(!this.mounted) return; + super.setState(fn); + } + @override + Widget build(BuildContext context) { + _settingProvider = Provider.of(context); + return Row( + children: [ + widget.mini ? SizedBox.shrink() : + ASubTitle("Text To Speech"), + widget.controller.text.isNotEmpty? + AIconButton2( + iconData: Icons.delete, + onPressed: (){ + widget.controller.clear(); + setState(() {}); + }, + ):SizedBox.shrink(), + Spacer(), + TextButton( + onPressed: (){ + if(_speechToText.isListening) return; + + if(_settingProvider.speechToText == "ar"){ + _settingProvider.setSpeechToText("en"); + } else{ + _settingProvider.setSpeechToText("ar"); + } + }, + child: Text(_settingProvider.speechToText) + ), + GestureDetector( + child: _speechToText.isListening + ? Icon(Icons.fiber_manual_record,color: Colors.red,) + : Icon(Icons.mic,color: Theme.of(context).colorScheme.primary,), + + onTap: () async { + if(!_speechEnabled){ + Fluttertoast.showToast(msg: "microphone not available"); + return; + } + _startListening(); + }, + ), + ], + ); + } +} diff --git a/lib/views/widgets/status/employee/employee_mune.dart b/lib/views/widgets/status/employee/employee_mune.dart new file mode 100644 index 0000000..5555233 --- /dev/null +++ b/lib/views/widgets/status/employee/employee_mune.dart @@ -0,0 +1,37 @@ +import 'package:test_sa/controllers/providers/api/status_drop_down/employee/employee_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class EmployeeMenu extends StatelessWidget { + final Function(Status) onSelect; + final Status initialValue; + + const EmployeeMenu({Key key, this.onSelect, this.initialValue}) : super(key: key); + @override + Widget build(BuildContext context) { + final settingProvider = Provider.of(context); + final userProvider = Provider.of(context); + final menuProvider = Provider.of(context); + return LoadingManager( + isLoading: menuProvider.isLoading, + isFailedLoading: menuProvider.items == null, + stateCode: menuProvider.stateCode, + onRefresh: () async { + menuProvider.reset(); + await menuProvider.getData( + user: userProvider.user, + host: settingProvider.host + ); + }, + child: SingleStatusMenu( + initialStatus: initialValue, + statuses: menuProvider.items, + onSelect: onSelect, + ) + ); + } +} diff --git a/lib/views/widgets/status/gas_refill/gas_cylinder_size.dart b/lib/views/widgets/status/gas_refill/gas_cylinder_size.dart new file mode 100644 index 0000000..842e5d0 --- /dev/null +++ b/lib/views/widgets/status/gas_refill/gas_cylinder_size.dart @@ -0,0 +1,39 @@ +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_cylinder_size_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_status_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_types_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class GasCylinderSizeMenu extends StatelessWidget { + final Function(Status) onSelect; + final Status initialValue; + + const GasCylinderSizeMenu({Key key, this.onSelect, this.initialValue}) : super(key: key); + @override + Widget build(BuildContext context) { + final settingProvider = Provider.of(context); + final userProvider = Provider.of(context); + final menuProvider = Provider.of(context); + return LoadingManager( + isLoading: menuProvider.isLoading, + isFailedLoading: menuProvider.items == null, + stateCode: menuProvider.stateCode, + onRefresh: () async { + menuProvider.reset(); + await menuProvider.getData( + user: userProvider.user, + host: settingProvider.host + ); + }, + child: SingleStatusMenu( + initialStatus: initialValue, + statuses: menuProvider.items, + onSelect: onSelect, + ) + ); + } +} diff --git a/lib/views/widgets/status/gas_refill/gas_status.dart b/lib/views/widgets/status/gas_refill/gas_status.dart new file mode 100644 index 0000000..524d8ac --- /dev/null +++ b/lib/views/widgets/status/gas_refill/gas_status.dart @@ -0,0 +1,39 @@ +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_cylinder_size_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_status_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_types_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class GasStatusMenu extends StatelessWidget { + final Function(Status) onSelect; + final Status initialValue; + + const GasStatusMenu({Key key, this.onSelect, this.initialValue}) : super(key: key); + @override + Widget build(BuildContext context) { + final settingProvider = Provider.of(context); + final userProvider = Provider.of(context); + final menuProvider = Provider.of(context); + return LoadingManager( + isLoading: menuProvider.isLoading, + isFailedLoading: menuProvider.items == null, + stateCode: menuProvider.stateCode, + onRefresh: () async { + menuProvider.reset(); + await menuProvider.getData( + user: userProvider.user, + host: settingProvider.host + ); + }, + child: SingleStatusMenu( + initialStatus: initialValue, + statuses: menuProvider.items, + onSelect: onSelect, + ) + ); + } +} diff --git a/lib/views/widgets/status/gas_refill/gas_type.dart b/lib/views/widgets/status/gas_refill/gas_type.dart new file mode 100644 index 0000000..9d9b47f --- /dev/null +++ b/lib/views/widgets/status/gas_refill/gas_type.dart @@ -0,0 +1,40 @@ +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_cylinder_size_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_status_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/gas_refill/gas_types_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_types_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class GasTypeMenu extends StatelessWidget { + final Function(Status) onSelect; + final Status initialValue; + + const GasTypeMenu({Key key, this.onSelect, this.initialValue}) : super(key: key); + @override + Widget build(BuildContext context) { + final settingProvider = Provider.of(context); + final userProvider = Provider.of(context); + final menuProvider = Provider.of(context); + return LoadingManager( + isLoading: menuProvider.isLoading, + isFailedLoading: menuProvider.items == null, + stateCode: menuProvider.stateCode, + onRefresh: () async { + menuProvider.reset(); + await menuProvider.getData( + user: userProvider.user, + host: settingProvider.host + ); + }, + child: SingleStatusMenu( + initialStatus: initialValue, + statuses: menuProvider.items, + onSelect: onSelect, + ) + ); + } +} diff --git a/lib/views/widgets/status/multi_status_menu.dart b/lib/views/widgets/status/multi_status_menu.dart new file mode 100644 index 0000000..b0c8272 --- /dev/null +++ b/lib/views/widgets/status/multi_status_menu.dart @@ -0,0 +1,132 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +import 'package:test_sa/views/widgets/app_text_form_field.dart'; +class MultiStatusMenu extends StatefulWidget { + final List statuses; + final List initialSelectedStatus; + final Function(List) onSelect; + + const MultiStatusMenu({Key key, this.statuses, this.onSelect, this.initialSelectedStatus}) : super(key: key); + @override + _MultiStatusMenuState createState() => _MultiStatusMenuState(); +} + +class _MultiStatusMenuState extends State { + + List _selectedStatus = []; + TextEditingController _controller; + + @override + void initState() { + _controller = TextEditingController(); + _selectedStatus.addAll(widget.initialSelectedStatus); + super.initState(); + } + + @override + void dispose() { + _controller.clear(); + super.dispose(); + } + @override + Widget build(BuildContext context) { + return + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + crossAxisAlignment: WrapCrossAlignment.start, + alignment: WrapAlignment.start, + runAlignment: WrapAlignment.start, + children: List.generate( + _selectedStatus.length, + (index) { + final status = _selectedStatus[index]; + return Container( + height: 36 * AppStyle.getScaleFactor(context), + margin: EdgeInsets.all(4 * AppStyle.getScaleFactor(context)), + //padding: EdgeInsets.all(4 * AppStyle.getScaleFactor(context)), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.circular(8) + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 12,), + Text( + status.label, + style: Theme.of(context).textTheme.bodyText1.copyWith( + color:Theme.of(context).colorScheme.onPrimary, + ), + ), + IconButton( + color:Theme.of(context).colorScheme.onPrimary, + onPressed: (){ + _selectedStatus.remove(status); + widget.onSelect(_selectedStatus); + setState(() {}); + }, + icon: const Icon(Icons.delete) + ) + ], + ) + ); + } + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16 + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color:AColors.black), + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + boxShadow: const [ + AppStyle.boxShadow + ] + ), + child: TypeAheadField( + textFieldConfiguration: TextFieldConfiguration( + style: Theme.of(context).textTheme.subtitle1, + controller: _controller, + textAlign: TextAlign.center, + decoration: const InputDecoration( + border: InputBorder.none, + disabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + textInputAction: TextInputAction.search, + ), + suggestionsCallback: (vale) { + return widget.statuses.where((Status option) { + return option.label.toLowerCase().contains(_controller.text); + }); + }, + itemBuilder: (context, part) { + return ListTile( + title: Text(part.label), + ); + }, + onSuggestionSelected: (status) { + _controller.clear(); + if(!_selectedStatus.contains(status)){ + _selectedStatus.add(status); + widget.onSelect(_selectedStatus); + setState(() {}); + } + }, + ), + ), + ], + ); + } +} diff --git a/lib/views/widgets/status/report/service_report_last_call.dart b/lib/views/widgets/status/report/service_report_last_call.dart new file mode 100644 index 0000000..7093e7e --- /dev/null +++ b/lib/views/widgets/status/report/service_report_last_call.dart @@ -0,0 +1,29 @@ +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_report_last_calls_provider.dart'; +import 'package:test_sa/models/service_report.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class ServiceReportLastCallsMenu extends StatelessWidget { + final Function(Status) onSelect; + final ServiceReport report; + + const ServiceReportLastCallsMenu({ + Key key,@required this.onSelect,@required this.report}) : super(key: key); + @override + Widget build(BuildContext context) { + ServiceReportLastCallsProvider _menuProvider = Provider.of(context); + return LoadingManager( + isLoading: _menuProvider.isLoading, + isFailedLoading: _menuProvider.calls == null, + stateCode: _menuProvider.stateCode, + onRefresh: () async {}, + child: SingleStatusMenu( + initialStatus: report?.callLastSituation, + statuses: _menuProvider.calls, + onSelect: onSelect, + ) + ); + } +} diff --git a/lib/views/widgets/status/report/service_report_reasons.dart b/lib/views/widgets/status/report/service_report_reasons.dart new file mode 100644 index 0000000..8aa4eff --- /dev/null +++ b/lib/views/widgets/status/report/service_report_reasons.dart @@ -0,0 +1,38 @@ +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_report_reasons_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class ServiceReportReasonsMenu extends StatelessWidget { + final Status initialValue; + final Function(Status) onSelect; + + const ServiceReportReasonsMenu({Key key, this.onSelect, this.initialValue}) : super(key: key); + @override + Widget build(BuildContext context) { + SettingProvider _settingProvider = Provider.of(context); + UserProvider _userProvider = Provider.of(context); + ServiceReportReasonsProvider _menuProvider = Provider.of(context); + return LoadingManager( + isLoading: _menuProvider.isLoading, + isFailedLoading: _menuProvider.reasons == null, + stateCode: _menuProvider.stateCode, + onRefresh: () async { + _menuProvider.reset(); + await _menuProvider.getTypes( + user: _userProvider.user, + host: _settingProvider.host, + + ); + }, + child: SingleStatusMenu( + initialStatus: initialValue, + statuses: _menuProvider.reasons, + onSelect: onSelect, + ) + ); + } +} diff --git a/lib/views/widgets/status/report/service_report_status.dart b/lib/views/widgets/status/report/service_report_status.dart new file mode 100644 index 0000000..41033e8 --- /dev/null +++ b/lib/views/widgets/status/report/service_report_status.dart @@ -0,0 +1,70 @@ +import 'dart:math'; + +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_report_last_calls_provider.dart'; +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_report_status_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/service_report.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class ServiceReportStatusMenu extends StatefulWidget { + final Function(Status) onSelect; + final ServiceReport report; + + const ServiceReportStatusMenu({Key key, this.onSelect, this.report}) : super(key: key); + + @override + State createState() => _ServiceReportStatusMenuState(); +} + +class _ServiceReportStatusMenuState extends State { + bool firstTime = true; + @override + Widget build(BuildContext context) { + SettingProvider _settingProvider = Provider.of(context); + UserProvider _userProvider = Provider.of(context); + ServiceReportStatusProvider _menuProvider = Provider.of(context); + ServiceReportLastCallsProvider _callsLastSituationsProvider = Provider.of(context,listen: false); + if(firstTime){ + _callsLastSituationsProvider.reset(); + + firstTime = false; + } + return LoadingManager( + isLoading: _menuProvider.isLoading == true || _callsLastSituationsProvider.isLoading == true, + isFailedLoading: _menuProvider.statuses == null || _callsLastSituationsProvider.calls == null, + stateCode: _menuProvider.stateCode == null || _callsLastSituationsProvider.stateCode == null ? null: + max(_menuProvider.stateCode ?? 0,_callsLastSituationsProvider.stateCode ?? 0), + onRefresh: () async { + if(_menuProvider.stateCode == null){ + _menuProvider.reset(); + await _menuProvider.getTypes( + user: _userProvider.user, + host: _settingProvider.host + ); + } + await _callsLastSituationsProvider.getCalls( + user: _userProvider.user, + host: _settingProvider.host, + serviceStatus: widget.report.status?.id.toString() + ); + }, + child: SingleStatusMenu( + statuses: _menuProvider.statuses, + initialStatus: widget.report.status, + onSelect: (status){ + _callsLastSituationsProvider.getCalls( + user: _userProvider.user, + host: _settingProvider.host, + serviceStatus: status.id.toString() + ); + widget.report.callLastSituation = null; + widget.onSelect(status); + }, + ) + ); + } +} diff --git a/lib/views/widgets/status/report/service_report_type.dart b/lib/views/widgets/status/report/service_report_type.dart new file mode 100644 index 0000000..26e4eb1 --- /dev/null +++ b/lib/views/widgets/status/report/service_report_type.dart @@ -0,0 +1,38 @@ +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_report_types_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class ServiceReportTypeMenu extends StatelessWidget { + final Function(Status) onSelect; + final Status initialValue; + + const ServiceReportTypeMenu({Key key, this.onSelect, this.initialValue}) : super(key: key); + @override + Widget build(BuildContext context) { + SettingProvider _settingProvider = Provider.of(context); + UserProvider _userProvider = Provider.of(context); + ServiceReportTypesProvider _menuProvider = Provider.of(context); + return LoadingManager( + isLoading: _menuProvider.isLoading, + isFailedLoading: _menuProvider.types == null, + stateCode: _menuProvider.stateCode, + onRefresh: () async { + _menuProvider.reset(); + await _menuProvider.getTypes( + user: _userProvider.user, + host: _settingProvider.host + ); + onSelect(initialValue ?? _menuProvider.types?.last); + }, + child: SingleStatusMenu( + initialStatus: initialValue ?? _menuProvider.types?.last, + statuses: _menuProvider.types, + onSelect: onSelect, + ) + ); + } +} diff --git a/lib/views/widgets/status/report/service_status.dart b/lib/views/widgets/status/report/service_status.dart new file mode 100644 index 0000000..6851ab3 --- /dev/null +++ b/lib/views/widgets/status/report/service_status.dart @@ -0,0 +1,37 @@ +import 'package:test_sa/controllers/providers/api/status_drop_down/report/service_types_provider.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/status/single_status_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class ServiceStatusMenu extends StatelessWidget { + final Function(Status) onSelect; + final Status initialValue; + + const ServiceStatusMenu({Key key, this.onSelect, this.initialValue}) : super(key: key); + @override + Widget build(BuildContext context) { + SettingProvider _settingProvider = Provider.of(context); + UserProvider _userProvider = Provider.of(context); + ServiceStatusProvider _menuProvider = Provider.of(context); + return LoadingManager( + isLoading: _menuProvider.isLoading, + isFailedLoading: _menuProvider.statuses == null, + stateCode: _menuProvider.stateCode, + onRefresh: () async { + _menuProvider.reset(); + await _menuProvider.getTypes( + user: _userProvider.user, + host: _settingProvider.host + ); + }, + child: SingleStatusMenu( + initialStatus: initialValue, + statuses: _menuProvider.statuses, + onSelect: onSelect, + ) + ); + } +} diff --git a/lib/views/widgets/status/single_status_menu.dart b/lib/views/widgets/status/single_status_menu.dart new file mode 100644 index 0000000..6923b55 --- /dev/null +++ b/lib/views/widgets/status/single_status_menu.dart @@ -0,0 +1,96 @@ +import 'package:test_sa/models/status.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class SingleStatusMenu extends StatefulWidget { + final List statuses; + final Status initialStatus; + final Function(Status) onSelect; + + const SingleStatusMenu({Key key, this.statuses, this.onSelect, this.initialStatus}) : super(key: key); + @override + _SingleStatusMenuState createState() => _SingleStatusMenuState(); +} + +class _SingleStatusMenuState extends State { + + Status _selectedStatus; + + @override + void didUpdateWidget(covariant SingleStatusMenu oldWidget) { + if(widget.initialStatus != null){ + _selectedStatus = widget.statuses?.firstWhere( + (element) { + return element?.id == widget.initialStatus.id; + }); + } else { + _selectedStatus = null; + } + super.didUpdateWidget(oldWidget); + } + + @override + void initState() { + if(widget.initialStatus != null){ + _selectedStatus = widget.statuses?.firstWhere( + (element) { + return element?.id == widget.initialStatus.id; + }); + } + + super.initState(); + } + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16 + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color:AColors.black), + borderRadius: BorderRadius.circular( + AppStyle.borderRadius * AppStyle.getScaleFactor(context) + ), + boxShadow: const [ + AppStyle.boxShadow + ] + ), + child: DropdownButton( + value: _selectedStatus, + iconSize: 24, + elevation: 16, + isExpanded: true, + hint: Text( + "Select", + style: Theme.of(context).textTheme.subtitle1, + ), + style: TextStyle( + color: Theme.of(context).primaryColor + ), + underline: SizedBox.shrink(), + onChanged: (Status newValue) { + setState(() { + _selectedStatus = newValue; + }); + widget.onSelect(newValue); + }, + items: widget.statuses + .map>((Status value) { + return DropdownMenuItem( + value: value, + child: Text( + value.label, + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: Theme.of(context).primaryColor, + fontSize: 11, + //fontWeight: FontWeight.bold + ), + ), + ); + }) + .toList(), + ), + ); + } +} diff --git a/lib/views/widgets/timer/app_timer.dart b/lib/views/widgets/timer/app_timer.dart new file mode 100644 index 0000000..c2c332b --- /dev/null +++ b/lib/views/widgets/timer/app_timer.dart @@ -0,0 +1,150 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; +class AppTimer extends StatefulWidget { + final TimerModel timer; + final Future Function(TimerModel) onChange; + final TextStyle style; + const AppTimer({ + Key key, + this.timer, + this.onChange, + this.style, + }) : super(key: key); + + @override + State createState() => _AppTimerState(); +} + +class _AppTimerState extends State { + Timer _timer; + DateTime _startAt; + DateTime _endAt; + int _delay = 0; + bool _running = false; + bool _loading = false; + final ValueNotifier _period = ValueNotifier("0:00:00"); + + _startTimer() async { + final time = DateTime.now(); + bool result = await widget.onChange( + TimerModel(startAt: time,endAt: null,durationInSecond: _delay)); + if(!result) return; + _running = true; + + if(_endAt != null){ + _delay += _endAt.difference(_startAt).inSeconds; + } + _startAt = time.subtract(Duration(seconds: _delay)); + _endAt = null; + + + + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + if(_loading == true) return; + _period.value = (_endAt ?? DateTime.now()).difference( + _startAt + ).toString().split(".").first; + }); + } + + _stopTimer() async { + final time = DateTime.now(); + final tempStartAt = _startAt.add(Duration(seconds: _delay)); + bool result = await widget.onChange( + TimerModel(startAt: tempStartAt,endAt: time,durationInSecond: _delay)); + if(!result) return; + _running = false; + _endAt = time; + _startAt = tempStartAt; + _timer?.cancel(); + } + + _onPressed() async { + _loading = true; + setState(() {}); + if(!_running){ + await _startTimer(); + }else{ + await _stopTimer(); + } + _loading = false; + setState(() {}); + } + + @override + void initState() { + // TODO: implement initState + // if(widget.timer?.isNotEmpty == true){ + // _startAt = widget.timer.last.startAt; + // _endAt = widget.timer.last.endAt; + // _running = _startAt != null && _endAt == null; + // print(_startAt); + // print(_endAt); + // print(_running); + // if(widget.timer.length != 1){ + // for (int i = 0;i( + valueListenable: _period, + builder: (context,value,_){ + return Text(value, + style: widget.style, + ); + } + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/timer/timer_manager.dart b/lib/views/widgets/timer/timer_manager.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/views/widgets/titles/app_sub_title.dart b/lib/views/widgets/titles/app_sub_title.dart new file mode 100644 index 0000000..0d611db --- /dev/null +++ b/lib/views/widgets/titles/app_sub_title.dart @@ -0,0 +1,24 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class ASubTitle extends StatelessWidget { + final String text; + final EdgeInsets padding; + final Color color; + final double font; + const ASubTitle(this.text, {Key key,this.padding, this.color, this.font}) : super(key: key); + @override + Widget build(BuildContext context) { + return Padding( + padding: padding ?? EdgeInsets.zero, + child: Text( + text, + style: Theme.of(context).textTheme.subtitle2.copyWith( + // fontWeight: FontWeight.bold, + fontSize: font, + color: color + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ); + } +} diff --git a/lib/views/widgets/titles/app_title.dart b/lib/views/widgets/titles/app_title.dart new file mode 100644 index 0000000..e97f8b9 --- /dev/null +++ b/lib/views/widgets/titles/app_title.dart @@ -0,0 +1,26 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class ATitle extends StatelessWidget { + final String text; + final EdgeInsets padding; + final bool center; + const ATitle(this.text, {Key key,this.padding,this.center= false}) : super(key: key); + @override + Widget build(BuildContext context) { + return Padding( + padding: padding ?? EdgeInsets.symmetric(horizontal: 16,vertical: 8), + child: Row( + mainAxisAlignment: center + ? MainAxisAlignment.center + : MainAxisAlignment.start, + children: [ + Text( + text, + style: Theme.of(context).textTheme.headline6, + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ], + ), + ); + } +} diff --git a/lib/views/widgets/titles/expandable_info_row.dart b/lib/views/widgets/titles/expandable_info_row.dart new file mode 100644 index 0000000..c67ff85 --- /dev/null +++ b/lib/views/widgets/titles/expandable_info_row.dart @@ -0,0 +1,93 @@ +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +class ExpandableInfoRow extends StatefulWidget { + final IconData iconData; + final String title; + final Widget child; + + const ExpandableInfoRow({Key key, this.iconData, this.title, this.child}) : super(key: key); + + @override + _ExpandableInfoRowState createState() => _ExpandableInfoRowState(); +} + +class _ExpandableInfoRowState extends State + with TickerProviderStateMixin{ + bool _isExpanded = false; + @override + Widget build(BuildContext context) { + return InkWell( + onTap: (){ + _isExpanded = !_isExpanded; + setState(() {}); + }, + child: Column( + children: [ + Row( + children: [ + widget.iconData != null ? + Padding( + padding: EdgeInsets.symmetric( + horizontal: 8 * AppStyle.getScaleFactor(context), + vertical: 2 * AppStyle.getScaleFactor(context), + ), + child: FaIcon( + widget.iconData, + color: Theme.of(context).primaryColor, + size: 20 * AppStyle.getScaleFactor(context), + ), + ):SizedBox.shrink(), + Expanded( + flex: 2, + child: Text( + widget.title, + style: TextStyle( + //color: Theme.of(context).dividerColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + textScaleFactor: AppStyle.getScaleFactor(context), + ), + ), + AnimatedSwitcher( + duration: Duration(milliseconds: 400), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + child: ScaleTransition( + child: child, + scale: animation + ), + opacity: animation, + ); + }, + child: Icon( + _isExpanded + ? Icons.keyboard_arrow_up + : Icons.keyboard_arrow_down, + key: ValueKey(_isExpanded + ? "Icons.keyboard_arrow_up" + : "Icons.keyboard_arrow_down"), + color: Theme.of(context).primaryColor, + size: 24 * AppStyle.getScaleFactor(context), + ), + ), + ], + ), + AnimatedSize( + duration: Duration(milliseconds: 300), + child: Visibility( + visible: _isExpanded, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 8 * AppStyle.getScaleFactor(context)), + child: widget.child, + ) + ), + ), + Divider(height: 2 * AppStyle.getScaleFactor(context),), + SizedBox(height: 8 * AppStyle.getScaleFactor(context),), + ], + ), + ); + } +} diff --git a/lib/views/widgets/visits/visit_item.dart b/lib/views/widgets/visits/visit_item.dart new file mode 100644 index 0000000..3990e41 --- /dev/null +++ b/lib/views/widgets/visits/visit_item.dart @@ -0,0 +1,194 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/models/enums/user_types.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/visits/visit.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/views/widgets/visits/visit_status.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +class VisitItem extends StatelessWidget { + final Visit visit; + final int index; + final bool isSelected; + final bool activeSelectMod; + final Function(Visit) onPressed; + final Function(Visit) onLongPress; + final Function(Visit) onSelect; + const VisitItem({ + Key key, + this.visit, + this.onPressed, + this.isSelected = false, + this.activeSelectMod = false, + this.onLongPress, + this.onSelect, this.index + }) : super(key: key); + + + @override + Widget build(BuildContext context) { + UserProvider _userProvider = Provider.of(context); + Color itemColor = index % 2 == 0 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onPrimary; + Color onItemColor = index % 2 != 0 + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onPrimary; + Subtitle _subtitle = AppLocalization.of(context).subtitle; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 8,horizontal: 8), + primary: itemColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + ), + ), + onPressed: (){ + if(activeSelectMod && _userProvider.user.type == UsersTypes.engineer) + onSelect(visit); + else + onPressed(visit); + }, + onLongPress: _userProvider.user.type == UsersTypes.engineer ? (){ + onLongPress(visit); + } : null, + + child:Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + "S.N: "+visit.deviceSerialNumber ?? "No serial number", + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 16, + fontWeight: FontWeight.bold + ), + ), + ), + AnimatedSwitcher( + duration: Duration(milliseconds: 400), + child: Visibility( + key: ValueKey(activeSelectMod), + visible: activeSelectMod, + child: SizedBox( + height: 18, + child: Checkbox( + activeColor: Colors.black38, + + value: isSelected, + onChanged: (value){ + onSelect(visit); + } + ), + ), + ), + ), + VisitStatusLabel(visit: visit,) + ], + ), + Divider(color: onItemColor,), + Row( + children: [ + Expanded( + child: Text( + visit.hospitalName ?? "No client found", + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: onItemColor, + fontSize: 14, + ), + ), + ), + Text( + visit.modelAndBrand ?? "", + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: onItemColor, + fontSize: 14, + ), + ), + ], + ), + Divider(color: onItemColor,), + Row( + children: [ + Expanded( + child: Text( + visit.employName ?? "No employ found", + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 14, + fontWeight: FontWeight.bold + ), + ), + ), + Text( + visit.contactStatus ?? "", + style: Theme.of(context).textTheme.subtitle1.copyWith( + color: onItemColor, + fontSize: 14, + ), + ), + ], + ), + Divider(color: onItemColor,), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _subtitle.expectDate, + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 14, + + ), + ), + Text( + visit.expectDate ?? _subtitle.noDateFound, + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 14, + + ), + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _subtitle.actualDate, + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 14, + ), + ), + Text( + visit.actualDate ?? _subtitle.noDateFound, + style: Theme.of(context).textTheme.headline6.copyWith( + color: onItemColor, + fontSize: 14, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/widgets/visits/visit_status.dart b/lib/views/widgets/visits/visit_status.dart new file mode 100644 index 0000000..7309dcd --- /dev/null +++ b/lib/views/widgets/visits/visit_status.dart @@ -0,0 +1,44 @@ +import 'package:test_sa/models/visits/visit.dart'; +import 'package:test_sa/views/app_style/colors.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:flutter/material.dart'; +class VisitStatusLabel extends StatelessWidget { + final Visit visit; + + const VisitStatusLabel({Key key, this.visit}) : super(key: key); + + Color getStatusColor(){ + switch(visit.status.id){ + case 0: return AColors.green; + case 1: return AColors.grey; + case 2: return AColors.grey; + default : return AColors.grey; + } + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(vertical: 2,horizontal: 8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: getStatusColor(), + borderRadius: BorderRadius.circular( + AppStyle.getBorderRadius(context) + ), + boxShadow: [ + AppStyle.boxShadow + ] + ), + child: Text( + visit.status.label == null + || visit.status.label.isEmpty + ? "no status" :visit.status.label, + style: Theme.of(context).textTheme.subtitle2.copyWith( + color: getStatusColor().computeLuminance() > 0.5 + ? AColors.black : Colors.white, + ), + ) + ); + } +} diff --git a/lib/views/widgets/visits/visits_list.dart b/lib/views/widgets/visits/visits_list.dart new file mode 100644 index 0000000..a5b0596 --- /dev/null +++ b/lib/views/widgets/visits/visits_list.dart @@ -0,0 +1,127 @@ +import 'package:test_sa/controllers/localization/localization.dart'; +import 'package:test_sa/models/subtitle.dart'; +import 'package:test_sa/models/visits/visit.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; +import 'package:test_sa/views/pages/user/visits/visit_details.dart'; +import 'package:test_sa/views/widgets/loaders/lazy_loading.dart'; +import 'package:test_sa/views/widgets/loaders/no_item_found.dart'; +import 'package:test_sa/views/widgets/visits/visit_item.dart'; +import 'package:flutter/material.dart'; +class VisitsList extends StatefulWidget { + final List visits; + final bool nextPage; + final Future Function() onLazyLoad; + final Function(List) onEditGroup; + + const VisitsList({ + Key key, + this.visits, + this.nextPage, + this.onLazyLoad, + this.onEditGroup + }) : super(key: key); + + @override + _VisitsListState createState() => _VisitsListState(); +} + +class _VisitsListState extends State { + List _selectedVisits = []; + @override + void initState() { + _selectedVisits.clear(); + super.initState(); + } + @override + Widget build(BuildContext context) { + Subtitle _subtitle = AppLocalization.of(context).subtitle; + if(widget.visits.length == 0){ + return NoItemFound(message: _subtitle.noVisitsFound,); + } + return Stack( + children: [ + LazyLoading( + nextPage: widget.nextPage, + onLazyLoad: widget.onLazyLoad, + child: ListView.builder( + //physics: BouncingScrollPhysics(), + itemCount: widget.visits.length, + padding: EdgeInsets.symmetric(horizontal: 16,vertical: 8), + itemBuilder: (context,itemIndex){ + Visit _visit = widget.visits[itemIndex]; + bool _isSelected = _selectedVisits.contains(_visit); + return VisitItem( + visit: _visit, + isSelected: _isSelected, + index: itemIndex, + activeSelectMod: _selectedVisits.isNotEmpty, + onPressed: (visit){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (_)=> VisitDetailsPage(visit: visit,) + ) + ); + }, + onSelect: (visit){ + if(_isSelected){ + _selectedVisits.remove(visit); + } else { + _selectedVisits.add(visit); + } + setState(() {}); + }, + onLongPress: (visit){ + if(_isSelected){ + _selectedVisits.remove(visit); + } else { + _selectedVisits.add(visit); + } + setState(() {}); + }, + ); + } + ), + ), + Align( + alignment: Alignment.bottomLeft, + child: Padding( + padding: EdgeInsets.all(8.0 * AppStyle.getScaleFactor(context)), + child: AnimatedSwitcher( + duration: Duration(milliseconds: 400), + child: Visibility( + key: ValueKey(_selectedVisits.isNotEmpty), + visible: _selectedVisits.isNotEmpty, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FloatingActionButton( + heroTag: "cancel", + child: Icon(Icons.cancel), + onPressed: (){ + _selectedVisits.clear(); + setState(() {}); + }, + ), + FloatingActionButton( + heroTag: "edit", + child: Icon(Icons.edit), + onPressed: (){ + if(!widget.visits.contains(_selectedVisits.first)){ + _selectedVisits.clear(); + setState(() {}); + return; + } + widget.onEditGroup(_selectedVisits); + //_selectedVisits.clear(); + }, + ), + ], + ), + ), + ), + ), + ) + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..6dc640b --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1039 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.11" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.5" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.9.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.3" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.10.0" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3+2" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + dbus: + dependency: transitive + description: + name: dbus + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.8" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.5.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + url: "https://pub.dartlang.org" + source: hosted + version: "14.2.0" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.9" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.10" + flare_flutter: + dependency: "direct main" + description: + name: flare_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.0" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + url: "https://pub.dartlang.org" + source: hosted + version: "5.4.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter_launcher_icons: + dependency: "direct main" + description: + name: flutter_launcher_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + url: "https://pub.dartlang.org" + source: hosted + version: "12.0.4" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + flutter_sound: + dependency: "direct main" + description: + name: flutter_sound + url: "https://pub.dartlang.org" + source: hosted + version: "9.2.13" + flutter_sound_platform_interface: + dependency: transitive + description: + name: flutter_sound_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "9.2.13" + flutter_sound_web: + dependency: transitive + description: + name: flutter_sound_web + url: "https://pub.dartlang.org" + source: hosted + version: "9.2.13" + flutter_svg: + dependency: transitive + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_typeahead: + dependency: "direct main" + description: + name: flutter_typeahead + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.1" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.2" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "10.3.0" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.5" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.2" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.6" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.5+4" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.10" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.6+4" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.2" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.7.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + logger: + dependency: transitive + description: + name: logger + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + package_info: + dependency: "direct main" + description: + name: package_info + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.22" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "10.2.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + url: "https://pub.dartlang.org" + source: hosted + version: "10.2.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.7" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.9.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.5" + qr_code_scanner: + dependency: "direct main" + description: + name: qr_code_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + recase: + dependency: transitive + description: + name: recase + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" + rive: + dependency: "direct main" + description: + name: rive + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.1" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.27.7" + share: + dependency: "direct main" + description: + name: share + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.14" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + signature: + dependency: "direct main" + description: + name: signature + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + speech_to_text: + dependency: "direct main" + description: + name: speech_to_text + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.1" + speech_to_text_macos: + dependency: transitive + description: + name: speech_to_text_macos + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + speech_to_text_platform_interface: + dependency: transitive + description: + name: speech_to_text_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0+2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.12" + timezone: + dependency: transitive + description: + name: timezone + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.1" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.7" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.22" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.7" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+2" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..f4f9eb3 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,102 @@ +name: test_sa +description: medical app to manage medical devices issuse + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 0.6.1+1 + +environment: + sdk: ">=2.7.0 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.5 + font_awesome_flutter: ^10.2.1 + http: ^0.13.5 + provider: ^6.0.3 + shared_preferences: ^2.0.15 + fluttertoast: ^8.0.9 + image_picker: ^0.8.6 + url_launcher: ^6.1.6 + flutter_launcher_icons: ^0.10.0 + package_info: ^2.0.2 + share: ^2.0.4 + flutter_local_notifications: ^12.0.1+1 + cached_network_image: ^3.2.2 + carousel_slider: ^4.1.1 + intl: ^0.17.0 + flutter_typeahead: ^4.1.1 + speech_to_text: ^6.1.1 + firebase_core: ^2.4.0 + firebase_messaging: ^14.2.0 + qr_code_scanner: ^1.0.1 + flutter_sound: ^9.2.13 + permission_handler: ^10.2.0 + rive: ^0.9.1 + audioplayers: ^1.1.1 + flare_flutter: ^3.0.2 + signature: ^5.3.0 + + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +flutter_icons: + ios: true + android: true + image_path_ios: "assets/images/app_logo.jpg" + image_path_android: "assets/images/app_logo.jpg" + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + assets: + - assets/ + - assets/images/ + - assets/subtitles/ + - assets/rives/ + fonts: + - family: Swiss + fonts: + - asset: assets/fonts/Gotham_Rounded_Light.otf + weight: 300 + - asset: assets/fonts/Gotham_Rounded_Book.otf + weight: 400 + - asset: assets/fonts/Gotham_Rounded_Bold.otf + weight: 700 diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..9018cb4 --- /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:test_sa/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(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); + }); +}